* Default new lightweight routines to tools-enabled
* Fix fmt and clippy on lightweight routine PR
* Use grouped execution field in routine no-tools fixture
* Align CLI routine defaults with tools-enabled lightweight mode
* feat(ux): complete UX overhaul — design system, boot screen, onboarding, web polish
Shared design system: CSS custom properties for spacing, typography,
transitions, and color tokens used across web UI and boot screen.
Boot screen: compact feature-tags line showing enabled subsystems
(db, tools, routines, heartbeat, skills, sandbox, embeddings) at a
glance. Downgrade startup info logs (libSQL, webhook, workspace seed)
to debug level since the boot screen now covers this.
Onboarding wizard: model picker with live API fetch, provider-aware
auth flow, improved error recovery and progress display.
Web UI: ARIA attributes, welcome card, streaming debounce,
connection status banner, skeleton loaders, send cooldown.
CLI: doctor command enhancements, status command cleanup,
REPL banner consolidation, shared fmt module.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* feat(ux): Apple-level design refinements — spring physics, glass morphism, chat polish
Merge staging theme support (dark/light/system toggle) and layer UX
polish on top: spring-physics motion, glass morphism depth, chat
experience improvements, and responsive mobile refinements.
Design system:
- Restore and extend design token system (spacing, typography, timing,
easing) with legacy aliases for theme compatibility
- Add shadow tiers, accent glow, glass morphism, spring easing tokens
- Tokens defined in both dark (:root) and light ([data-theme="light"])
Micro-interactions (Phase 2):
- Spring-overshoot message entry animation (slideUp)
- Spring-scale button press on all interactive buttons
- Tab crossfade animation, tool card smooth accordion (max-height)
- Modal scale(0.95) + blur(8px) entry, toast spring slide
- Sidebar width crossfade, card hover lift
Visual depth (Phase 3):
- Tab bar glass morphism + surface highlight + sliding indicator
- Active tab accent background pill
- Assistant message accent left border, user message bubble tail
- Floating input area (rounded + shadow + margin)
Chat polish (Phase 4):
- Smooth streaming cursor (cursorPulse), message hover timestamps
- Time separators (Today/Yesterday/date)
- Textarea smooth auto-expand, send button glow
Settings & forms (Phase 5):
- iOS-style toggle switches for boolean settings
- Input focus glow, save feedback spring animation
- Welcome card with gradient background + proper spacing
- Sticky settings group headers with glass backdrop
Accessibility & mobile (Phase 6):
- Animated focus ring, prefers-reduced-motion global kill-switch
- Touch target audit (44px min), mobile bottom-sheet modals
- Mobile bottom tab bar, toast redesign (icon + border + countdown)
- Thread hover translateX, badge in_progress pulse
Bug fixes:
- Gateway/TEE popover z-index (tab-bar z-index: 200, popovers 500)
- Connection lost banner as fixed top bar instead of flex child
- Sidebar collapse keeps toggle + new thread buttons visible
- Downgrade noisy startup logs (db, webhook, vector) to debug
- Remove green dot pulse animation on connected status
- Deduplicate confirm-modal in HTML, add tab-indicator div
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* feat(web): mobile layout improvements — sidebar toggle, settings drill-down, tab bar polish
- Fix mobile sidebar toggle: use expanded-mobile class instead of collapsed,
add backdrop overlay, auto-close on thread select, outside-click dismiss
- Settings: replace cramped horizontal tabs with drill-down navigation
(category list → detail view → back button)
- Bottom tab bar: add glass morphism, hide theme toggle, flip tab indicator
to top edge
- Keep thread toggle button visible in collapsed 36px sidebar strip
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* feat(repl): interactive approval selector and transient status lines
- Replace ASCII-art approval box with clean horizontal rule card
- Add inquire-based interactive selector for tool approvals (↑↓ + Enter)
- Selector runs directly from send_status via spawn_blocking, with
stdin_locked flag to prevent readline from competing for stdin
- Transient thinking/tool-started lines: each replaces the previous,
all erased before final output (no clutter left in scrollback)
- Esc in selector sends denial so agent never gets stuck
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix: widen TurnCost token fields to u64 and remove unused variable
- Change input_tokens/output_tokens from u32 to u64 in StatusUpdate::TurnCost,
SseEvent::TurnCost, and the thread_ops emit site to avoid truncation on
large conversations
- Remove unused _routine_engine_for_loop binding in agent_loop.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* chore: reduce startup log noise — demote info to debug
Demote routine startup messages (builder, WASM tools, tunnel, WASM
channels) from info to debug so the default log output stays clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix(web): allow CDN scripts in CSP connect-src directive
Add cdn.jsdelivr.net and cdnjs.cloudflare.com to connect-src so the
browser can fetch marked.js and DOMPurify without CSP violations.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* style: fix cargo fmt in repl.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix(web): gate turn_cost SSE handler on current thread
Prevents cost badge from attaching to the wrong message when
switching threads or receiving events from background threads.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* ci: retrigger CI
* fix: add missing extension_manager to webhook EngineContext
The webhook trigger path added in #736 was missing the
extension_manager field introduced by #1453.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* chore: ignore RUSTSEC-2026-0049 rustls-webpki CRL advisory
Low impact — requires compromised CA to exploit. Tracked for
upstream rustls-webpki upgrade.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix(routines): use fields.join for cron normalization
Use split_whitespace fields instead of re-trimming the original string
to avoid preserving extra internal whitespace in cron expressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* feat(repl): Apple-style approval card — clean vertical flow
- Drop verbose tool description (the command IS the decision surface)
- Unified vertical pipe layout: ◆ header → │ params → │ selector
- Selector options show keyboard shortcuts inline: Approve (y)
- Compact help message, answered state uses └ to close the flow
- No horizontal rules, no blank-line padding — just breathing room
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* refactor(repl): replace inquire with crossterm for approval selector
Drop the inquire dependency (which pulled in crossterm 0.25, duplicating
the existing 0.28). The 3-option approval selector is now built directly
with crossterm raw mode — same UX, zero new dependencies.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* chore(deps): upgrade crossterm 0.28 → 0.29, eliminate duplication
termimad (via crokey) uses crossterm 0.29. Upgrading our direct
dependency from 0.28 to 0.29 collapses to a single crossterm version
in the dependency tree. Also migrated termimad::crossterm:: references
to the direct crossterm import.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix: address review comments — box_top off-by-one, smart_truncate overflow, mobile theme toggle
- Fix box_top() fill calculation: was off-by-one, producing boxes 1 char
too wide (fmt.rs)
- Fix smart_truncate(): account for "..." in the budget so output never
exceeds max_chars (repl.rs)
- Move theme toggle to settings sidebar on mobile instead of display:none,
so mobile users can still switch themes (style.css, index.html, app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* style: cargo fmt repl.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix: address review — retry duplication, CSP connect-src, deny color
- Remove failed message before retry to prevent duplicate user messages
- Revert connect-src to 'self' — CDN hosts only need script-src
- Use red for Deny confirmation in REPL approval selector
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
* docs: add comments explaining CLI_ENABLED=false in service templates (#990)
Clarify that CLI_ENABLED=false is needed in daemon mode (launchd/systemd)
to prevent blocking on stdin when running as a background service.
Closes#990
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* Add owner-scoped full-job routine permissions
* Address PR review feedback
* Fix owner gate test timing
---------
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
* fix: add tool_info schema discovery for WASM tools
* refactor: simplify WASM schema and hint state
* refactor: store tool_info registry reference as Weak
* Add generic host-verified webhook ingress for tools
* Stabilize trace E2E test rig and approval behavior
* Fix webhook security issues from review feedback
- Reject tools without webhook_capability() (was unauthenticated RCE)
- Remove secret-in-query-string fallback (leak via logs/referrers)
- Require approval for event_emit tool (escalation via routine triggers)
- Simplify header_value() (HeaderMap already case-insensitive)
- Redact internal errors from webhook HTTP responses
- Remove unused hmac_timestamp_tolerance_secs field
- Add regression test for tool without webhook capability
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Harden webhook ingress: require auth mechanism, body limit layer, health check
- Reject webhook capabilities that declare no auth mechanism (empty
WebhookCapability would previously allow unauthenticated access)
- Add DefaultBodyLimit layer to reject oversized payloads before buffering
- Health check (GET) now verifies tool has webhook_capability(), not just
existence
- Add regression tests for all three fixes
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Fix auto_approve_tools inconsistency between dispatcher and thread_ops
dispatcher.rs skips all approval checks (including Always) when
auto_approve_tools is true, but thread_ops.rs still required approval
for Always tools. This caused deferred tool calls to unexpectedly halt
in test rigs and auto-approve configurations.
Match dispatcher behavior: short-circuit all approval when
auto_approve_tools is enabled.
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* Add event-triggered routines and workflow skill templates
* fix(ci): secrets can't be used in step if conditions [skip-regression-check] (#787)
GitHub Actions step-level `if:` doesn't have access to `secrets` context.
Replace `if: secrets.X != ''` with `continue-on-error: true` and let
the Set token step handle the fallback.
Co-authored-by: Claude Sonnet 4.6 <[email protected]>
* fix(ci): clean up staging pipeline — remove hacks, skip redundant checks [skip-regression-check] (#794)
- Remove continue-on-error from staging-ci.yml app token steps (secrets are configured)
- Skip test.yml and code_style.yml on PRs targeting staging (staging-ci.yml
already runs tests before promoting, promotion PR gets full CI on main)
- Allow ironclaw-ci[bot] in Claude Code review for bot-created promotion PRs
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix: address PR review feedback for event_emit security and quality
Security fixes:
- Require approval (UnlessAutoApproved) for event_emit, matching routine_fire
- Enable sanitization on event_emit payload (external JSON reaches LLM)
- Remove user_id parameter from event_emit to prevent IDOR — always use ctx.user_id
Correctness fixes:
- Rename source → event_source in event_emit for consistency with routine_create
- Use json_value_as_filter_string for filter parsing (handles numbers/booleans)
- Case-insensitive matching for event source and event_type
- Add debug logging for missing filter keys in payload
- Fix skill_install_routine_webhook_sim test missing .with_skills()
- Fix schema_validator test for event_emit payload properties
Code quality:
- Move EventEmitTool struct/impl after RoutineHistoryTool (fix split layout)
- Deduplicate routine_to_info into RoutineInfo::from_routine in types.rs
- Add test section headers in e2e_routine_heartbeat.rs
- Clarify event_emit description to specify system_event routines only
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(ci): run fmt + clippy on staging PRs, skip Windows clippy [skip-regression-check] (#802)
- Remove branches:[main] filter from code_style.yml so it runs on all PRs
- Gate clippy-windows with `if: github.base_ref == 'main'` (skip on staging PRs)
- Update rollup job to allow skipped clippy-windows
- Simplify claude-review.yml to only trigger on labeled event (avoids duplicate runs)
Co-authored-by: Claude Opus 4.6 <[email protected]>
* feat: persist user_id in save_job and expose job_id on routine runs (#709)
* feat: persist worker events to DB and fix activity tab rendering
In-process Worker (used by Scheduler::dispatch_job) now persists events
via save_job_event at key execution points: plan creation, LLM
responses, tool_use, tool_result, and job completion/failure/stuck.
Event data shapes match the container worker format so the gateway
activity tab renders them correctly.
Frontend: tool_result errors now show a red X icon with danger styling
instead of a silent empty output. The result event falls back to the
error field when message is absent.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: wire RoutineEngine into gateway for direct manual trigger firing
Replace the message-channel hack in routines_trigger_handler with a
direct call to RoutineEngine::fire_manual(), ensuring FullJob routines
dispatch correctly when triggered from the web UI. Inject the engine
into GatewayState from Agent::run after construction.
Also persists user_id in save_job for both PG and libSQL backends,
removes the source='sandbox' filter so all jobs are visible, and
exposes job_id on RoutineRunInfo for the frontend job link.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: remove stale gateway_state argument from Agent::new test call sites
The gateway_state parameter was removed from Agent::new during rebase
(replaced by post-construction set_routine_engine_slot), but three test
call sites still passed the extra None argument.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR review — restore sandbox source filter, remove blank lines
- Revert removal of `source = 'sandbox'` filter in all SandboxStore
queries (8 sites across PG and libSQL). Sandbox-specific APIs should
stay scoped to sandbox jobs; unified job listing for the Jobs tab
should use a separate query path.
- Remove extra blank lines in agent_loop.rs and worker.rs that caused
formatting CI failure.
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address review — regenerate Cargo.lock, add user_id regression test
- Regenerate Cargo.lock from main's lockfile to eliminate dependency
version downgrades (anyhow, syn, etc.) that were churn from rebase.
- Add regression test verifying user_id round-trips through save_job
and get_job in the libSQL backend.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: remove trailing blank line in libsql jobs.rs
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* test: add Postgres-side regression test for user_id persistence in save_job
Mirrors the existing libSQL test (test_save_job_persists_user_id) for the
Postgres backend. Gated behind #[cfg(feature = "postgres")] + #[ignore]
since it requires a running PostgreSQL instance (integration tier).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix: make routine_system_event_emit test create routine before emitting
- Add routine_create step to trace fixture so event_emit has a matching
routine to fire
- Assert fired_routines > 0, not just key presence (Copilot review)
- Add .with_auto_approve_tools(true) since event_emit now requires approval
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: renumber test headers after system_event test insertion
Test 4 was duplicated (routine_cooldown and heartbeat_findings).
Renumber heartbeat_findings to Test 5 and heartbeat_empty_skip to Test 6.
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: merge staging and add missing RoutineEngine args in test
RoutineEngine::new on staging requires `tools` and `safety` params.
Update system_event_trigger_matches_and_filters test to pass them.
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address new Copilot review comments
- Add .with_auto_approve_tools(true) to skill_install_routine_webhook_sim
test so event_emit doesn't block on approval
- Fix module-level doc comment for event_emit to specify system_event trigger
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: deduplicate json_value_as_string helper
Remove private `json_value_as_string` from routine_engine.rs and use
the identical public `json_value_as_filter_string` from routine.rs,
eliminating divergence risk. (Copilot review)
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Henry Park <[email protected]>
Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Align TestRig with the production agent wiring so create_job exercises the real scheduler path instead of silently falling back to an unscheduled context-only job. Tighten the e2e assertion to lock in the in-progress scheduler behavior for future refactors.
Made-with: Cursor
Co-authored-by: Zaki Manian <[email protected]>
* test: add 29 E2E trace tests for worker, threading, tools, workspace, and routines (#571-575)
Add comprehensive E2E test coverage across five test files:
- e2e_worker_coverage (7 tests): parallel tool calls, error feedback, unknown tools,
invalid params, rate limiting, iteration limits, planning mode
- e2e_thread_scheduling (3 tests + 2 deferred): multi-turn state, undo/redo, concurrent dispatch
- e2e_builtin_tool_coverage (8 tests): time parse/diff/invalid, routine CRUD/history,
job create/status/list/cancel, HTTP replay
- e2e_workspace_coverage (6 tests): chunked search, multi-doc search, hybrid search,
directory tree, document lifecycle, identity in system prompt
- e2e_routine_heartbeat (5 tests): cron triggers, event matching, cooldown enforcement,
heartbeat findings, empty checklist skip
Infrastructure: extend TestRig with database/workspace/trace_llm accessors, register
job and routine tools by default, add with_extra_tools() for custom stub tools.
Includes 24 JSON trace fixtures across worker/, threading/, tools/, and workspace/.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: use 6-field cron format in routine_create_list fixture
The cron 0.13 crate accepts both 6 and 7 fields, but the routine_create
tool documents 6-field format. Align the fixture to match.
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: eliminate vacuous passes and silently-skipped assertions in E2E tests
- job_create_status: replace job_status (needs dynamic UUID) with list_jobs,
assert both succeed via completed() not just started()
- job_list_cancel: keep cancel_job but explicitly assert it fails with
invalid canned job_id "latest", verify create_job + list_jobs succeed
- unknown_tool_name: add !is_empty() guard before .all() to prevent
vacuous pass on empty iterator
- workspace tests: change `if let Some(ws)` to `.expect()` so assertions
are never silently skipped when workspace/trace_llm is available
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: add template substitution to TraceLlm for dynamic tool result forwarding
Add {{call_id.json_path}} template syntax to trace fixtures, enabling
tool results from one step to flow into subsequent steps' arguments.
TraceLlm extracts variables from Role::Tool messages (stripping the
safety layer's <tool_output> XML wrapper and unescaping entities) and
substitutes them in canned tool_call arguments before returning.
This fixes job_create_status and job_list_cancel tests to properly test
job_status and cancel_job with real dynamic UUIDs from create_job,
instead of using invalid canned IDs that silently failed.
Also adds tool result content assertions to job_create_status to verify
the actual tool output contains expected data (job_id, title).
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR review feedback on E2E tests
- undo_redo_cycle: assert exactly 3 turns instead of >= 2
- tool_error_feedback: use tempfile::tempdir() instead of hardcoded /tmp path,
patch fixture path at runtime for CI portability
- worker_timeout → iteration_limit: rename to accurately describe what's tested
- post_plan_work_remaining → simple_echo_flow: rename, test doesn't exercise planning
- identity_in_system_prompt: seed IDENTITY.md before test, assert system prompt
contains the seeded content instead of just checking Role::System exists
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: strengthen workspace E2E test assertions per PR review
- write_chunk_search: assert memory_search was called and returned
payment/architecture-related results
- multi_document_search: assert memory_search was called for
cross-document search
- hybrid_search_with_embeddings: assert both memory_write and
memory_search were called to confirm write-then-search pipeline
- directory_tree: assert tree output contains expected alpha/beta
project paths
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>