mirror of
https://github.com/nearai/ironclaw.git
synced 2026-09-02 23:56:24 +08:00
staging
1325 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
db261f83b6 |
Merge pull request #2604 from nearai/staging
Promote Staging to Main |
||
|
|
0f7314caaa | fix: bump registry versions for promotion check (#2605) | ||
|
|
eaa0d720e7 |
Merge pull request #2603 from nearai/main-to-staging-sync-2602
Sync main into staging |
||
|
|
5fc61f6c2b |
Merge remote-tracking branch 'origin/staging' into HEAD
# Conflicts: # CHANGELOG.md # Cargo.toml |
||
|
|
0dc0fb495d |
test: run MCP lifecycle trace in gateway mode (#2595)
* test: run MCP lifecycle trace in gateway mode * test: isolate MCP lifecycle oauth env |
||
|
|
2536835436 | Fix gateway auth/pairing flow handling (#2594) | ||
|
|
1c7a991060 |
fix(gateway): restore web login bootstrap (#2592)
* fix(gateway): restore web login bootstrap * fix(ci): address gateway syntax review feedback |
||
|
|
ab8d64cbfc |
feat: new-project skill and template ref resolution for parallel tool calls (#2353)
* feat(gateway): project metrics dashboard, mission scheduling UI, and new-project skill Adds project metrics types, mission cadence scheduling via gateway, and a /new-project skill for creating autonomous projects with goals, metrics, and missions. Includes gateway frontend enhancements for project views with metrics and goal tracking. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): resolve template refs in parallel tool calls and rewrite new-project skill Two fixes from trace analysis (trace_20260411T133641.json): 1. Skill rewrite: new-project skill now instructs the model to use memory_write + mission_create directly instead of referencing nonexistent project_create/project_update tools. Includes goals and metrics when appropriate. Instructs sequential execution. 2. Template ref resolution: some OpenAI-format models (e.g. Qwen) emit {{call_id.field}} references in parallel tool call arguments. Added resolution pass in LlmBridgeAdapter that scans ActionCall parameters for these patterns and resolves them from prior tool results in the conversation history. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add project detail page screenshot test Playwright test that seeds mock project data via page.route() API interception, navigates to the Projects tab, drills into a project, and captures a screenshot showing goals, missions, and activity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add project detail screenshot for PR Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review — remove project tools, fix IDOR, scope widgets, add tests - Remove project_create/project_update/project_list tools and capability registration (skill uses memory_write + mission_create only) - Add ownership check on mission_create project_id override to prevent IDOR - Reject non-UUID project_id values explicitly instead of silent fallback - Add goals field to ProjectOverviewEntry so frontend drill-in renders them - Propagate store errors in overview instead of unwrap_or_default masking failures - Scope project widget CSS server-side via scope_css (prevents style leakage) - Fix template ref doc comment to match partial resolution semantics - Fix E2E mock widget response shape (bare array, not wrapped object) - Call crBackToOverview() on tab switch to tear down project widgets - Add caller-level test for template ref resolution through LlmBridgeAdapter - Clean up stale cargo-deny advisory ignores, add RUSTSEC-2026-0097 (rand) - Run cargo fmt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve project slugs in mission_create, fix widget CSS comments - mission_create now accepts project name/slug (not just UUID) by matching against the user's projects — fixes the skill's slug-based project_id - Fix misleading CSS comment in app.js (CSS is scoped server-side) - Fix style variable hoisting issue in widget mounting - Log workspace.list() errors instead of silently swallowing them Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review round 3 — slug matching, template injection, N+1 queries - Remove over-broad `starts_with` slug prefix matching in mission_create project_id resolution — require exact name/slug match only (serrrfirat) - Fix slug generation inconsistency: frontend.rs now uses is_ascii_alphanumeric() matching effect_adapter.rs (serrrfirat) - Prevent second-order template injection: resolve_template_refs now advances past resolved content instead of re-scanning from position 0, and skips unresolvable refs instead of breaking (serrrfirat) - Parallelize N+1 overview queries: per-project thread/mission fetches now use tokio::try_join! + futures::try_join_all (serrrfirat, Copilot) - Add two new security tests for template ref resolution Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
fbb904116c |
fix(web): prevent user messages from vanishing on thread switch (#2409) (#2498)
* fix(web): prevent user messages from vanishing during safety-pipeline window (#2409) When loadHistory() re-renders the chat (thread switch, SSE reconnect, page reload), user messages that haven't been persisted yet disappear because the agent loop persists them after safety checks (100ms-1s delay). This fix tracks pending messages client-side and re-injects them into the DOM when loadHistory() doesn't find them in the DB yet. - Add _pendingUserMessages Map with 60s TTL - Record pending messages in sendMessage() before the fetch call - Clear pending entries when SSE events confirm agent processing - Re-inject non-persisted pending messages in loadHistory() fresh path - Suppress welcome card when pending messages exist Purely frontend fix — no backend changes, no safety pipeline bypass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add Playwright tests for pending message persistence (#2409) Six scenarios covering the frontend fix for disappearing user messages: - User message visible immediately after send (optimistic display) - Pending message survives SSE reconnect (re-injected by loadHistory) - Pending messages cleared after agent response (no stale entries) - No duplicates when DB already has the message - Welcome card suppressed when pending messages exist - Full round-trip message survives page reload (DB persistence) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(e2e): use domcontentloaded for reload test — SSE blocks networkidle Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(web): address review — remove SSE early-clear race, use frequency map for pending dedup (#2498) Remove _pendingUserMessages.delete() from response/tool_started/stream_chunk SSE handlers to prevent race condition when user sends multiple messages in quick succession. Replace Set-based dedup in loadHistory with a frequency map so duplicate-content messages ("ok", "ok") are tracked correctly. Simplify welcome-card guard using hoisted freshPending. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(web): clear pending entry on turn completion — address henrypark133 review (#2498) * fix(web): address review — remove pending on send fail, Map for dedup, improve reconnect test (#2498) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove unused imports in pending message test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: retrigger checks against updated staging base * fix(web): preserve images in pending messages, harden tests (#2498) Address remaining review feedback: - Capture attached image data URLs in optimistic display and in the _pendingUserMessages entry so a thread switch / SSE reconnect re-injects thumbnails alongside the text instead of just an "(images attached)" placeholder. - Rewrite the SSE-reconnect test to drive the real production path: stub apiFetch so /api/chat/send hangs, send via the real UI, force a reconnect, and assert the message survives — instead of manually pre-populating the pending map. - Add coverage for the .catch() cleanup branch in sendMessage so a rejected /api/chat/send leaves _pendingUserMessages clean. - Add a FIFO-assumption comment on the response-handler shift() and drop the leading underscore on the function-local `pending` (the underscore convention in this file is for module-level state). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com> |
||
|
|
22cd378461 |
fix(safety): add inbound secret scanning to engine v2 path (#2494)
* fix(safety): add inbound secret scanning to engine v2 path (#2491) The v2 engine path (`handle_with_engine_inner` in `bridge/router.rs`) forwarded user messages directly to the conversation manager without any safety checks. This allowed secrets (API keys, Slack tokens, AWS credentials, etc.) pasted in chat to reach the LLM and be permanently stored in conversation history. Add the same three safety checks that the v1 path (`thread_ops.rs`) already enforces: `validate_input`, `check_policy`, and `scan_inbound_for_secrets`. Messages containing detected secrets are now rejected with a user-facing warning before reaching the engine. Includes a regression test exercising Slack bot tokens and OpenAI keys through the v2 code path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style(safety): fix rustfmt formatting in secret scan test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(safety): fix OpenAI key test — payload too short for regex (#2494) The mock OpenAI key `sk-abc123def456ghi789` had only 19 chars after the prefix, but the leak detector regex requires 20+. Extended the key and added a specific assertion matching the Slack token check. Addresses gemini-code-assist review feedback. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(deps): ignore RUSTSEC-2026-0099 webpki advisory Wildcard name constraint bypass in rustls-webpki 0.102.8, pinned by the libsql transitive dependency chain. Same root cause as the already-ignored RUSTSEC-2026-0049. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: minor comment tweak to retrigger CI Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): resolve clippy and fmt errors Remove useless .into_iter() in catalog.rs and fix rustfmt style in e2e_attachments.rs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(bridge): use BridgeOutcome instead of Option<String> in safety checks The inbound safety scanning code was written against the old Option<String> return type, but handle_with_engine_inner now returns BridgeOutcome. Replace Ok(Some(...)) with Ok(BridgeOutcome::Respond(...)) and update tests to match on the enum variants. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com> |
||
|
|
e65ba2e4d9 |
fix(engine): security hardening for v2 orchestrator and Monty sandbox (#1958)
* fix(engine): security hardening for v2 orchestrator and Monty sandbox Address deferred security items C1/C2/C4/M2 from PR #1557 review: C1/C2 — Orchestrator self-modification approval gates: - memory_write tool now returns ApprovalRequirement::Always for protected orchestrator paths when ORCHESTRATOR_SELF_MODIFY=true, forcing human approval before any orchestrator or prompt overlay patch is written - Store adapter validates Python syntax via Monty parser before persisting orchestrator patches, preventing broken code from consuming failure budget - Content hash (SHA-256) stamped on all protected docs for audit trail C4 — Sandbox security test coverage (6 new tests): - sandbox_enforces_rlm_query_depth_limit: depth check at max recursion - sandbox_rejects_final_injection: FINAL() captures payload literally - sandbox_rejects_tool_name_injection: dynamic names can't bypass leases - sandbox_context_variable_is_not_mutable: Python mutations don't affect Rust - sandbox_handles_deep_recursion: infinite recursion terminates safely - validate_syntax_rejects_broken_code: syntax validation unit test M2 — Remove ownership-bypassing thread operations: - Delete stop_thread_system() and inject_message_system() — dead code with no callers, was a privilege escalation footgun. Ownership-checking variants (stop_thread/inject_message) are the only API now. Also: document child lease budget snapshot semantics (intentional design), add EngineError::InvalidInput variant, re-export validate_python_syntax. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: cargo fmt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: collapse nested if per clippy suggestion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: cargo fmt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): address PR #1958 review findings Review feedback from Copilot and serrrfirat: 1. Use ORCHESTRATOR_FAILURES_TITLE constant consistently in both branches of the self-modify gate (was string literal in deny branch). 2. Normalize metadata to {} before stamping content_hash, so the audit trail is reliably present even for docs with null/non-object metadata. 3. Add 256KB size cap to validate_python_syntax() to prevent pathological inputs from causing CPU/memory pressure on the store write path. 4. Tighten sandbox_rejects_tool_name_injection test assertion to verify the expected "not_found" outcome, not just absence of "ESCAPED". 5. Change ApprovalRequirement::Always → UnlessAutoApproved for protected orchestrator writes. The v2 effect bridge maps Always to hard denial (LeaseDenied), making the self-modify path unusable. UnlessAutoApproved triggers the gate/pause flow so human approval is possible. 6. Extend is_protected_orchestrator_path() to cover physical workspace paths (engine/orchestrator/*) in addition to logical aliases. Prevents bypassing the approval gate by writing to the persisted file path. 7. Persist project_id and user_id in frontmatter (serialize_knowledge_doc) and restore them on load (deserialize_knowledge_doc). Previously all reloaded docs got project_id=nil and user_id="legacy", making them invisible to project-scoped queries after restart. 8. Synthesize MemoryDocs from raw .py orchestrator files on startup (synthesize_orchestrator_doc_from_py). Orchestrator versions are persisted as engine/orchestrator/v{N}.py but load_knowledge_docs could not parse them — they silently disappeared on restart. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): PR #1958 round-2 review — close path-bypass, forgeable-metadata, and rehydration holes Address the critical/high findings from @serrrfirat's second review on PR #1958. Every fix is paired with a caller-level regression test per the "Test Through the Caller" rule in .claude/rules/testing.md. ## Critical — path normalization bypass `is_protected_orchestrator_path` and `is_orchestrator_code_path` both did raw `starts_with` on user input. Paths like `engine/./orchestrator/v3.py`, `engine//orchestrator/v3.py`, and `engine/knowledge/../orchestrator/v3.py` resolved to the protected location but sidestepped the check, letting LLM-authored writes skip the approval gate *and* syntax validation. Added `normalize_workspace_path` (in tools/builtin/memory.rs) and `normalize_path` (in bridge/store_adapter.rs): strip `.` and empty segments, reject `..` traversal outright. Both guards now normalize first, then match against both `.system/engine/orchestrator/` and the legacy `engine/orchestrator/` prefix. Traversal attempts are treated as protected so they route through the approval gate — and `execute()` rejects them with `InvalidParameters` before reaching the workspace. ## High — forgeable `is_system_internal` metadata The gate previously trusted `doc.metadata.source == "compiled_in"` to mark a write as system-internal. An LLM tool call could craft a doc with that field and bypass both denial and validation. Replaced with a `tokio::task_local!`-backed trusted-write scope in the new `crates/ironclaw_engine/src/runtime/internal_write.rs` module: runtime::with_trusted_internal_writes(async { store.save_memory_doc(&seed).await }) The orchestrator v0 seeder in `MissionManager::seed_orchestrator_v0` enters the scope; the store gate reads `is_trusted_internal_write_active()`. Task-locals cannot be set from outside a trusted callsite and do not propagate across `tokio::spawn`, so an untrusted caller cannot inherit the flag. A unit test asserts the no-propagation property. ## High — rehydration rendered orchestrator invisible after restart `synthesize_orchestrator_doc_from_py` returned a doc with `ProjectId::nil()`, but `list_memory_docs` filters by exact project, so persisted `.py` orchestrator files disappeared from project-scoped queries after a process restart and the runtime silently reverted to compiled-in defaults. Override `HybridStore::list_shared_memory_docs` to surface docs flagged as "physically global" (by title — orchestrator, failure tracker, prompt overlay) for any project query, regardless of the stored project_id. Physical storage is one file per workspace; the override reflects that. New end-to-end round-trip test writes an orchestrator via the store, rebuilds a fresh HybridStore from the same workspace, and asserts the rehydrated doc is visible to both the original project and an unrelated project. ## High — env var read on every gate check Both `memory_write::requires_approval` and `save_memory_doc` read `ORCHESTRATOR_SELF_MODIFY` from the environment on every call. Env vars are global mutable state; a future sandbox escape could flip a security gate mid-flight. Centralized in `runtime::self_modify_enabled()`: a process-wide `OnceLock<bool>` seeded on first read. Tool, store, engine loop, and self-improvement mission all share the same snapshot. Tests need to flip the flag, so the module also exposes a `SelfModifyTestGuard` that overrides the snapshot and serializes concurrent tests via a process-wide `Mutex`. The override layer is compiled out of release builds (`cfg(debug_assertions)`). ## High — PR description / code mismatch + hard denial regression Original PR said `ApprovalRequirement::Always`, code used `UnlessAutoApproved`. The `Always` path is mapped by the v2 effect bridge to `LeaseDenied` (permanent refusal), not to a resumable approval gate. Fixed the code to `UnlessAutoApproved` (already in the previous round), but added an end-to-end regression test in `effect_adapter.rs` that drives `EffectBridgeAdapter::execute_action` with the real `MemoryWriteTool` and asserts the protected target produces `GatePaused(Approval)` — not `LeaseDenied`. Sibling test asserts that with self-modify disabled, the write surfaces as a non-resumable refusal so the agent doesn't loop on an unreachable approval. ## Medium — audit-hash scope The content hash stamped on protected docs is **write-time audit only**: the workspace file is the trust boundary, and anyone with workspace access can edit the raw file bypassing save_memory_doc. Documented this explicitly in `save_memory_doc` so future readers don't mistake it for a runtime integrity check. Also cleaned up the clippy `map_for_value` nit by switching to `if let Some(map) = ...`. ## Test coverage (all caller-level, new) Unit tests (41 new): - `memory.rs` path normalization & protected-path guard (dot-segment bypass, double-slash bypass, traversal, legacy path, canonical path, logical alias, unrelated path) — 9 cases - `memory.rs` requires_approval branches (enabled + protected, disabled + protected, physical path, dot bypass, traversal bypass, unprotected, missing target) — 7 cases - `store_adapter.rs` normalize_path, is_orchestrator_code_path, synthesize_orchestrator_doc_from_py, validate_orchestrator_content, is_protected_orchestrator_doc, is_globally_shared — 23 cases - `internal_write.rs` trusted-write scope semantics — 3 cases - `list_shared_memory_docs` override surfaces global vs project-scoped docs correctly — 2 cases Integration tests (11 new, libsql feature): - `dispatch.rs::integration_tests` — drives `ToolDispatcher::dispatch()` against the real `MemoryWriteTool` for all bypass paths (protected alias, physical path, dot segment, double slash, traversal, unprotected baseline) — 6 cases - `store_adapter.rs::migration_tests::orchestrator_py_round_trips_through_restart` — full write → restart → load → cross-project query cycle - `store_adapter.rs::migration_tests::knowledge_md_doc_round_trips_project_id_and_user_id` — asserts frontmatter `project_id`/`user_id` survive restart (was previously dropped, making docs invisible to project queries) - `store_adapter.rs::migration_tests::invalid_python_orchestrator_is_rejected_at_write_time` — validator gate fires before persistence - `effect_adapter.rs::tests::memory_write_orchestrator_target_paused_for_approval_when_self_modify_enabled` — the UnlessAutoApproved regression test - `effect_adapter.rs::tests::memory_write_orchestrator_target_refused_when_self_modify_disabled` — asserts no gate pauses when self-modify is off ## Quality gate - `cargo fmt` — clean - `cargo clippy -p ironclaw --lib --tests --features libsql` — 0 warnings - `cargo clippy -p ironclaw_engine --all-targets` — 0 warnings (crate-local) - `cargo test -p ironclaw_engine` — 358/358 pass - `cargo test -p ironclaw --lib --features libsql` — 4735/4735 pass - `cargo test --test engine_v2_gate_integration --test engine_v2_skill_codeact` — 27/27 pass Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): round-3 review — syntax validation in memory_write, Always-approval gate, global doc visibility Critical: MemoryWriteTool::execute() now validates Python syntax for protected .py paths before writing to workspace (was bypassing the Store-level validator entirely). High: ApprovalRequirement::Always now produces GatePaused(Approval) with allow_always=false instead of hard LeaseDenied; memory_write returns Always (not UnlessAutoApproved) for protected paths so session auto-approve cannot silently skip the gate. High: Global docs (orchestrator, failures, prompt overlay) have project_id normalized to nil on save so they surface immediately from any project query, not just after restart. Medium: Traversal paths no longer trigger spurious approval gates — requires_approval returns Never for normalization failures so execute() rejects them immediately as InvalidParameters. Medium: Dead code removed from is_orchestrator_code_path (equality checks unreachable after .py suffix requirement). Medium: Document prompt overlay validation skip and syntax validation threat model; expanded validate_python_syntax test coverage (size cap, empty input, unicode, error format). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): update deny.toml — remove stale wasmtime advisories, add rand 0.8.5 Wasmtime was upgraded and no longer triggers RUSTSEC-2025-0046, RUSTSEC-2025-0118, RUSTSEC-2026-0020, RUSTSEC-2026-0021. The stale ignores caused cargo-deny to fail with "advisory was not encountered". Added RUSTSEC-2026-0097 (rand 0.8.5 unsound aliased mutable ref in ThreadRng during reseed from custom logger) — transitive dep via monty/wasmtime; upgrade to rand 0.9+ tracked separately. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): path-boundary check in requires_approval, fix stale docstring Address two Copilot review comments: - requires_approval used raw starts_with on normalized path, matching unrelated paths like orchestrator_backup/. Added path-boundary checks. - Updated stale docstring on the Always-approval gate test to reflect current behavior (Always→GatePaused, not UnlessAutoApproved). [skip-regression-check] Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): deduplicate requires_approval gate, move syntax validation after param checks - requires_approval now delegates to is_protected_orchestrator_path instead of duplicating the matching logic (reviewer concern about drift between the two checks) - Syntax validation for protected .py paths moved after patch-mode parameter validation (empty old_string, missing new_string) so an empty-string replace can't create a huge intermediate string before being rejected [skip-regression-check] Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): address PR #1958 round-4 review findings 5 issues from serrrfirat review (2026-04-13): 1. High — clamp `always` in `resolve_gate` against `pending.resume_kind` so a caller-supplied `always: true` can no longer install a session- wide auto-approval on an `Approval { allow_always: false }` gate (orchestrator self-modify writes). Extracted to `clamp_always_to_ resume_kind` with unit tests covering all three ResumeKind variants. 2. Medium — expand `validate_python_syntax` rustdoc to document that `MontyRun::new()` parses and prepares only (no heap/namespaces, no module-level execution) and explain the 256 KB size cap as a bound on parser allocation, not an execution-time safeguard. 3. Medium — remove the `doc.title == ORCHESTRATOR_FAILURES_TITLE` shortcut from the store-adapter self-modify gate. The two legitimate callers (`record_orchestrator_failure`, `reset_orchestrator_failures`) now wrap their `save_memory_doc` in `with_trusted_internal_writes`. Additionally reject untrusted writes to the failures title regardless of self-modify state, since no LLM-reachable code path should ever persist the system-internal tracker. 4. Medium — add a parity test asserting `normalize_path` (store adapter) and `normalize_workspace_path` (memory tool) agree on a canonical input set. Shared extraction isn't clean across the bridge/tool boundary; the test is the lighter guard against drift on this security boundary. 5. Medium — tighten `MemoryWriteTool::requires_approval` commentary with an explicit cross-reference to the `execute()` rejection site (~line 444) and a load-bearing invariant warning so a future refactor that weakens `execute()` will also be forced to flip this branch to `Always`. Also collapse a clippy::collapsible_if that appeared in the traversal gate check. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
79ad2e38ae |
fix(gateway): align historical/live tool call cards and preserve tool call correlation (#2182)
* Align history tool cards with live activity cards * Make tool cards keyboard accessible * Preserve tool call IDs in web event handling * fix: chevron icon size * Guard response call IDs against unknown tool outputs --------- Co-authored-by: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> |
||
|
|
27d53f5153 |
docs(skills): code-review v2 + GitHub endpoint fixes + minor text updates (#2528)
* feat(skills): paranoid-architect code-review skill v2 Rewrite the code-review skill from a 6-bullet checklist into a paranoid-architect workflow that handles both local diffs and GitHub PRs end-to-end: - Two input shapes: local `git diff` or `owner/repo N` / `github.com/.../pull/N` URLs. - Step 1 wraps GitHub fetches in `async def` + `FINAL(await ...)` to avoid the closure-capture quirk that kept tripping LLMs (see the paired codeact preamble update); reads metadata, diff, and files via three sequential awaits instead of `asyncio.gather`. - Step 2 reads each changed file in full (raw media type, no base64 module needed) so reviews account for surrounding context. - Step 3 runs the change through six lenses: correctness, edge cases, security (with a real adversarial checklist), test coverage, docs, architecture. - Step 4 renders findings as a severity table and asks which to post. - Step 5 posts line-level comments via the PR comments endpoint with the captured head SHA, falling back to issue comments for multi-file findings. Bumps `requires.skills` to include `github` so the activation pulls in the GitHub API recipes via the chain-loader. Adds a live e2e test (`e2e_live_code_review.rs`) plus a recorded trace fixture (PR #2483) so the workflow is replayable without hitting GitHub. * docs(github): clarify search endpoints, response envelope, @me queries LLMs kept inventing a `search_issues` action and looping over `/repos/{owner}/{repo}/pulls` for "my PRs" queries. Clarify the GitHub tool surface in three places: - `tools-src/github/src/lib.rs` and `registry/tools/github.json`: enumerate the three real search actions and call out that `search_issues_pull_requests` covers both. Add the canonical `is:pr author:@me sort:updated-desc` recipe for cross-repo "my PRs". - `skills/github/SKILL.md`: add an "Authenticated User & Cross-Repo Queries" section with copy-paste recipes for `@me`, the search endpoints with proper URL encoding, and the response-envelope contract (`body` is parsed JSON for application/json, raw `str` for diff endpoints — never call `json.loads()` on it, never write `.get("body", body)` as a fallback). * fix: resolve CI failures — clippy useless_conversion + missing test harness methods - Remove `.into_iter()` on `details` in catalog.rs (clippy::useless_conversion) - Add `with_skills_dir` to `LiveTestHarnessBuilder` for e2e_live_code_review test - Add `active_skill_names` to `TestRig` extracting from SkillActivated status events Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(skills): address zmanian + gemini review — URL encoding, multi-line comments, description trimming (#2528) - URL-encode file paths in GitHub API content URLs - Add start_line/start_side to multi-line comment example - Add 'locally' keyword override for mode detection - Trim overly long schema descriptions - Remove duplicated /search/issues note from Common Mistakes - Fetch PR title from trace fixture instead of hard-coding Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(test): propagate skills_dir into TestRig config (#2528) LiveTestHarnessBuilder::with_skills_dir() stored a PathBuf but only used it as an is_some() flag — the actual SkillRegistry always pointed at an empty temp directory. Now the stored path flows through TestRigBuilder::with_skills_dir() into config.skills.local_dir and the SkillRegistry constructor. Also generalizes the hardcoded nearai/ironclaw repo name in the github skill's response-handling example to {owner}/{repo}. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
a6443dd450 |
fix: resolve staging CI test failures blocking promotion (#2574)
* fix: resolve 3 categories of staging CI test failures 1. pending_gate_extension_name now extracts extension name from tool_install/tool_activate/tool_auth parameters even when auth_manager is unavailable, matching the AuthManager logic and returning "telegram" instead of "telegram_bot_token". 2. Updated CLI help snapshots to match new onboard/config/doctor/login descriptions and the addition of the profile subcommand. 3. Relaxed E2E pairing approve assertions to check only the code field, accommodating the new optional thread_id the frontend now sends. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(web): address ilblackdragon review — ensure auth_manager always available, remove fallback duplication (#2574) - Remove inline fallback that duplicated AuthManager::resolve_extension_name_for_auth_flow() logic in pending_gate_extension_name(); auth_manager is now always wired in tests via a minimal InMemorySecretsStore-backed AuthManager - Fix trim inconsistency in AuthManager::resolve_extension_name_for_auth_flow() where the predicate trimmed whitespace but the return value did not Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: allow clippy::too_many_arguments on register_startup_channels Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
2874d2e98f |
docs: MCP server configuration guide (#1138)
* docs: add MCP server configuration guide Covers the three transport types (HTTP, stdio, Unix), OAuth 2.1 authentication, environment variables for stdio servers, custom headers, the mcp-servers.json config format, example servers, and troubleshooting. Written against the current implementation in src/tools/mcp/ and src/cli/mcp.rs. * fix: docs * chore: better explain toggle --------- Co-authored-by: Guille <gagdiez.c@gmail.com> Co-authored-by: Guillermo Alejandro Gallardo Diez <gagdiez@iR2.local> |
||
|
|
89b350ec56 |
feat(engine): execution obligation -- require tool attempt on explicit user commands (#2539)
* feat(engine): execution obligation for v2 — require tool attempt on explicit user commands
When a user explicitly asks the engine to execute something ("run the
tests", "fetch the data", "please check the logs"), the v2 engine now
requires the model to attempt at least one tool/action call before
accepting a plain-text response.
Adds `user_signals_execution_intent()` heuristic in reasoning.rs that
detects imperative execution phrases. The router sets
`require_action_attempt = true` on ThreadConfig when detected. The
Python orchestrator enforces this by nudging the model if it responds
with text-only without attempting any action.
The obligation resolves when the model enters a code/action path
(before execution), preventing retry loops on approval gates. The
obligation nudge and tool-intent nudge are mutually exclusive to
avoid double-nudging.
Closes #2447
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback on execution obligation
- Fix nudge interaction bug: move obligation check before
consecutive_nudges reset so tool-intent nudge exhaustion
can't trick the mutual exclusion guard
- Add available-actions guard: obligation only fires when
__get_actions__() returns tools, preventing useless nudges
when no tools are loaded
- Remove "check the " from heuristic: too broad for personal
assistant context ("check the calendar" is a query, not
an execution command)
- Add exhaustion e2e test: model refuses 3 times, hits
max_action_requirement_nudges, text accepted as final
(proves the feature terminates)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: cargo fmt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: remove useless .into_iter() to satisfy clippy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(engine): enforce execution obligation on follow-up messages
The obligation nudge only fired when spawning a new thread (where
ThreadConfig.require_action_attempt was set). Follow-up messages
injected into a running thread or resuming a suspended thread used
the original thread config, so "run the tests" in turn 2+ was
silently ignored.
Fix: detect execution intent per-message in the Python orchestrator
rather than only from thread config. Two paths covered:
- inject (running thread): check injected message text for intent
keywords, enable obligation and reset state if detected
- resume (suspended thread): check the last user message in the
initial context on run_loop startup
Adds signals_execution_intent() to default.py (ported from Rust
user_signals_execution_intent), plus a multi-turn e2e test that
verifies the inject path: turn 1 is conversational (no obligation),
turn 2 says "run the echo tool" and the nudge fires.
Closes review feedback from henrypark133 on #2539.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: fix doc comment placement on strip_code_blocks
The doc comment for strip_code_blocks was incorrectly placed above
user_signals_execution_intent. Moved it to its own function and
cleaned up the user_signals_execution_intent doc.
Addresses gemini review feedback on #2539.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(engine): reset obligation state on resume + add gate resume test
The context-based obligation check at run_loop startup did not reset
_obligation_resolved and _obligation_nudge_count from persisted state.
On resume, a stale "resolved" flag from a prior run would silently
suppress the new obligation. Fixed by resetting both state flags when
execution intent is detected from context.
Also: the multi-turn e2e test (followup_inject) was mislabeled -- the
test rig processes messages sequentially so turn 2 always spawns a new
thread (the already-working spawn path). Renamed to reflect what it
actually tests.
Added a proper gate-based resume test in engine_v2_gate_integration:
1. Thread spawns with no execution intent in goal
2. Tool call hits a gate, thread enters Waiting
3. Resume with "run the echo tool" (execution intent)
4. Obligation nudge fires, echo tool called
This tests the real resume path through ThreadManager.resume_thread
where ThreadConfig.require_action_attempt was never set.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: cargo fmt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
030c09a68e |
fix(routines) - make cadence mandatory and expose guardrails (#2547)
Agents could not create reactive missions (e.g. "log every telegram message") because of three shortcomings: 1. Cadence was optional and defaulted to "manual" silently — malformed values like "every 5 min" were also swallowed and became manual triggers 2. Reactive missions (event/webhook) had a hardcoded 300s cooldown with no way to override it via mission_create 3. mission_update and mission_delete existed in code but were not documented in the CodeAct preamble or the Tier 0 ActionDef schemas, so the LLM never called them Changes: - parse_cadence now returns Result and rejects unrecognized strings with a readable error so the LLM can correct the call - mission_create requires cadence (returns error if missing instead of defaulting to manual) - mission_create and mission_update accept guardrail params (cooldown_secs, max_concurrent, dedup_window_secs, max_threads_per_day) as top-level fields - mission_list output now includes cadence and guardrails so misconfigured missions are visible - mission_update and mission_delete added to CodeAct preamble - Tier 0 ActionDef schemas updated with correct cadence formats and guardrails - Regression tests for malformed cadence rejection Changes on review * chore: cargo fmt * fix: enforce numeric params to be u64 * fix: address reviewer comments * chore: divide long strings of comments * fix: extract_fuardrails overrided existing mission update * fix: allow event:*:pattern as cadence * fix: check before storing events * chore: fix docs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: add limit to regexbuilder * chore: homogenize tool description * fix: add tests * fix: implement requested fixes --------- Co-authored-by: Guillermo Alejandro Gallardo Diez <gagdiez@iR2.local> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> |
||
|
|
1a33cfa2af |
feat(cli/logs): add --grep support for filtering log output (#1533)
* feat(cli/logs): add --grep support for filtering log output - Add --grep/-g flag for regex filtering (conflicts with --follow) - Add --ignore-case for case-insensitive matching - Add --context N to show lines around matches - Implement filter_lines() with regex crate - Add 6 comprehensive tests - Update documentation with examples Branching from staging as suggested in #371. * refactor(cli/logs): simplify regex compilation per bot feedback - Use RegexBuilder::new().case_insensitive(ignore_case) instead of if/else - Fix test assertion to avoid move issue (single unwrap_err() call) - Apply cargo fmt formatting * fix(cli/logs): address maintainer feedback - Use saturating_add to prevent integer overflow in context calculation - Add test_filter_lines_with_non_overlapping_context to verify BTreeSet dedup logic Addresses review comments on PR #1533. |
||
|
|
729449fa12 | ci: build ironclaw docker image hourly (#2519) | ||
|
|
4308628301 |
Refine v2 web activity shell (#2560)
* Refine v2 web activity shell * Polish v2 web activity shell |
||
|
|
fe5cdceeb2 |
fix: owner_id was stored as string when recovered from settings on restart (#2561)
* fix: owner_id was stored as string when recovered from settings on restart Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add regression test for owner_id type on restart Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: use existing function --------- Co-authored-by: Guillermo Alejandro Gallardo Diez <gagdiez@iR2.local> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
2474f65951 | Fix WASM channel startup restore semantics (#2563) | ||
|
|
3ac8e5f7e8 |
Unify gateway onboarding, auth gates, and pairing flows (#2515)
* fix(channels): wire up pairing approval, polling restart, and onboarding state The Telegram channel setup flow via the gateway was broken end-to-end. Four interconnected bugs prevented pairing/ownership from completing: 1. pairing_approve_handler only wrote to channel_identities DB — the running WasmChannel's owner_actor_id was never updated, so the owner was never recognized and broadcast metadata was never stored. 2. refresh_active_channel() re-ran on_start() but never called ensure_polling(), leaving polling in a stale state on repeated tool_activate calls and causing Telegram 409 conflicts. 3. activate_wasm_channel() had a TOCTOU race on active_channel_names that allowed duplicate polling loops, and hot_add() didn't await old polling task termination. 4. onboarding_state was always None in extension API responses and PairingRequired SSE was never emitted, so the frontend could never render the pairing card. Changes: - approve_pairing (DB trait + both backends) now returns external_id - WasmChannel.owner_actor_id wrapped in RwLock with set_owner_actor_id() - ExtensionManager.complete_pairing_approval() orchestrates: persist owner_id → update running channel → restart polling - pairing_approve_handler calls complete_pairing_approval and emits PairingCompleted SSE (scoped to approving user) - refresh_active_channel() calls ensure_polling() and syncs owner - Per-channel activation mutex prevents TOCTOU race - hot_add() drops write lock before awaiting shutdown - Extension list handlers populate onboarding_state when Pairing - derive_onboarding() helper in handlers/extensions.rs - Regression tests for derive_onboarding and resolve_message_scope Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(bridge): eliminate dual card + text emission for gate-paused flows When the v2 engine hits a gate-paused state (approval needed, auth required), the web gateway was sending BOTH an interactive card (via send_status → SSE) AND a redundant text message (via AppEvent::Response). Users saw a duplicate prompt. Root cause: v2 bridge functions returned Ok(Some(text)) for gate-paused outcomes, which mapped via from_legacy to HandleOutcome::Respond — sending both the card and the text. The v1 path correctly used HandleOutcome::Pending. Fix: - Gate-paused paths in router.rs now return Ok(None) instead of text - New bridge_to_outcome() checks has_any_pending_gate() after each v2 bridge call — if a gate exists, returns Pending (suppresses text + Done) - New from_bridge() maps None → NoResponse (not Shutdown) for v2 paths - Removed pending_gate_prompt_message() — the function that generated the duplicate text - notify_pending_gate() no longer emits GateRequired SSE directly (redundant with send_pending_gate_status per-channel routing) - Updated 3 tests to assert None return + StatusUpdate delivery Each channel renders the approval/auth card natively via send_status: web → SSE card, TUI → widget, relay → buttons. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review comments - bridge_to_outcome: only return Pending when handler returned None (preserves legitimate text responses for ambiguous gate messages) - process_emitted_messages: clone owner_actor_id out of read lock before awaiting resolve_message_scope_with_pairing - Normalize channel_name to lowercase in complete_pairing_approval and pairing_approve_handler for consistent webhook/store lookups - cargo fmt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address self-review — BridgeOutcome enum, ExternalId newtype, pairing extraction - Replace Option<String> bridge handler returns with typed BridgeOutcome enum (Respond/NoResponse/Pending), eliminating post-hoc has_any_pending_gate query and the None→NoResponse mapping that swallowed v2 shutdown signals - Add ExternalId newtype for approve_pairing return (was bare String) - Fix noop PairingStore::approve to return NotFound instead of Ok("") - Extract pairing approval orchestration to src/pairing/approval.rs - Clone RwLock<owner_actor_id> before awaiting in respond() - Downgrade warn! to debug! in pairing handlers (TUI logging rule) - Gate TELEGRAM_TEST_API_BASE_ENV const behind cfg(test/debug_assertions) - Remove hardcoded Telegram auth instructions; use capabilities prompt - Fix unused mut receiver in test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(channels): remove dead Telegram verification flow, consolidate to generic pairing The Telegram-specific verification challenge (/start CODE deep link flow) blocked the generic pairing flow from ever running — configure() returned early with activated:false when the challenge was pending, so the channel never started polling and users couldn't generate pairing codes. Removed ~1200 lines: - TelegramBindingResult, TelegramBindingData, TelegramOwnerBindingState, TelegramVerificationMeta, PendingTelegramVerificationChallenge types - configure_telegram_binding, resolve_telegram_binding, issue_telegram_verification_challenge, notify_telegram_owner_verified and all Telegram API response types (getUpdates polling loop, etc.) - ConfigureResult.verification field + VerificationChallenge re-export - All verification-related test fixtures and 6 test functions - Dead RecordingChannel test helper, unused set_channel_owner_id method - Gated send_telegram_text_message + helpers behind cfg(test) Replaced with: - validate_telegram_token() — lightweight getMe call for token validation + bot_username extraction (persisted for mention detection) - All channels now follow: credentials → validate → activate → pairing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(channels): broadcast PairingRequired SSE after activation in pairing mode After a channel activates with no owner binding, broadcast a per-user PairingRequired SSE event so the web UI shows the pairing card without requiring a manual refresh. Also populate pairing_required, onboarding_state, and onboarding fields on ConfigureResult so callers know the channel needs pairing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(agent): don't persist auth instructions as turn response When a tool triggers an auth gate (awaiting_token), the dispatcher already sends an AuthRequired card and puts the thread in auth mode. The thread_ops handler was then calling complete_turn(&instructions) which overwrote auth mode back to Idle AND persisted the auth prompt ("Enter your Telegram Bot API token...") as the turn response — rendering a redundant text bubble alongside the auth card. Fix: skip complete_turn and persist_assistant_response for AuthPending. The turn is paused (not complete), and the auth card is the only user-facing signal. Tool calls are still persisted for history. Also removes the now-unused `instructions` field from AgenticLoopResult::AuthPending — the instructions were already sent via the AuthRequired status event before AuthPending is returned. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(channels): resume agent turn after auth + pairing completion After the web UI submits a token via /api/chat/auth-token or approves pairing via /api/pairing/{channel}/approve, the agent's turn was stuck at Pending forever — these HTTP handlers configured the extension directly but never signaled the agent loop to resume. Fix: inject a follow-up message through msg_tx (the agent's message channel) after successful auth/pairing. This uses the same pattern as the OAuth callback handler — the LLM picks up the injected message, sees the activation/pairing result, and produces a natural response. The response goes through the full agent pipeline (hooks, safety, history persistence, Done event). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(channels): also resume agent turn on auth cancel When the user dismisses the auth card, the frontend calls /api/chat/auth-cancel which clears auth mode. But the original agent turn was still paused at Pending with no Done event. The UI stayed stuck at "Processing..." forever. Fix: inject a cancellation message through msg_tx so the LLM can acknowledge the cancellation and the turn completes naturally. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(channels): pass thread_id in pairing approve for proper routing The injected follow-up message after pairing approval had no thread_id, causing the gateway to fail with "missing a routing target." The response from the LLM was produced but couldn't be delivered. Fix: add optional thread_id to PairingApproveRequest. The frontend passes currentThreadId so the agent responds in the same conversation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add Playwright tests for channel pairing flow Covers: - Auth-token/cancel handlers don't 500 - Pairing approve accepts optional thread_id field - Backward compatibility: approve without thread_id works - PairingRequired SSE shows pairing card - PairingCompleted SSE dismisses pairing card - Frontend sends currentThreadId in pairing approve request body Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(agent): transition thread to Idle on AuthPending The AuthPending handler was not calling complete_turn() (to avoid persisting redundant auth instructions as the response), but this also skipped the ThreadState::Processing → Idle transition. The thread stayed stuck in Processing forever, so the follow-up message injected through msg_tx after auth/pairing was silently rejected. Fix: explicitly set thread.state = Idle in both AuthPending arms without calling complete_turn(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(e2e): remove dead verification challenge branch from telegram e2e The Telegram verification challenge flow was removed — channels now go straight to activation and use the generic pairing flow. The conditional verification retry in setup_telegram() was dead code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review comments - Gate TELEGRAM_TEST_API_BASE_ENV and telegram_api_base_url() behind cfg(any(test, debug_assertions)) to prevent production env var override (serrrfirat HIGH — ship blocker) - Sanitize validate_telegram_token() error messages to avoid leaking bot tokens via reqwest Display (Copilot) - Log failed msg_tx sends instead of silently dropping (ilblackdragon) - Forward thread_id in PairingCompleted SSE event (Copilot) - Fix stale doc comment on persist_numeric_owner_id (Copilot) - Hoist duplicate parse::<i64>() in propagate_approval (ilblackdragon) - Delete dead _removed_telegram_verification_test (ilblackdragon) - Fix always-passing E2E thread_id assertion (Copilot) - Add V24 migration checksum to checksums.lock Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update PairingStore::approve doc for noop mode The doc said "silently succeeds" but the implementation returns NotFound when no database is configured. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: sanitize extension names in agent prompts + live owner_actor_id in spawned tasks Two hardening fixes from PR review deferrals: 1. Extension names from HTTP request bodies were interpolated directly into format strings that become IncomingMessage content fed to the agent loop. Add sanitize_extension_name() that strips non-alphanumeric chars and apply it at the two prompt injection points in chat_auth_token_handler and chat_auth_cancel_handler. 2. start_polling() and start_websocket_runtime() captured owner_actor_id as an owned Option<String> at spawn time. After pairing approval, WebSocket channels kept using the stale pre-approval value. Change to pass Arc<RwLock<Option<String>>> so spawned tasks read the current owner on each tick/event. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review findings — TurnOutcome refactor, security hardening, WS parity Structural changes: - Replace Thread::complete_turn/fail_turn/interrupt with single conclude_turn(TurnOutcome) that makes it impossible to forget the turn state. Fixes AuthPending arms leaving Turn stuck at Processing. - Add TurnOutcome::CompletedSilently for auth-card-only turns. Security: - Sanitize channel name in pairing_approve_handler (missed injection site) - Fix bot token leak in validate_telegram_token — log safe fields (is_timeout, is_connect, status) instead of reqwest error display which includes the URL containing the token - Consume stale fallback auth gate before replaying message to prevent duplicate agentic runs on repeated OAuth callbacks - Sanitize channel_name in derive_onboarding user-visible strings - Add #[must_use] to BridgeOutcome enum WS/REST parity: - Add thread_id to WsClientMessage::AuthToken and AuthCancel - WS AuthToken handler now injects follow-up message via msg_tx (matching REST chat_auth_token_handler behavior) - WS AuthCancel handler now clears engine pending auth and injects cancellation message (matching REST chat_auth_cancel_handler) Cleanup: - Deduplicate build_runtime_config_updates (manager.rs imports from approval.rs instead of maintaining its own copy) - Downgrade info! to debug! for auto-generated secret log - Downgrade warn! to debug! for OAuth fallback diagnostic - Upgrade debug! to warn! for on_start failure in propagate_approval - Rename misleading e2e test to match what it actually tests - Add mixed-character truncation test for sanitize_extension_name Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add critical coverage for auth flow security and msg_tx injection New e2e tests: - test_auth_cancel_injects_follow_up_message_via_sse: verifies the msg_tx injection path actually delivers messages end-to-end (SSE response event appears after auth-cancel) - test_sanitize_extension_name_in_auth_cancel: verifies injection characters in extension_name are stripped before reaching the agent loop - test_pairing_approve_sanitizes_channel_name: verifies channel path param is sanitized in pairing approve handler - test_ws_auth_token_accepts_thread_id: verifies WS auth_token messages accept the new thread_id field - test_ws_auth_cancel_accepts_thread_id: verifies WS auth_cancel messages accept thread_id and connection stays alive Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: always inject follow-up message after auth token submission When result.activated was false, the chat_auth_token_handler skipped the msg_tx injection. This left the paused turn (Pending with Done suppressed) permanently stuck — the UI showed "Running tool_install..." forever. Now both REST and WS handlers always: 1. Clear auth mode 2. Broadcast AuthCompleted (with success=true/false) 3. Inject a follow-up message via msg_tx The message content varies based on activation status so the LLM can respond appropriately. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: revert hot_add to clone-then-shutdown to preserve message_tx receiver The previous fix (drop write lock before shutdown) removed the channel from the map before calling shutdown(). This dropped the last strong Arc reference in the channel manager, killing the forwarding task's receiver. The router holds its own Arc to the inner WasmChannel, so propagate_approval's ensure_polling() could still send via message_tx — but the receiver was dead, causing "channel closed" errors. Revert to the staging pattern: read-lock to clone the Arc, drop the lock, shutdown the clone, then write-lock to insert the replacement. The old entry stays in the map (keeping the forwarding task alive) until the insert atomically replaces it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: log bot_username set_setting failure instead of silently dropping Copilot review: the set_setting result for bot_username was silently dropped with `let _ =`. Now logs at debug level if the DB write fails, giving visibility into mention detection degradation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: repair message_tx when Channel::start() fails at boot When a WASM channel is loaded at boot without credentials (fresh DB), on_start fails (e.g., Telegram deleteWebhook returns 404 with unresolved {TELEGRAM_BOT_TOKEN}). Previously, message_tx was set BEFORE on_start, so the sender survived but the receiver (rx) was dropped on error return. Later, refresh_active_channel restarted polling which cloned the orphaned sender — every send failed with "channel closed". Fixes: - Move message_tx creation AFTER on_start succeeds in Channel::start() - Add WasmChannel::ensure_message_channel() that creates (tx, rx) if message_tx is None or closed, returning the stream for forwarding - refresh_active_channel calls ensure_message_channel() after on_start succeeds and wires up a forwarding task if needed Also: - Revert hot_add to match staging exactly (no behavior change needed) - Remove temporary debug logging (message_tx state before dispatch) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address remaining review comments — stale doc, websockets import - Update AuthPending doc to reflect TurnOutcome::CompletedSilently (was "turn NOT completed", now accurately describes conclude_turn) - Move `import websockets` inside try block so ImportError is caught by the except handler when the package isn't installed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review comments — propagate on_start error, dedupe helpers, tighten tests - propagate_approval: propagate on_start() error as ActivationFailed instead of swallowing it (zmanian review #1) - router.rs: move test-only HashMap import into mod tests (zmanian #2) - chat.rs: remove duplicate clear_auth_mode (Copilot review #1) - e2e: strengthen auth-token assertion to check status 200 + success field, remove overlapping test_auth_cancel_returns_success (Copilot #2/#3) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address serrrfirat review — TOCTOU race, missing v2 auth clear, warn log - ensure_message_channel: single write lock for atomic check-and-create (fixes TOCTOU race where concurrent callers could orphan a forwarding task) - chat_auth_token_handler: add missing clear_engine_pending_auth() call (REST/WS parity — WS and REST cancel already had it, REST token did not) - pairing_approve_handler: debug! → warn! for complete_pairing_approval failure (operationally significant — channel won't route until restart) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(web,extensions): address review — sanitize agent messages, fix approve propagation, skip double Telegram getMe (#2432) - Sanitize result.message before interpolation into synthetic agent input to prevent prompt injection via crafted validation errors (server.rs + ws.rs) - Surface complete_pairing_approval() failure to frontend with success=false SSE event and ActionResponse::fail instead of silently succeeding - Return ActionResponse::ok when auth_url is present even if activated=false so OAuth flows can progress through the frontend popup - Skip generic validation_endpoint check for Telegram (validate_telegram_token already calls getMe and extracts bot_username — avoids double API round-trip) - Sanitize generic validation_endpoint error messages to avoid leaking sensitive URL paths (e.g. bot tokens) via reqwest::Error Display Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Unify gateway onboarding and pairing flows * Fix gateway message metadata scoping * Clean up web gateway warnings * Fix auth and onboarding regression fallout * Fix gate resolution and pairing rollback trust boundaries * Guard legacy agent loop from v2 submissions * Fix PR review follow-ups for onboarding flow * Fix CI clippy failure in pairing tests * Fix onboarding review follow-ups * Fix clippy warning in skills catalog * Tighten pairing flow e2e assertions * Fix onboarding auth review follow-ups * Fix auth routing and tui clippy lint * Fix pairing gate handoff in onboarding flow * Fix clippy guard in mission event scan * Fix merged clippy regressions --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: serrrfirat <f@nuff.tech> |
||
|
|
ea15092736 |
Fix Slack relay OAuth callback state lookup (#2512)
* fix: use gateway owner_id for relay OAuth nonce storage The relay OAuth nonce was stored under the authenticated user's ID (a DB user UUID) but the callback handler looked it up under state.owner_id (the gateway owner, typically "default"). This mismatch caused the nonce lookup to silently fail, returning "Invalid or expired authorization" on every Slack OAuth callback. Use self.user_id (which holds config.owner_id) in auth_channel_relay for nonce storage so both sides use the same scope. Also adds tracing to the callback handler's get_decrypted error path to make future auth failures diagnosable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix relay OAuth callback state lookup * Fix relay callback import ordering * fix: address relay oauth review feedback * refactor: share extension name candidate logic * Make relay OAuth nonce consumption atomic * Tighten relay naming and missing-nonce logs * Fix clippy warning in skills catalog * Fix engine and skills clippy warnings * Fix tui clippy match guards * Fix clippy collapsible_match and unnecessary_sort_by warnings Collapse nested `if` inside match arms into match guards and replace `sort_by(|a, b| b.1.cmp(&a.1))` with `sort_by_key(|b| Reverse(b.1))` in glob/grep tools. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Pierre LE GUEN <26087574+PierreLeGuen@users.noreply.github.com> |
||
|
|
a34bba249e |
resolve an issue with shared skills (#2086)
* resolve an issue with shared skills * fix(engine): non-breaking default for list_memory_docs_by_owner: * address comments * fix given github copilot comments * fix project_id and user_id are not stored in the frontmatter * fix(skills): address review feedback on shared-skill visibility - Add caller-level regression test driving handle_list_skills end-to-end so a future revert to project-scoped listing fails at the call site, not just the helper (per .claude/rules/testing.md). - Drop the redundant sort in list_skills_global — callers sort/dedupe the merged result anyway; keep only dedup-by-DocId within the shared set. - Document the N+1 caveat on the default list_memory_docs_by_owner impl so production Store impls know they must override with a flat query. - Log a debug! when deserialize_knowledge_doc falls back to nil project_id or "legacy" user_id, so stale on-disk frontmatter is traceable instead of silently invisible to scoped queries. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(skills): address remaining review comments - Escape user_id, title, and tag strings before embedding in YAML frontmatter. A user_id containing a quote, backslash, or newline (e.g., an OIDC sub with unexpected characters) would otherwise produce unparsable YAML and make the doc unloadable. - Clarify the comment in handle_list_skills: the initial call loads all doc types for the user, not just skills — skill filtering happens later in the filter pass. - Add list_memory_docs_by_owner stubs to the three non-overriding TestStore impls (tests/engine_v2_gate_integration.rs, tests/engine_v2_skill_codeact.rs, src/bridge/router.rs) so the default-impl fallback into list_all_projects (which errors) can't silently swallow shared-skill visibility in integration tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix migrate_legacy_user_ids to preserve __shared__ ownership for Skill docs instead of stamping them with owner_id * add nil-project pass to migrate_legacy_user_ids, and tests --------- Co-authored-by: Emil Bogomolov <emil.bogomolov@near.ai> Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
c835fe99d3 |
fix(ci): make tests resilient to sandboxed/offline environments (#2257)
* fix(ci): make tests resilient to sandboxed/offline environments Tests failed in CI because DNS resolution is unavailable in the sandbox, the process runs as root, and an HTTP proxy intercepts outbound traffic. Core fix: add `dns_probe_available()` to `config::helpers` — a cached, 2-second-timeout probe that detects whether external DNS works. When DNS is unavailable, `validate_base_url_with_policy()` skips the IP resolution/SSRF check while still enforcing syntactic URL validation. Additional fixes: - webhook_server: bind non-local IP (192.0.2.1) instead of privileged port 1, which succeeds as root - tunnel/custom: use closed localhost port instead of TEST-NET-1 IP that the proxy intercepts - mcp/auth: use IP literal instead of hostname requiring DNS - wasm/http_security: add .no_proxy() so pinned resolution test works behind egress proxy - wasm/runtime: remove `enabled = true` from cache TOML config, which was removed as a valid field in Wasmtime 43 https://claude.ai/code/session_01PUK8B5x6dKTG3bxWeSWdaH * fix(deny): add RUSTSEC-2026-0097 ignore, remove stale wasmtime advisories The rand 0.8.5 unsoundness advisory requires the `log` feature which is not enabled on our dep. Wasmtime 43 patches the 4 previously-ignored advisories so those ignores are removed. https://claude.ai/code/session_01LR9WsjkTuMNA6xkGS4TgMt * fix(security): use time-limited DNS probe cache and resolve target hostname Replace OnceLock-based permanent DNS probe cache with a Mutex-guarded cache that expires after 5 minutes, preventing transient DNS unavailability at startup from permanently disabling SSRF validation. Additionally, try resolving the actual target hostname before falling back to the generic probe. This avoids false negatives in firewalled environments where the generic probe target (previously dns.google) may be blocked but the actual target is reachable. Addresses review feedback from serrrfirat on PR #2257. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: apply cargo fmt and fix clippy collapsible_if after rebase on staging https://claude.ai/code/session_01T4ysh3bVb44UustMmJcQgQ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b2a725b380 |
fix(agent): prevent image_analyze calls on web-uploaded images (#2500)
* fix(agent): prevent image_analyze calls on web-uploaded images When images are uploaded through the web UI, they are sent to the LLM as multimodal ContentPart::ImageUrl data URLs — the model can already see them via its vision capabilities. However, the attachment metadata text included a filename (e.g., "image-0.png") with a vague description that led the LLM to call the image_analyze tool, which tries to read from the filesystem. Since uploaded images only exist in memory (never written to disk), this always failed with "No such file or directory (os error 2)", followed by futile glob/list_dir searches. The fix updates the attachment body text for images with inline data to explicitly tell the LLM it already has the image and should not attempt filesystem access or tool calls. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * fix: correct .contains() indentation in attachments test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> |
||
|
|
017218d0a3 |
feat(engine): add code execution failure categorization instrumentation (#2483)
* feat(engine): add code execution failure categorization instrumentation Add structured error classification to the v2 engine's CodeAct execution path so we can measure whether REPL failures come from Monty VM limitations, LLM logic errors, tool dispatch issues, or resource limits. - Add CodeExecutionFailure enum (8 categories: SyntaxError, RuntimeError, NameLookup, VmPanic, ResourceLimit, ToolError, GatePause, OsDenied) - Add CodeExecutionFailed event kind to EventKind for event sourcing - Tag every error return path in scripting.rs with the correct category - Emit CodeExecutionFailed events from the orchestrator on code errors - Enhance trace analyzer to use structured events (with fallback to message-level pattern matching for pre-instrumentation threads) - Expand fallback error patterns from 4 to 10 Python exception types - Add 13 regression tests covering classification and trace detection This enables aggregate queries like "what % of code failures are Monty VM panics vs LLM generating bad Python" to inform runtime decisions. https://claude.ai/code/session_018jFKVTjv1pkzwJobw43HwP * fix(engine): address ilblackdragon + gemini review — failure instrumentation correctness (#2483) - Replace `had_error: bool` + `failure_category: Option<_>` with single `failure: Option<CodeExecutionFailure>` field, making invalid states unrepresentable - Remove `GatePause` variant (gate pauses are suspensions, not failures) - Convert all 9 catch_unwind Err(_) paths to emit VmPanic instead of propagating EngineError, so panics get proper instrumentation events - Fix error_text truncation: take last 500 chars (where tracebacks are), not first 500 chars (where print output is) - Thread real `Instant::now()` timing through `duration_ms` instead of hardcoded 0 - Tighten `classify_runtime_error`: "syntax" → "syntaxerror", remove loose `"os" && "denied"` substring match, reorder checks - Replace `DefaultHasher` with FNV-1a for stable cross-version hashing - Add `#[serde(rename_all = "snake_case")]` so Serialize matches Display - Add `#[serde(other)] Unknown` to EventKind for forward-compat - Fix misleading CodeExecutionFailed docstring - Add orchestrator caller test for CodeExecutionFailed event emission - Use `to_ascii_lowercase()` per project convention Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve clippy warnings — simplify boolean expressions and remove tautological assert - Replace `!result.failure.is_some()` with `result.failure.is_none()` in scripting tests - Remove tautological `duration_ms >= 0` assertion on unsigned type in orchestrator test - Add missing V24 migration checksum to checksums.lock Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): address ilblackdragon review nits — tail_chars helper, tighten fuel match, clippy (#2483) - Extract `tail_chars(s, n)` helper to deduplicate last-N-chars logic in `handle_execute_code_step` (used by both ActionFailed and CodeExecutionFailed event emission) - Tighten `classify_runtime_error` fuel match from `contains("fuel")` to `contains("out of fuel") || contains("fuel exhausted")` to avoid miscategorizing runtime errors that mention the word "fuel" - Fix test message typo: "should set had_error" → "should set failure" - Fix clippy warnings: `!x.is_some()` → `x.is_none()`, remove tautological `u64 >= 0` assertion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): drop stray V24 checksums.lock entry (#2483) Rebase artifact — no V24 migration exists in this PR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
853b6a531d |
fix: restore issue-2402 v2 gate resume and action alias consistency (#2458)
* fix(engine): normalize granted action aliases across lease checks Keep lease preflight, policy, and consumption consistent for hyphen/underscore action aliases so installed tools do not fail mid-turn after being allowed. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix(web): preserve pending gate call ids on auth resume Resolve or synthesize the original action call id when resuming auth or external callback gates so resumed ActionResult messages remain correctly paired with the waiting assistant call. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * test(engine): cover install resume followed by aliased tool use Add a higher-fidelity v2 gate integration regression that proves an install auth resume can flow directly into an aliased follow-up tool call and still complete the thread instead of stalling. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix(engine): apply policy checks to aliased action names Resolve structured preflight action definitions with the same hyphen/underscore alias semantics as lease matching so aliased calls cannot bypass approval or deny policies. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * style: cargo fmt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(bridge): scan internal_messages in legacy call_id fallback The `resolved_call_id_for_pending_action` legacy fallback scanned only `thread.messages`, but in production the orchestrator writes ActionResult and assistant messages to `thread.internal_messages` via `sync_runtime_state`. This meant the `resolved_ids` set was always empty and the fallback never found a match, silently falling through to a synthetic id. Scan both `messages` and `internal_messages` so the legacy path works correctly for orchestrator-driven threads. Addresses review feedback from @standardtoaster on #2458. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: format bridge router --------- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> Co-authored-by: Zaki <zaki@iqlusion.io> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
5b2bd1563a |
fix(gateway): extend settings search to card, tool, and user sections (#2518)
* fix(gateway): extend settings search to card, tool, and user sections Settings search only filtered .settings-row elements, leaving Channels, Extensions, MCP, Skills, Tools, and User Management sections unsearchable. Add filtering for .ext-card, .tool-permission-row, and #users-tbody tr elements, and update CSS to hide matched elements. * fix(gateway): extend settings search to card, tool, and user sections Settings search only filtered .settings-row elements, leaving Channels, Extensions, MCP, Skills, Tools, and User Management sections unsearchable. Add filtering for .ext-card, .tool-permission-row, and #users-tbody tr elements, update CSS to hide matched elements, and reorder logic so container visibility checks run after all items are filtered. Add E2E tests covering search across tool rows and extension cards. * chore: minor --------- Co-authored-by: Robert Yan <46699230+think-in-universe@users.noreply.github.com> |
||
|
|
ecd37e10db | ci: support historical Dockerfile targets in rebuild workflow (#2509) | ||
|
|
b3478cf381 |
ci: add historical release image rebuild workflow (#2507)
* ci: add release image rebuild workflow * ci: tighten release image rebuild checks |
||
|
|
427783da67 |
fix(engine): surface action errors to LLM with [ACTION FAILED] prefix (#2326)
When tools fail (e.g. "No lease for action"), the orchestrator appended the raw error JSON as an ActionResult message. The LLM frequently ignored these errors and claimed success — a trust/hallucination issue. Prefix failed action results with "[ACTION FAILED] <tool>:" so the LLM receives an unmissable signal that the tool call did not succeed. The Rust executor already sets `is_error: true` on lease and policy failures. Closes #2279 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
7008e9a881 |
feat(gate): persist "always approve" decisions to DB in v2 engine path (#2428)
* feat(db): add per-user CachedSettingsStore decorator SettingsStore methods hit the database on every call. The v2 engine path (effect_adapter) and the dispatcher's per-turn tool permission loading both called get_all_settings() without caching, adding unnecessary DB round-trips on every agentic loop iteration. Add a write-through CachedSettingsStore decorator that caches get_all_settings() results per user_id. Write operations (set_setting, delete_setting, set_all_settings) delegate to the inner store then invalidate that user's cache entry. The write lock is held across DB loads to prevent stale-data races from concurrent invalidations. Wire the cache into TenantScope via a new settings_store field on AgentDeps, so all settings reads in the agent loop go through the cache. Remove the per-turn cached_tool_permissions Mutex hack from ChatDelegate that was working around the missing cache layer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review feedback - Store Arc<HashMap> in cache instead of bare HashMap to avoid cloning the full settings map on every cache hit. get_setting/has_settings now only clone the single requested value or check emptiness through the Arc. - Route get_setting_with_admin_fallback() through self.settings() instead of self.inner so both the per-user and admin lookups go through the cache. - Update settings section comment to accurately describe which methods delegate through settings(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(gate): persist "always approve" decisions to DB in v2 engine path The v2 engine's resolve_gate() only stored "always approve" decisions in-memory via EffectBridgeAdapter::auto_approve_tool(), losing them on process restart. The v1 path (thread_ops.rs) already persisted to DB. Add persist_always_allow() and revert_always_allow() helpers that write tool_permissions.{name} = AlwaysAllow to the SettingsStore, preferring the CachedSettingsStore for write-through cache invalidation. Includes defense-in-depth: tools declaring ApprovalRequirement::Always are never persisted regardless of what the client sends. Reverts the DB write if the resumed tool execution fails. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove no-op test flagged in PR review Remove test_single_approval_does_not_persist — it only asserted an empty store was empty without exercising any production code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(gate): address PR review — security and correctness fixes - Use pending.parameters (not empty json) for defense-in-depth check so param-dependent tools like shell correctly detect Always requirement - Validate action_name with is_valid_admin_tool_name() before persisting to prevent settings key injection via dots or special characters - Save pre-existing permission value before overwriting; restore it on revert instead of blindly deleting (preserves long-standing prefs) - Replace serde_json::to_value().unwrap_or() with json!("always_allow") - Add tests: prior-value restoration, invalid tool name rejection, settings_store=None fallback to state.db Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(gate): downgrade warn! to debug! in persist/revert paths Internal diagnostics in persist_always_allow and revert_always_allow used tracing::warn!, which corrupts the REPL/TUI per CLAUDE.md logging rules. Downgraded all 5 call sites to debug!. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(gate): address ilblackdragon + serrrfirat review feedback - Upgrade persist/revert failure logs from debug! to warn! — DB persistence failures are security-relevant (user believes preference is permanent but it silently vanishes on restart). Matches v1 pattern at thread_ops.rs:1256. Safe in v2 (web gateway, not TUI). - Fix serialization drift: use serde_json::to_value(PermissionState:: AlwaysAllow) instead of hardcoded json!("always_allow"), coupling to the enum's serde rename attribute. - Add dispatch-exempt comments on direct set_setting/delete_setting calls per .claude/rules/tools.md. - Add #[cfg(feature = "libsql")] gate to test_persist_falls_back_to_ state_db — test_db() requires the libsql feature. - Update ApprovalGate docstring: v2 persistence is now wired, not aspirational. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add Playwright persistence happy-path test (#2485) Add 3 e2e tests for always-approve persistence: - test_always_approve_persists_to_db: verifies DB row after "always" - test_revoke_always_approve_updates_db: verifies PUT revocation - test_always_approve_survives_restart: restartable server, verifies auto-approve persists across process restart Fix: remove raw Database fallback from persist/revert_always_allow. The state.db fallback bypassed CachedSettingsStore cache invalidation, causing GET /api/settings/tools to serve stale data until the 5-min TTL expired. In production agent.deps.settings_store is always available when the DB is; the fallback was dead code that broke cache coherence. Also: unit test for Settings::from_db_map tool_permissions parsing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(gate): downgrade always when allow_always is false A crafted client could send always:true on a gate where the pending ResumeKind had allow_always:false (e.g. ApprovalRequirement::Always tools). The in-memory auto_approve_tool would be set, silently bypassing future approval prompts. persist_always_allow already guarded against this via the ApprovalRequirement::Always check, but the in-memory path did not. Now resolve_gate downgrades always to false when the pending gate's resume_kind doesn't permit it, before touching either the in-memory set or the DB. Also: fix ApprovalGate docstring to distinguish persistence from hydration per Copilot review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
7206bf0694 |
feat(gateway): rich tool cards in history + thread processing indicator (#2477)
* feat(gateway): rich tool cards in history + thread processing indicator History rendering: - Add createActivityGroupFromHistory() to render the most recent turn's tool calls as the same .activity-tool-card DOM structure used during live SSE (expandable cards with icons, output preview, error details). Older turns keep the compact "N tools used" summary to limit DOM size. Thread processing indicator: - Track background threads with active agent work via processingThreads Set (fed by thinking, tool_started, stream_chunk SSE events for non-current threads; cleared on status "Done" and SSE reconnect). - Render a .thread-processing spinner in the sidebar for threads that are actively processing. E2E tests: - test_message_persists_across_page_reload: message + response survive full page reload - test_tool_calls_rendered_as_activity_cards_after_reload: echo tool renders as rich .activity-tool-card with data-status="success" - test_tool_calls_expandable_after_reload: summary click expands cards container, card header click expands body - test_background_thread_shows_processing_indicator: background thread gets unread badge after completion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add processing indicator tests + review fixes - test_processing_indicator_shows_on_thread_switch: verify completed turns show no stale "Processing..." when switching back - test_processing_indicator_shows_for_incomplete_turn: verify the thinking indicator appears when switching to a mid-turn thread (gracefully skips if agent completes too fast to catch) - Add activity_thinking/activity_thinking_text selectors to helpers.py Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(gateway): address PR #2477 review comments - Clear processingThreads + refresh sidebar on SSE reconnect so stale spinners are removed immediately - Clear processingThreads on "Awaiting approval" status (terminal state where agent is blocked on user input, not actively processing) - Map tool call status from has_result/has_error: running (neither), success (has_result), fail (has_error) — shows spinner for in-progress tools in history instead of misleading checkmark - Auto-expand activity group when any tool call has an error - Add data-thread-id attribute to .thread-item for testability - Scope processing spinner and unread badge assertions to specific thread ID in E2E tests - Add explicit spinner visibility/removal assertions to background thread processing indicator test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(gateway): address second round of PR #2477 review comments - Use activity-icon-success/activity-icon-fail CSS classes for history tool card icons (matches live card styling with colored ✓/✗) - Fix _wait_for_completed_turn to check turns[-1] instead of any() to avoid early return when earlier turns are already completed - Rename test_processing_indicator_shows_on_thread_switch to test_no_stale_processing_indicator_for_completed_thread to match what it actually verifies (no stale indicator, not indicator presence) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
62bb007b57 |
feat(tui): add multiline support and input clear (#2449)
* feat(tui): add multiline support & minor fixes This commit introduces: - Support for multi-line messagess (input and rendering) - A UX improvement: ctrl+c first clears the screen, then exits Now, users can create multi-line messages by inserting new lines using (Shift+Enter) or (Alt+Enter) or (Ctrl+J) - all terminal standards to handle multi-line input. The input area grows with content (3..=12 rows) based on logical line count. Pastes with CR or CRLF line endings are normalized so multi-line pastes from terminals like macOS Terminal.app render as real newlines User messages in the conversation panel now render each logical line on its own row instead of collapsing newlines into whitespace - Ctrl+C on a non-empty input clears it; Ctrl+C on an empty input quits - History recall (Up/Down) lands the cursor on the first line of the recalled entry, and returning to the saved draft also lands at (0,0), so long multi-line drafts are immediately re-navigable - Down within a recalled multi-line message now moves the cursor instead of snapping back to the draft - Tests added for all of the above (176 passing) * fix: height calculation and missing history As Gemini code assistant pointed, there were two errors with the code. First, we were for some reason getting ride of the history_index when inserting a new line, which makes no sense. We are now simply adding the new line. Second, we had an off-by-one error on counting the number of displayed lines to the user, for which we were showing one less than expected. * chore: undo unnecesary change on comment * fix: remove unnecesary pub(crate) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: address reviewer comments - Renamed Quit action to ClearOrQuite to better reflect its behaviour. - Added a cast to u16 on clamping text_rows * fix: better separate history content on lines * chore: cargo fmt * chore: apply minor reviews from copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: update input overlay on /model as suggested by copilot * chore: rename test function Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Guillermo Alejandro Gallardo Diez <gagdiez@iR2.local> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> |
||
|
|
be0b33b2a3 |
fix: duplicate reasoning_content fields in chat completions response (#2493)
* fix: duplicate reasoning_content fields in chat completions response
* fix: resolve comments
* style: fix rustfmt and ignore RUSTSEC-2026-{0098,0099} in cargo-deny
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve comments
---------
Co-authored-by: serrrfirat <f@nuff.tech>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
4353493a97 |
fix(gateway): resolve assistant thread for threadless broadcasts (#2444)
* fix(gateway): resolve assistant thread for threadless broadcasts Mission notifications, self-repair alerts, and extension activation messages broadcast via channels without a thread_id. The gateway's broadcast() rejected these with MissingRoutingTarget, silently dropping the messages. Two fixes: 1. Mission notification now chains .in_thread() — the thread_id was already available on MissionNotification but not being passed through. 2. Gateway broadcast() falls back to the user's assistant conversation when thread_id is None and a DB store is available. This routes threadless messages (self-repair, extension activation) to a known thread instead of rejecting them. When no store is available, the original MissingRoutingTarget error is preserved. Fixes #2405 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: don't leak owner thread_id to notify_user in mission broadcasts When notify_user differs from the mission owner, omit .in_thread() so the gateway's broadcast() fallback resolves the recipient's own assistant thread instead of attaching the owner's thread_id. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: verify broadcast thread_id resolution and cross-user guard Address review feedback on #2444: 1. Fallback test now subscribes to SSE, verifies the emitted thread_id matches the DB assistant conversation UUID, and confirms the row exists. 2. Three new caller-level tests for the cross-user guard in handle_mission_notification: - cross-user: notify_user != user_id -> owner's thread_id is NOT attached, recipient gets their own assistant thread - same-user: notify_user is None -> owner's mission thread_id IS attached to the broadcast - explicit same-user: notify_user = Some(user_id) -> guard still matches, thread_id is attached (catches is_none() refactors) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: fix rustfmt in cross-user guard tests Collapse handle_mission_notification call sites to single-line form to satisfy cargo fmt. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: ignore RUSTSEC-2026-0098 and bump rustls-webpki 0.103.12 RUSTSEC-2026-0098 (URI name constraint bypass in rustls-webpki) affects 0.102.8, which is pinned by the libsql 0.6.0 transitive dependency chain (libsql -> rustls 0.22 -> rustls-webpki 0.102.x). The fix (>=0.103.12) is only available on the 0.103.x line, so the 0.102.8 instance cannot be upgraded without a libsql major bump. Add the advisory to deny.toml ignore list (same rationale as the existing RUSTSEC-2026-0049 exception for the same crate/version). Also bump rustls-webpki 0.103.10 -> 0.103.12 for the non-pinned instance. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: serrrfirat <f@nuff.tech> |
||
|
|
ae1f69838d |
fix(llm): map HTTP 413 to ContextLengthExceeded for auto-compaction (#2339)
* fix(llm): map HTTP 413 to ContextLengthExceeded for auto-compaction (#2276) HTTP 413 (Payload Too Large) was falling through to generic RequestFailed, causing the retry provider to retry the same oversized payload 3x, count toward the circuit breaker threshold, and fail over to other providers with the same too-large context. The existing compaction recovery in dispatcher.rs (which handles ContextLengthExceeded) was never reached. Fix: - nearai_chat.rs: explicit 413 check → ContextLengthExceeded - nearai_chat.rs: detect context length errors in 400 response bodies - rig_adapter.rs: map_rig_error() detects context length patterns in error messages from OpenAI/Anthropic/Ollama providers Now when context exceeds provider limits, the dispatcher automatically triggers compaction and retries with a smaller context window. Closes #2276 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ci): resolve formatting and cargo-deny failures Run cargo fmt on rig_adapter.rs (two chain-expression reflows) and add RUSTSEC-2026-0097 (rand 0.8.x unsoundness) to deny.toml ignore list. https://claude.ai/code/session_01VidPyvxYesocfhH1bYJP5Y * chore(deny): remove stale wasmtime advisories resolved by v43 upgrade The 4 wasmtime advisories (RUSTSEC-2025-0046, RUSTSEC-2025-0118, RUSTSEC-2026-0020, RUSTSEC-2026-0021) no longer match any crate in the lockfile after the v43 upgrade and were generating advisory-not-detected warnings. https://claude.ai/code/session_01MMhMuxXvAXTcFZ3EAga12k * fix(llm): remove bare "413" false-positive match, parse token counts from errors Address review feedback on PR #2339: - Remove bare "413" substring match from map_rig_error() to prevent false positives on timestamps, token counts, and request IDs. The "payload too large" pattern already covers legitimate 413 errors. - Parse used/limit token counts from error messages when providers include them (e.g. OpenAI's "maximum context length is X tokens... resulted in Y tokens" format), instead of always returning 0/0. - Use to_ascii_lowercase() and idiomatic slice-based any() pattern per project convention. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(llm): resolve cargo fmt violations in nearai_chat.rs Collapse multi-line let bindings for parse_token_counts() calls onto single lines, matching rustfmt expectations. https://claude.ai/code/session_01NSKpFprVJLtoyAaVs4FXio * fix(deps): update gimli 0.33.1 -> 0.33.0 to resolve yanked crate gimli 0.33.1 was yanked from crates.io, causing cargo-deny to fail. https://claude.ai/code/session_01CfQr8EtFrqnrjseuVeGJ13 --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
16a07316d0 |
test(e2e): add Playwright persistence happy-path test (#2475)
* test(e2e): add Playwright persistence happy-path test Add `test_message_persists_across_page_reload` which validates the full persistence round-trip: send a message via the chat UI, reload the page (clearing all client-side state), switch back to the thread, and verify both user message and assistant response are restored from the database. Cross-checks via the history API that exactly one user turn exists with a completed response. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(e2e): address PR review comments - Replace fixed `wait_for_timeout(2000)` with polling via history API until the turn reaches `Completed` state (avoids CI flakiness) - Use `SEL["auth_screen"]` instead of hardcoded `"#auth-screen"` selector (follows project convention from helpers.py) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
8973d1b534 |
fix: use gateway owner_id for relay OAuth nonce storage (#2473)
* fix: use gateway owner_id for relay OAuth nonce storage The relay OAuth nonce was stored under the authenticated user's ID (a DB user UUID) but the callback handler looked it up under state.owner_id (the gateway owner, typically "default"). This mismatch caused the nonce lookup to silently fail, returning "Invalid or expired authorization" on every Slack OAuth callback. Use self.user_id (which holds config.owner_id) in auth_channel_relay for nonce storage so both sides use the same scope. Also adds tracing to the callback handler's get_decrypted error path to make future auth failures diagnosable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add test for nonce user scope mismatch Verifies that a nonce stored under a DB user UUID (different from the gateway owner_id) is not found by the callback handler, reproducing the bug that caused "Invalid or expired authorization" on hosted instances. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review comments - Also delete legacy caller-scoped nonce on upgrade (Copilot) - Include redacted state param in tracing log (Gemini) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
2dc78b2d94 |
feat(db): add per-user CachedSettingsStore decorator (#2425)
* feat(db): add per-user CachedSettingsStore decorator SettingsStore methods hit the database on every call. The v2 engine path (effect_adapter) and the dispatcher's per-turn tool permission loading both called get_all_settings() without caching, adding unnecessary DB round-trips on every agentic loop iteration. Add a write-through CachedSettingsStore decorator that caches get_all_settings() results per user_id. Write operations (set_setting, delete_setting, set_all_settings) delegate to the inner store then invalidate that user's cache entry. The write lock is held across DB loads to prevent stale-data races from concurrent invalidations. Wire the cache into TenantScope via a new settings_store field on AgentDeps, so all settings reads in the agent loop go through the cache. Remove the per-turn cached_tool_permissions Mutex hack from ChatDelegate that was working around the missing cache layer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review feedback - Store Arc<HashMap> in cache instead of bare HashMap to avoid cloning the full settings map on every cache hit. get_setting/has_settings now only clone the single requested value or check emptiness through the Arc. - Route get_setting_with_admin_fallback() through self.settings() instead of self.inner so both the per-user and admin lookups go through the cache. - Update settings section comment to accurately describe which methods delegate through settings(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address all PR review feedback - Use `crate::db::` imports instead of `super::` (convention fix) - Add `wrap()` factory fn to CachedSettingsStore, simplify app.rs construction - Store `Arc<HashMap>` in cache to avoid full map clones on hits - Expose `invalidate_user()` and `flush()` public methods - Wire `flush()` into SIGHUP handler via concrete `settings_cache` on AppComponents - Wire `settings_store` into GatewayState and route all settings handlers through it so web UI writes invalidate the cache (critical fix) - Route `get_setting_with_admin_fallback()` through `self.settings()` - Add error-path test (FailingStore mock, cache not poisoned on error) - Add concurrent-access test (8 concurrent readers, inner store hit once) - Add TenantScope caller-level test (read/write through cache wiring) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reuse resolve_settings_store() in settings_tools_set_handler Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address all review feedback on CachedSettingsStore - Add TTL (300s) and max-entries cap (1000) to bound cache staleness and memory growth. Entries expire after TTL; cache clears when cap exceeded. - Route admin tool_policy GET/PUT through resolve_settings_store() so writes invalidate the __admin__ cache entry. - Route settings_export_handler and settings_tools_list_handler through resolve_settings_store() (were bypassing cache on reads). - Wire invalidate_user() into users_delete_handler and users_suspend_handler so deleted/suspended users' settings are evicted. - Replace GatewayState.settings_store (trait object) with settings_cache (concrete CachedSettingsStore) — single field for both trait dispatch and cache management, no desync risk. - Add settings_override to ExtensionManager with with_settings_store() builder. All settings reads/writes in ExtensionManager now route through the cached store when available. - Make ExtensionManager::settings_store() pub(crate); update AuthManager to call it instead of database(), closing the auth descriptor cache bypass. - Remove unused wrap() method; merge redundant invalidate/invalidate_user. - Add tracing::debug on SIGHUP cache flush. - Expand module docs with design assumptions, known bypass paths, TTL and eviction semantics. - Add tests: expired_entry_triggers_reload, fresh_entry_does_not_reload, max_entries_cap_triggers_eviction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: collapse nested if into filter to satisfy clippy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
82d341d628 |
fix(sandbox): try Docker socket before CLI binary check (#2467)
* fix(sandbox): try Docker socket before CLI binary check The sandbox detection checked `which docker` first and returned NotInstalled if the CLI binary was absent — even when the Docker daemon was reachable via a bind-mounted socket. This broke container-in-container deployments (e.g., Nomad shards with /var/run/docker.sock mounted) where bollard can talk to the daemon but no CLI is installed in the slim image. Reorder check_docker() to try connect_docker() (bollard socket ping) first. If the daemon responds, return Available immediately. The CLI check is now only used as a fallback for error-message quality when the socket connection fails. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(sandbox): skip slow daemon ping when Docker is clearly absent check_docker() called connect_docker() before checking whether Docker was even present, causing a 120s bollard timeout on hosts with an unreachable DOCKER_HOST and no Docker installation. Add a fast-path that checks for the docker binary, DOCKER_HOST env var, and socket files on disk before attempting the daemon ping. This preserves DinD support (bind-mounted socket, no CLI binary) while avoiding the latency regression for non-Docker hosts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(sandbox): add regression tests for check_docker fast-path Extract should_skip_daemon_ping() predicate from check_docker() and add unit tests covering all combinations: skip when no binary, no DOCKER_HOST, and no socket (the bug scenario); no skip when any of the three signals is present (DinD socket, DOCKER_HOST, CLI binary). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
d63601eaed |
fix(security): gate test URL rewriters behind #[cfg(test)] (fixes #2056) (#2401)
The `rewrite_telegram_api_url_for_testing()`, `rewrite_http_url_for_testing()`, and their supporting constants/helpers were gated behind `#[cfg(any(test, debug_assertions))]`, which means they shipped in all debug builds — including development/staging deployments. An attacker who could set `IRONCLAW_TEST_TELEGRAM_API_BASE_URL` or `IRONCLAW_TEST_HTTP_REWRITE_MAP` environment variables on such a deployment could redirect Telegram API traffic (and other HTTP traffic) to an arbitrary host. Changes: - Narrow all test URL rewrite constants, functions, and helpers from `#[cfg(any(test, debug_assertions))]` to `#[cfg(test)]` - Add missing `#[cfg(test)]` to `TELEGRAM_TEST_API_BASE_ENV` (was ungated) - Wrap the call site in `http_request()` with `#[cfg(test)]`/`#[cfg(not(test))]` blocks so production builds use `logical_url` directly - Remove the now-unnecessary `#[cfg(not(...))]` stub functions that returned None Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
28c6a1520c |
fix(ci): exclude test files from PR size classification (#2387)
* fix(ci): exclude test files from PR size classification Test code shouldn't inflate PR size labels — a 1-line fix with 500 lines of tests was getting classified as XL instead of XS. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: retrigger with skip-regression-check label Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Zaki <zaki@iqlusion.io> |
||
|
|
d85c11dff2 |
fix(telegram): route WASM owner_id fallback (#2349)
* Fix WASM channel owner_id fallback * ci: ignore rand advisory * ci: satisfy cargo-deny path dependency versions * fix(telegram): handle null/string owner_id and propagate to WASM config The bundled Telegram capabilities.json ships `"owner_id": null`. The previous code only called `Value::as_i64()`, which returns `None` for `Null`, so the fallback silently produced no owner — the fix never actually worked for Telegram. Changes: - Handle `Null`, `String`, and `Number` variants in `owner_actor_id_for_channel()` so the real production payload works. - Propagate the *resolved* owner_id into the WASM runtime config map regardless of whether it came from runtime config or capabilities fallback (previously only the runtime-config path injected it). - Add `tracing::debug!` for non-scalar owner_id values to aid debugging. - Add tests: null config, missing capabilities file, empty string, non-scalar value, and caller-level register_channel tests that verify config injection and null-owner-id handling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: drop overlapping Cargo.toml and deny.toml changes per review Revert cosmetic Cargo.toml attribute reorder and deny.toml comment shortening that overlap with #2370 already on staging, avoiding potential merge conflicts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: log debug warning for non-integer numeric owner_id in capabilities When as_i64() returns None for a numeric owner_id (e.g., 1.0), emit a debug log to aid debugging instead of silently returning None. Adds a regression test for the float case. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: retrigger checks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Zaki <zaki@iqlusion.io> |
||
|
|
5140279bb4 |
docs: guide how to host ironclaw on google cloud (#2262)
* feat: add google turorial * feat: update zh google tutorial * feat: update firewall rules * feat: update zh files |
||
|
|
1fa73a43e3 |
docs: add Responses API section to USER_MANAGEMENT_API (#2440)
Document the /v1/responses endpoints (create, get) including streaming SSE events, structured context (x_context), and multi-turn conversation support via previous_response_id. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
0a9d8165e8 |
fix(responses-api): thread creation, GET by ID, streaming delta, context injection (#2167)
* fix(responses-api): thread creation, GET by ID, streaming delta, context injection - Allow new threads from Responses API (metadata.source check) - Session manager adopts external UUID as internal thread ID - Skip duplicate delta when StreamChunks already delivered - x_context field for structured data injection (IronClaw extension) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address zmanian + serrrfirat review feedback 1. Replace spoofable metadata check with channel-based check: `message.channel == "gateway"` instead of `metadata["source"] == "responses_api"`. Channel names are server-set and unforgeable by WASM channels. 2. Reuse placeholder item ID in streaming worker: capture ID from acc.output[idx] instead of calling make_item_id() again. Fixes added→done ID mismatch that breaks client correlation. 3. Enforce 10 KB size limit on x_context to prevent context window exhaustion and DB bloat. Returns 400 if exceeded. 4. Log warning on UUID collision in create_thread_with_id. 5. Guard ext_uuid adoption: re-check under read lock that UUID isn't already mapped to another ThreadKey before adopting. 6. Update x_context doc: clarify alias collision risk, max size. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: remove accidentally committed integration test files * fix: address henrypark133 review — warn→debug, comment, unwrap 1. tracing::warn! → tracing::debug! in create_thread_with_id (warn corrupts TUI per project convention) 2. Comment fix: "Re-check under write lock" → "Check under read lock" (matches actual code which uses read().await) 3. Replace .unwrap_or_default() with .map().unwrap_or(0) to avoid no-unwrap policy violation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
1041094bee |
style: fix cargo fmt line wrapping in thread_ops.rs (#2451)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |