mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-27 08:00:17 +00:00
189fc031e36f2a9a9ea82673e49680bcee6244ff
155
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a4f6cda5c9 |
fix(routines): add missing extension_manager field in trigger_manual EngineContext
The EngineContext construction in trigger_manual was missing the extension_manager field, causing compilation failure on libsql-only builds (Windows CI). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
6d847c6009 |
feat(webhooks): add public webhook trigger endpoint for routines (#736)
* feat(webhooks): add public webhook trigger endpoint for routines
Add POST /api/webhooks/{path} endpoint that matches incoming webhooks
against routines with Trigger::Webhook, validates secrets using
constant-time comparison (subtle crate), and fires the matched routine
through the message pipeline.
Closes #651
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(webhooks): address PR review feedback - access control, targeted query, rate limiting
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(ci): add missing webhook_rate_limiter field and fix formatting
Add the webhook_rate_limiter field to the GatewayState initializer in
gateway_workflow_harness.rs and fix rustfmt formatting for the webhook
tuple in types.rs.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(security): require webhook secret, add rate limiting, improve tests
Extract validate_webhook_secret() from the handler so the security-critical
secret validation logic (mandatory secret, constant-time comparison) is
directly testable without mocking the database layer. Improves the error
message for misconfigured routines to guide users toward the fix.
Replaces the previous unit tests (which only tested Rust pattern matching
and status code constants) with tests that exercise the actual validation
function against all rejection paths: missing secret (403), non-webhook
trigger (403), wrong secret (401), empty secret (401), and different-length
secret (401).
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* Route webhook triggers through RoutineEngine instead of chat pipeline
Adds fire_webhook() to RoutineEngine and updates the webhook handler
to use it. This ensures webhook-triggered routines get proper run
tracking, guardrail enforcement (cooldown + max_concurrent),
notifications, and FullJob dispatch support.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: fix formatting in webhook handler
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: [email protected] <[email protected]>
|
||
|
|
d3b69e7be3 |
Fix CI approval flows and stale fixtures (#1478)
* Fix CI approval flows and stale fixtures * Backfill approval thread mapping across channels |
||
|
|
ee6f5cd62a |
Use live owner tool scope for autonomous routines and jobs (#1453)
* Use live owner tool scope for autonomous runs * Address autonomous tool scope review feedback * Normalize routine context paths again |
||
|
|
1b97ef4feb |
fix: resolve wasm broadcast merge conflicts with staging (#395) (#1460)
* channels/wasm: implement telegram broadcast path for message tool * channels/wasm: tighten telegram broadcast contract and tests * fix: resolve merge conflicts with staging for wasm broadcast - Remove duplicate broadcast() impls from WasmChannel and SharedWasmChannel (staging already has the generic call_on_broadcast path) - Remove obsolete telegram-specific test helpers and tests that tested the old telegram-only broadcast logic - Add test_broadcast_delegates_to_call_on_broadcast for the generic path - Fix missing fallback_deliverable field in job_monitor test SseEvents Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: davidpty <[email protected]> Co-authored-by: firat.sertgoz <[email protected]> Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
e82f4bd2e5 |
fix: register sandbox jobs in ContextManager for query tool visibility (#1426)
* fix: register sandbox jobs in ContextManager for query tool visibility
Sandbox jobs created via execute_sandbox() were persisted to the database
but never registered in the in-memory ContextManager. Since all query tools
(list_jobs, job_status, job_events, cancel_job) only search the
ContextManager, sandbox jobs were invisible to the agent despite running
successfully in Docker containers.
Changes:
- Add register_sandbox_job() to ContextManager (pre-determined UUID,
starts InProgress, respects max_jobs)
- Extract insert_context() helper to deduplicate create_job_for_user
and register_sandbox_job
- Add update_context_state / update_context_state_async to sync
ContextManager state on sandbox job completion/failure
- Extend job_monitor with spawn_job_monitor_with_context() and
spawn_completion_watcher() so fire-and-forget jobs transition out
of InProgress when the container finishes
- Make CancelJobTool sandbox-aware (stops container + updates DB)
- Wire sandbox deps into CancelJobTool in register_job_tools()
- 8 regression tests across context manager and job monitor
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* fix: add missing allow_always field in PendingApproval test literal
Upstream commit
|
||
|
|
b952d229f9 |
fix: prefer execution-local message routing metadata (#1449)
* fix: prefer execution-local message routing metadata * test: cover message routing fallback metadata * refactor: simplify message target resolution * fix: ignore stale channel defaults for notify user metadata |
||
|
|
31c3b5b041 |
feat(agent): activate stuck_threshold for time-based stuck job detection (#1234)
* feat(agent): activate stuck_threshold for time-based stuck job detection (#1223) The stuck_threshold field on DefaultSelfRepair was defined but never used (marked #[allow(dead_code)]). Jobs that got stuck in InProgress without transitioning to Stuck state (e.g., deadlock, unhandled timeout) were never detected by self-repair. Changes: - Add find_stuck_jobs_with_threshold() to ContextManager that detects InProgress jobs running longer than the threshold - Wire stuck_threshold into detect_stuck_jobs() so it uses threshold-based detection alongside explicit Stuck state detection - Remove dead_code annotation from stuck_threshold - Accept InProgress jobs in the stuck job detection filter Configurable via AGENT_STUCK_THRESHOLD_SECS (default: 300s). Closes #1223 Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(agent): address PR #1234 review feedback for stuck_threshold - Transition InProgress jobs to Stuck before returning them from detect_stuck_jobs(), so attempt_recovery() (which requires Stuck state) works correctly on threshold-detected jobs - Add detect-and-repair E2E test covering the full InProgress -> Stuck -> recovery -> InProgress cycle - Rename idle_threshold -> elapsed_threshold in find_stuck_jobs_with_threshold for clarity - Add `use std::time::Duration` import and remove fully qualified paths - Update CLAUDE.md to reflect that stuck_threshold is now actively used Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: measure stuck_duration from Stuck transition, handle InProgress→Stuck in repair - Fix stuck_duration computation to use the most recent Stuck transition timestamp instead of started_at, preventing jobs that ran for hours before becoming stuck from immediately exceeding the threshold - Fix last_activity to also use the Stuck transition timestamp - Transition InProgress jobs to Stuck before calling attempt_recovery() in repair_stuck_job(), since attempt_recovery() requires JobState::Stuck - Add regression test verifying a recently-stuck job with old started_at is not misdetected as exceeding a 5-minute threshold Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(agent): address Copilot review comments on PR #1234 - Add comment in find_stuck_jobs_with_threshold() noting that started_at is not reset on Stuck->InProgress recovery, which may cause false positives for recovered jobs. Suggests tracking in_progress_since or using the most recent StateTransition as a future improvement. - Fix misleading test comment in stuck_duration_measured_from_stuck_transition test: explicitly Stuck jobs are always returned regardless of threshold. The test verifies stuck_duration is near-zero, not that the job is excluded. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> Co-authored-by: [email protected] <[email protected]> |
||
|
|
806d402876 |
feat: chat onboarding and routine advisor (#927)
* feat: port NPA psychographic profiling system into IronClaw
Port the complete psychographic profiling system from NPA into IronClaw,
including enriched profile schema, conversational onboarding, profile
evolution, and three-tier prompt augmentation.
Personal onboarding moved from wizard Step 9 to first assistant
interaction per maintainer feedback — the First Contact system prompt
block now instructs the LLM to conduct a natural onboarding conversation
that builds the psychographic profile via memory_write.
Changes:
- Enrich profile.rs with 5 new structs, 9-dimension analysis framework,
custom deserializers for backward compatibility, and rendering methods
- Add conversational onboarding engine with one-step-removed questioning
technique, personality framework, and confidence-scored profile generation
- Add profile evolution with confidence gating, analysis metadata tracking,
and weekly update routine
- Replace thin interaction style injection with three-tier system gated on
confidence > 0.6 and profile recency
- Replace wizard Step 9 with First Contact system prompt block that drives
conversational onboarding during the user's first interaction
- Add autonomy progression to SOUL.md seed and personality framework to
AGENTS.md seed
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: replace chat-based onboarding with bootstrap greeting and workspace seeds
Remove the interactive onboarding_chat.rs engine in favor of a simpler
bootstrap flow: fresh workspaces get a proactive LLM greeting that
naturally profiles the user. Identity files are now seeded from
src/workspace/seeds/ instead of being hardcoded. Also removes the
identity-file write protection (seeds are now managed), adds routine
advisor integration, and includes an e2e trace for bootstrap greeting.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat(safety): sanitize identity file writes via Sanitizer to prevent prompt injection
Identity files (SOUL.md, AGENTS.md, USER.md, IDENTITY.md) are injected into
every system prompt. Rather than hard-blocking writes (which broke onboarding),
scan content through the existing Sanitizer and reject writes with High/Critical
severity injection patterns. Medium/Low warnings are logged but allowed.
Also clarifies AGENTS.md identity file roles (USER.md = user info, IDENTITY.md =
agent identity) and adds IDENTITY.md setup as an explicit bootstrap step.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* docs: update profile_onboarding_completed comment to reflect current wiring
The field is now actively used by the agent loop to suppress BOOTSTRAP.md
injection — remove the stale "not yet wired" TODO.
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(setup): use env_or_override for NEARAI_API_KEY in model fetch config
When the user authenticates via NEAR AI Cloud API key (option 4),
api_key_login() stores the key via set_runtime_env(). But
build_nearai_model_fetch_config() was using std::env::var() which
doesn't check the runtime overlay — so model listing fell back to
session-token auth and re-triggered the interactive NEAR AI
authentication menu.
Switch to env_or_override() which checks both real env vars and the
runtime overlay.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(agent): correct channel/user_id in bootstrap greeting persist call
persist_assistant_response was called with channel="default",
user_id="system" but the assistant thread was created via
get_or_create_assistant_conversation("default", "gateway") which owns
the conversation as user_id="default", channel="gateway". The mismatch
caused ensure_writable_conversation to reject the write with:
WARN Rejected write for unavailable thread id user=system channel=default
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(web): remove all inline event handlers for CSP compliance
The Content-Security-Policy header (added in
|
||
|
|
455f543ba5 |
fix(routines): surface errors when sandbox unavailable for full_job routines (#769)
* feat(db): add list_dispatched_routine_runs to RoutineStore trait Add method to query routine runs with status='running' AND job_id IS NOT NULL, enabling the routine engine to sync completion status from background jobs. Implements for both PostgreSQL and libSQL backends. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(routines): sync dispatched full-job runs with background job status (#697) Full-job routines were immediately marked Ok on dispatch, so failures/completions were never reflected in the routine run record. Now dispatch returns Running status, and a periodic sync checks linked jobs to update the run when the job completes, fails, or is cancelled. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(routines): fail fast when sandbox unavailable at dispatch time (#697) Thread sandbox_available bool from Docker detection through AgentDeps to RoutineEngine. Full-job routines now fail immediately with a clear error message when sandbox is enabled but Docker is not available, instead of dispatching a job that silently fails. Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat(startup): notify user when sandbox unavailable (#697) When sandbox is enabled but Docker is not installed or not running, send a user-visible warning through all channels at startup (with a 2s delay to let channels connect). Previously this was only logged via tracing::warn, invisible to TUI/web users. Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: fix formatting in routine_engine.rs Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(tests): set sandbox_available=true in test rig for full_job traces Test rig doesn't use real Docker — full_job routines execute via trace replay. Setting sandbox_available=true allows the routine_news_digest trace test to dispatch full_job routines as before. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(routines): address review feedback on sync_dispatched_runs (#697) - Sanitize last_reason from job transitions before using in notifications (truncate to 500 chars, strip control characters) - Treat Submitted as in-progress (can still transition to Failed), only Completed and Accepted are terminal success states - Add test for sanitize_summary Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(tests): add missing sandbox_available field to test constructors Staging added sandbox_available to AgentDeps and RoutineEngine::new. Add the missing field/argument in test files to fix CI compilation. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: sanitize job reason in notifications, fix state handling for Submitted/Accepted - Enhance sanitize_summary to strip HTML tags and collapse whitespace, preventing injection via untrusted container job reasons - Use char-boundary-safe truncation to avoid panics on multi-byte strings - Treat Submitted and Accepted as in-progress states (continue polling) rather than terminal success, since they can still transition to Failed - Increase channel-connect delay from 2s to 5s and add debug log for sandbox-unavailable warning delivery Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * Replace sandbox_available bool with SandboxReadiness enum Distinguishes DisabledByConfig from DockerUnavailable so full-job routine errors give actionable guidance instead of a generic message. Co-Authored-By: Claude Opus 4.6 <[email protected]> * ci: re-trigger CI with latest changes Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: add missing owner_id arg to send_notification call Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: update e2e tests to use SandboxReadiness enum Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: [email protected] <[email protected]> |
||
|
|
cac6f4013c |
Add owner-scoped permissions for full-job routines (#1440)
* docs: add comments explaining CLI_ENABLED=false in service templates (#990) Clarify that CLI_ENABLED=false is needed in daemon mode (launchd/systemd) to prevent blocking on stdin when running as a background service. Closes #990 Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * Add owner-scoped full-job routine permissions * Address PR review feedback * Fix owner gate test timing --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
65062f3cc0 |
feat: structured fallback deliverables for failed/stuck jobs (#236)
* feat: structured fallback deliverables for failed/stuck jobs (#221) When a job fails or gets stuck, build a FallbackDeliverable that captures partial results, action statistics, cost, timing, and repair attempts. This replaces opaque error strings with structured data users can act on. - Add FallbackDeliverable, LastAction, ActionStats types in context/fallback.rs - Store fallback in JobContext.metadata["fallback_deliverable"] on failure - Surface fallback in job_status tool output and SSE job_result events - Update mark_failed() and mark_stuck() in worker to build fallback - 8 unit tests covering zero/mixed actions, truncation, timing, serialization Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address review comments on fallback deliverables - Fix doc comment: "200 chars" -> "200 bytes (UTF-8 safe)" since truncate_str operates on byte length, not character count. - Add code comment documenting that SSE fallback_deliverable is currently always None (forward-compatible infrastructure). Co-Authored-By: Claude Opus 4.6 <[email protected]> * refactor: take Option<&FallbackDeliverable> instead of &Option<…> Addresses Gemini review feedback: idiomatic Rust prefers Option<&T> over &Option<T> for borrowed optional values. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: guard against non-object metadata and add fallback test - store_fallback_in_metadata now resets metadata to {} when it's any non-object type (string, array, number), not just null. Prevents panic on index assignment. - Add test_job_status_includes_fallback_deliverable to verify the fallback field is surfaced in job_status tool output. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: use sanitized output in fallback preview + add integration tests Security fix: FallbackDeliverable::build() now uses output_sanitized instead of output_raw, preventing secrets/PII from leaking through the job_status tool and SSE job_result events. Also adds: - test_fallback_uses_sanitized_output: proves raw secrets don't leak - test_store_fallback_in_metadata_roundtrip: full serialize/deserialize - test_store_fallback_handles_non_object_metadata: edge case coverage - test_store_fallback_none_is_noop: None input is safe Addresses serrrfirat review feedback on PR #236. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: harden fallback deliverables against review findings - Truncate failure_reason to 1000 bytes to prevent metadata bloat - Add tracing::warn on fallback serialization failure (was silently discarded) - Fix module/struct docs to cover stuck jobs, remove stale SSE claim - Fix job.rs test to use real FallbackDeliverable field names - Add tests for failure_reason truncation and completed_at=None elapsed time - Fix pre-existing clippy warning in settings.rs (field_reassign_with_default) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address Copilot review findings on fallback deliverables - Fix output_raw/output_sanitized field swap in ActionRecord::succeed() so sanitized data actually goes into the sanitized field (security) - Return None instead of empty Memory when get_memory fails in build_fallback, with tracing::warn for observability - Replace manual elapsed calculation with ctx.elapsed() which already clamps negative durations Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: resolve rebase conflicts and update tests for parameter swap - Add fallback field to SseEvent::JobResult in job_monitor - Fix type annotation in fallback deliverable test - Update test_action_record_succeed_sets_fields for new parameter order - Use create_job_for_user in test (API changed on main) Co-Authored-By: Claude Opus 4.6 <[email protected]> * chore: trigger CI re-check after rebase * fix: fall back to error message for failed action output_preview When the last action is a failed tool call, output_sanitized is None, leaving output_preview empty. Now falls back to the action's error message so users see what went wrong. [skip-regression-check] * ci: add safety comments to test code for no-panics check The CI no-panics grep check cannot distinguish test code inside src/ files from production code. Add // safety: test annotations to .unwrap(), .expect(), and assert!() calls in #[cfg(test)] modules. * fix: clarify succeed() doc and avoid clone in output_preview - Fix doc comment: output_raw is stored as pretty-printed JSON string, not a raw JSON value - Borrow string slice directly in fallback preview to avoid cloning potentially large sanitized outputs before truncation * refactor: reuse floor_char_boundary in truncate_str Replace hand-rolled UTF-8 boundary logic with existing crate::util::floor_char_boundary to reduce duplication. * fix: rename SSE fallback field to fallback_deliverable for consistency The SSE JobResult field was named `fallback` while everywhere else (metadata key, job_status tool) uses `fallback_deliverable`. Align the SSE wire format to avoid forcing clients to handle two names. --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
86ae12747b |
feat: LRU embedding cache for workspace search (#1423)
* feat: LRU embedding cache for workspace search (#165) Add CachedEmbeddingProvider that wraps any EmbeddingProvider with an in-memory LRU cache keyed by SHA-256(model_name + text). This avoids redundant HTTP calls when the same text is embedded multiple times (common during reindexing and repeated searches). - Cache uses HashMap + last_accessed tracking with manual LRU eviction (same pattern as llm::response_cache::CachedProvider) - Lock is never held during HTTP calls to prevent blocking - embed_batch() partitions into hits/misses and only fetches misses - Default 10,000 entries (~58 MB for 1536-dim vectors) - Configurable via EMBEDDING_CACHE_SIZE env var - Workspace.with_embeddings() auto-wraps; with_embeddings_uncached() available for tests Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address review comments on embedding cache - Validate embed_batch return count matches expected miss count - Replace unwrap_or_default() with proper error propagation - Fix batch eviction: run final eviction pass after insert to enforce cap - Fix test: use different-length inputs to verify ordering correctness - Reject EMBEDDING_CACHE_SIZE=0 in config validation (minimum is 1) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: replace .expect() with proper error handling in embed_batch The all-cache-hits early-return path used .expect("all cache hits") which violates the project convention of no .unwrap()/.expect() in production code. Replaced with the same ok_or_else pattern used in the normal path. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: clarify memory sizing docs and use saturating_add for eviction - Update memory comments in embedding_cache.rs, config/embeddings.rs, and workspace/mod.rs to note the ~58 MB figure is payload-only (actual memory is higher due to HashMap/key/allocation overhead) - Use saturating_add(1) instead of + 1 for eviction threshold to prevent overflow if max_entries is usize::MAX Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address Copilot review on embedding cache - Avoid double-clone per miss in embed_batch: move embedding into results, clone only for the cache entry - Evict per-insert instead of after all inserts to keep peak memory bounded during large batches - Clamp max_entries to at least 1 in constructor to prevent unexpected eviction behavior when set to 0 via the public API Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: reduce embedding_cache module visibility to private Types are already re-exported via `pub use`, so the module itself doesn't need to be public. Reduces unnecessary API surface. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address serrrfirat review feedback on embedding cache - Add TODO comment for O(n) LRU eviction scalability - Add thundering herd note at lock release in embed() - Warn when cache max_entries exceeds 100k - Use with_embeddings_uncached() in integration test - Add tests: error_does_not_pollute_cache, embed_batch_empty_input - Update README with cache-aware with_embeddings() docs Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: prevent u32 wrapping in FailThenSucceedMock failure counter fetch_sub(1) wraps to u32::MAX when called past zero, silently breaking the mock for 3+ calls. Switch to load-then-store to avoid the wrapping bug in both embed() and embed_batch(). Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address Copilot and serrrfirat review findings on embedding cache - Switch tokio::sync::Mutex to std::sync::Mutex (lock never held across .await — cheaper synchronous lock) - Extract DEFAULT_EMBEDDING_CACHE_SIZE constant to avoid 10_000 duplication between EmbeddingCacheConfig and EmbeddingsConfig Co-Authored-By: Claude Opus 4.6 <[email protected]> * test: add all-misses batch test for embedding cache Adds embed_batch_all_misses test covering the case where every text in a batch is a cache miss — fulfilling the commitment from serrrfirat's review. Co-Authored-By: Claude Opus 4.6 <[email protected]> * chore: trigger CI re-check after rebase * fix: use raw [u8;32] cache keys and pre-allocate HashMap capacity Address Copilot review findings: - cache_key() now returns [u8; 32] instead of hex String, avoiding a 64-byte allocation per lookup - HashMap::with_capacity(max_entries) avoids incremental reallocation - Fix pre-existing staging compilation error in cli/routines.rs (missing max_tool_rounds/use_tools fields) [skip-regression-check] * fix: make cache accessors sync and update doc for [u8;32] keys Address Copilot review: - len(), is_empty(), clear() are now sync since they only take a std::sync::Mutex lock with no .await points - Update cache_size doc comment to reflect [u8;32] keys instead of String keys [skip-regression-check] * fix: remove clone_on_copy for [u8; 32] cache keys [skip-regression-check] * ci: add safety comments to test code for no-panics check The CI no-panics grep check cannot distinguish test code inside src/ files from production code. Add // safety: test annotations to .unwrap(), .expect(), and assert!() calls in #[cfg(test)] modules. * fix: correct cache doc and demote hit/miss logs to trace - Fix misleading "String keys" in memory comment (cache uses [u8; 32]) - Demote per-request hit/miss logs from debug to trace to reduce noise on hot paths (batch summary stays at trace too) * docs: add missing Arc import in workspace README example * perf: batch eviction in embed_batch to avoid O(n×m) cost Replace per-insert evict_lru call with a single evict_k_oldest pass that computes eviction count upfront and removes the k oldest entries in one O(n) scan. Avoids O(n×m) HashMap iterations while holding the mutex during batch inserts. * fix: cap batch cache inserts at max_entries and use O(n) selection - evict_k_oldest now uses select_nth_unstable_by_key for O(n) average partial selection instead of O(n log n) full sort - embed_batch caps cached entries at max_entries when misses exceed capacity, preventing the cache from growing unbounded - Added test: batch_exceeding_capacity_respects_max_entries * fix: flatten test assert for fmt compatibility Shorten assert message to fit single line so cargo fmt doesn't split the safety annotation onto a separate line. * fix: address review feedback and improve embedding cache (takeover #235) - Fix merge conflict: add missing allow_always field in PendingApproval - Thread EmbeddingCacheConfig through CLI memory commands so they respect EMBEDDING_CACHE_SIZE instead of silently using default (fixes #235 review) - Cap HashMap pre-allocation at min(max_entries, 1024) to avoid upfront memory waste at large cache sizes - Fix FailThenSucceedMock race: replace load+store with atomic fetch_update - Remove noisy '// safety: test' comments (40+ lines of diff noise) - Fix collapsed lines from comment removal - Simplify redundant Ok(...collect()?) to just collect() Co-Authored-By: ztsalexey <[email protected]> Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(embedding-cache): skip eviction on concurrent duplicate insert When the lock is released for the HTTP call, another caller may insert the same key. Re-check under lock and just update the existing entry without evicting, avoiding unnecessary cache churn under concurrency. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: ztsalexey <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: ztsalexey <[email protected]> |
||
|
|
09e1c97a27 |
fix(approval): make "always" auto-approve work for credentialed HTTP requests (#1257)
The HTTP tool returned `ApprovalRequirement::Always` for requests with credentials, but `Always` is hardcoded to ignore the session auto-approve set. This meant users who clicked "always" were re-prompted on every subsequent HTTP call — the UI offered "always" but the backend ignored it. Two fixes: 1. HTTP credentialed requests now return `UnlessAutoApproved` instead of `Always`, so the session auto-approve set is respected. 2. `StatusUpdate::ApprovalNeeded` now carries `allow_always: bool`. All channel UIs (Telegram, Slack, Signal, REPL, Web) conditionally hide the "always" option when a tool truly requires per-invocation approval (`ApprovalRequirement::Always`, e.g. destructive shell commands). Also boxes `PendingApproval` in `AgenticLoopResult::NeedApproval` to fix a pre-existing clippy `large_enum_variant` warning. Regression tests included (test_credentialed_requests_respect_auto_approve, test_allow_always_matches_approval_requirement) but CI heuristic cannot detect them in cross-fork PR diffs. [skip-regression-check] Co-authored-by: Tyler <[email protected]> |
||
|
|
3dcccc1e64 |
feat(self-repair): wire stuck_threshold, store, and builder (#712)
* feat(self-repair): wire stuck_threshold, store, and builder (#647) Wire the previously dead-code fields in DefaultSelfRepair: - stuck_threshold: detect_stuck_jobs() now filters by duration, only reporting jobs stuck longer than the configured threshold - with_store(): wired in agent_loop.rs from AgentDeps.store for tool failure tracking via Database trait - with_builder(): wired from register_builder_tool() return value through AppComponents and AgentDeps for automatic tool rebuilding - tools: passed alongside builder for hot-reload logging Remove all #[allow(dead_code)] annotations. Add regression tests for threshold-based filtering (both above and below threshold). Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: add missing `builder` field to AgentDeps in gateway workflow harness After rebase onto staging, AgentDeps gained a `builder` field for self-repair tool rebuilding. The gateway workflow test harness was missing this field, causing CI compilation failure. Co-Authored-By: Claude Opus 4.6 <[email protected]> * ci: retrigger CI * fix: force CI refresh after path_routing_tests dedup * test: add E2E test for stuck job repair and tool rebuild cycle Tests the full self-repair flow requested in review: 1. Job transitions Pending -> InProgress -> Stuck 2. detect_stuck_jobs() finds it (zero threshold) 3. repair_stuck_job() recovers it back to InProgress 4. A broken tool is repaired via MockBuilder 5. Verify builder was invoked and repair succeeded Uses a MockBuilder (impl SoftwareBuilder) that returns successful BuildResult without requiring an LLM or filesystem. Uses libsql test database for the store (increment_repair_attempts, mark_tool_repaired). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(self-repair): measure stuck_duration from Stuck transition, not started_at - Use ctx.transitions to find the most recent Stuck transition timestamp instead of ctx.started_at (which reflects job start, not stuck time) - Fix StuckJob.last_activity to use stuck transition timestamp - Remove misleading "hot-reloaded into registry" log - Remove stray "// ci fix" comment in memory.rs - Add regression test: backdated started_at must not inflate stuck_duration Co-Authored-By: Claude Opus 4.6 <[email protected]> * ci: re-trigger CI with latest changes Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: add type annotation to Ok(()) in test to resolve E0282 Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
ec04354c6b |
fix: address valid review comments from PR #1359 (#1380)
- Cache discovery_schema() with OnceLock for routine tools (fixes #1361, #1371) - Early-return on empty event cache before allocating Vec (fixes #1369) - Extract batch concurrent count query helper to reduce duplication - Fix ROUTINE_OK sentinel substring matching - Migrate crate::safety import to ironclaw_safety per project convention Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
14abd60917 |
fix: full_job routine runs stay running until linked job completion (#1374)
* fix: full_job routine runs stay running until linked job completion (#1317) Previously, execute_full_job() returned RunStatus::Ok immediately after dispatching the job, causing routine runs to be marked as completed before the linked worker job had actually finished. This meant failure notifications were never sent and max_concurrent guardrails stopped applying once the run was prematurely finalized. Changes: - execute_full_job() now returns RunStatus::Running instead of Ok - execute_routine() skips finalization for Running status (leaves run open) - New sync_dispatched_runs() polls on each cron tick, checks linked job state, and finalizes runs when jobs reach terminal states - New list_dispatched_routine_runs() DB method on both backends - Deferred notifications are sent when the run is actually finalized - consecutive_failures is preserved (not reset) while outcome is unknown Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: address PR review feedback (watcher predicate, running_count safety) - FullJobWatcher: use is_parallel_blocking() instead of is_active() so the watcher exits when a job reaches Completed (not terminal but finished executing). Fixes infinite-poll for routine jobs. - Remove running_count decrement from sync_dispatched_runs() — in normal flow execute_routine() handles it; sync only runs for crash recovery where the counter is already 0. - Update PR description to match actual FullJobWatcher behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: sync only at startup to prevent double-completion race - Move sync_dispatched_runs() out of cron loop into startup-only path. During normal operation FullJobWatcher handles finalization inline; running sync on every tick would race with the watcher. - Update complete_dispatched_run() to properly advance runtime fields (last_run_at, next_fire_at, run_count) for crash recovery — in that scenario execute_routine() never reached its runtime update. - Fix stale doc comment on complete_dispatched_run(). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: use boot_time filter for safe periodic sync of orphaned runs - Add boot_time field to RoutineEngine, set to Utc::now() at creation. - sync_dispatched_runs() now filters runs by started_at < boot_time, so it only processes orphans from a previous process — never races with FullJobWatcher instances from the current process. - Move sync back into the cron loop (safe with boot_time filter) and run it BEFORE check_cron_triggers to avoid picking up freshly dispatched runs. - Fix doc comments to match actual behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
6831bb4d7b |
fix: full_job routine concurrency tracks linked job lifetime (#1372)
* fix: add FullJobWatcher to track full_job lifecycle for concurrency (#1318) full_job routines previously bypassed max_concurrent and global concurrency limits because execute_full_job() returned RunStatus::Ok immediately after dispatch. This meant running_count was decremented and the routine_run row was finalized before the actual job completed. Introduce FullJobWatcher struct that polls store.get_job() every 5s until the linked job reaches a non-active state, then maps the final JobState to RunStatus. execute_full_job now creates and awaits the watcher, keeping both the DB-level running row and the in-memory running_count elevated for the full job duration. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * test: full_job concurrency regression tests (issue #1318) Add two integration tests verifying full_job routine concurrency: 1. full_job_max_concurrent_blocks_second_fire_while_first_active: Inserts a Running routine_run (simulating an in-flight full_job) and verifies fire_manual returns MaxConcurrent error for max_concurrent=1. 2. global_concurrency_counts_live_full_job_runs: Elevates running_count to simulate a live full_job holding the global slot, verifies check_cron_triggers skips due routines, then releases the slot and verifies the routine fires. Also makes running_count_for_test() unconditionally public so integration tests (separate crate) can access it. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * style: fmt and clippy fixes for full_job concurrency tests Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: address PR review feedback on FullJobWatcher - Add #[doc(hidden)] to running_count_for_test() to hide from public API - Derive MAX_POLLS from POLL_INTERVAL to keep constants coupled - Check job state before first sleep to finalize promptly for fast jobs - Update execute_full_job doc comment to reflect blocking behavior Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
20202700db |
Fix duplicate LLM responses for matched event routines (#1275)
* fix: consume matched event routine messages * style: run rustfmt for event routine fix * fix: preserve preprocessing for routine-triggered messages * fix: match routines against rewritten input * refactor: narrow check_event_triggers API and simplify routine_engine_slot Address Copilot review feedback: - Change check_event_triggers to accept (user_id, channel, content) instead of &IncomingMessage, eliminating the need to clone the full message (including attachments) when hooks rewrite content. - Remove routine_trigger_message and the Cow<IncomingMessage> indirection; the event-trigger check now inlines the is_internal + UserInput guard and passes the post-hook content string directly. - Make routine_engine_slot non-optional since Agent::new() always initializes it. Removes the redundant Option wrapper and simplifies accessor/setter methods. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
428303af11 |
Redesign routine create requests for LLMs (#1147)
* Redesign routine create requests for LLMs * Fix panic-check false positives in routine tests * Tighten routine schema requirements * Tighten routine schema tests * Mark test assertions safe for CI scan * Align test assertions with panic scan * Polish routine schema metadata * Simplify routine test assertions * Improve tool discovery guidance * Clarify lightweight routine delivery prompts * Fix routine delivery target defaults |
||
|
|
4675e9618c |
Fix Telegram auto-verify flow and routing (#1273)
* Fix Telegram auto-verify flow and routing * Fix CI formatting and clippy follow-ups * Simplify Telegram waiting state update * Fix notification fallback scopes * Fix message metadata routing and zh-CN copy |
||
|
|
c6128f4e41 |
fix: misleading UI message (#1265)
* fix: misleading UI message * review fixes * review fixes * enhance test |
||
|
|
b50eddfe0a |
Merge branch 'main' into fix/resolve-conflicts
Resolved merge conflicts in 5 files: 1. src/agent/job_monitor.rs - Used is_internal flag approach (HEAD) for safe internal message marking. Removed metadata-based approach which could be spoofed by external channels. 2. src/agent/agent_loop.rs - Used is_internal check (HEAD) for routing internal messages, consistent with security model where is_internal field cannot be spoofed. 3. src/agent/dispatcher.rs - Included notify_metadata in job context (main), needed for job routing through JobMonitorRoute. 4. src/setup/wizard.rs - Added build_nearai_model_fetch_config() function (main) for model selection during setup. 5. src/tools/builtin/job.rs - Used both comments from HEAD (clarifying notify_channel and notify_user logic) while removing metadata field from JobMonitorRoute (consistent with job_monitor.rs). All conflicts resolved with security-first approach: use is_internal boolean field for internal message marking (cannot be spoofed), while passing routing metadata through context. Co-Authored-By: Claude Haiku 4.5 <[email protected]> |
||
|
|
878a67cdb6 |
Refactor owner scope across channels and fix default routing fallback (#1151)
* refactor: add explicit owner scope across channels * fix: tighten routine owner target routing * fix: address owner scope review feedback * Fix owner-scope onboarding and event trigger isolation * Tighten routing fallback and wizard owner validation * fix: address owner-scope follow-up review * fix: tighten owner-scope follow-up details * fix: import Channel trait in telegram test * fix: normalize http webhook sender ids * fix: address remaining owner-scope review issues * fix: reconcile config rebase fallout * fix: reconcile extension manager rebase drift * fix: address current copilot review regressions * fix: restore clippy matrix after rebase |
||
|
|
ea0fa7c2c5 |
Merge pull request #1196 from nearai/staging-promote/e74214dc-23104855330
chore: promote staging to staging-promote/97b11ffd-23104193988 (2026-03-15 06:18 UTC) |
||
|
|
218e8778b9 |
Merge pull request #1192 from nearai/staging-promote/15ab156d-23103553911
chore: promote staging to staging-promote/c79754df-23099429381 (2026-03-15 04:45 UTC) |
||
|
|
4890e73a34 |
Merge pull request #1132 from nearai/staging-promote/e805ec61-23059634819
chore: promote staging to main (2026-03-13 16:09 UTC) |
||
|
|
f618166ad8 |
feat(heartbeat): fire_at time-of-day scheduling with IANA timezone (#1029)
* feat(heartbeat): fire_at time-of-day scheduling with IANA timezone support - HEARTBEAT_FIRE_AT=HH:MM — fire heartbeat at a specific time of day instead of on a rolling interval; format is 24h HH:MM (e.g. "14:00") - HEARTBEAT_TIMEZONE=Region/City — IANA timezone name for fire_at (e.g. "Pacific/Auckland", "America/New_York"). Defaults to UTC. - When fire_at is set, interval_secs is ignored - Config also readable from settings.toml [heartbeat] section Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * feat(heartbeat): wire fire_at + timezone into HeartbeatConfig runner Missed file from heartbeat scheduling commit. HeartbeatConfig struct in agent/heartbeat.rs now carries fire_at: Option<NaiveTime> and timezone: Tz so the runner can schedule against a fixed time of day. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix: add chrono-tz dependency for heartbeat fire_at timezone support The chrono-tz crate was used in the heartbeat fire_at commits but its Cargo.toml entry was lost during rebase conflict resolution. Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: rustfmt fix for chained method call Co-Authored-By: Claude Opus 4.6 <[email protected]> * test(heartbeat): add fire_at scheduling and DST safety tests - test_default_config_has_no_fire_at: interval-based default unchanged - test_with_fire_at_builder: builder sets time and timezone - test_duration_until_next_fire_is_bounded: result always 1s–24h - test_duration_until_next_fire_dst_timezone_no_panic: US Eastern DST - test_resolved_tz_defaults_to_utc: missing timezone falls back to UTC - test_resolved_tz_parses_iana: IANA string resolves correctly Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(heartbeat): restore drift-free interval, add settings.json fallback for fire_at - Interval path: restore tokio::time::interval (drift-free) instead of tokio::time::sleep which drifts by loop body execution time - fire_at config: fall back to settings.heartbeat.fire_at when HEARTBEAT_FIRE_AT env var is not set, consistent with other settings Addresses Gemini Code Assist review feedback. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: IronClaw <[email protected]> Co-authored-by: Claude Sonnet 4.6 <[email protected]> |
||
|
|
bde0b77a86 |
fix(security): prevent metadata spoofing of internal job monitor flag (#1195)
The `__internal_job_monitor` metadata key that bypassed the entire agent pipeline (hooks, safety checks, LLM processing) was spoofable by external channels — WASM channel plugins could inject arbitrary metadata including this key, causing attacker-controlled content to be forwarded directly as assistant responses. Replace the metadata-based check with a dedicated `is_internal` field on `IncomingMessage` that can only be set via `into_internal()` by trusted in-process code. Both the field and setter are `pub(crate)` to prevent external crates from spoofing the flag. Also remove `notify_metadata` forwarding (the monitor only needs channel/user/thread routing) and the unused `__job_monitor_job_id` metadata key. Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
e0f393bf04 |
fix(auth): avoid false success and block chat during pending auth (#1111)
* fix(auth): avoid false success and block chat while auth pending * fix(web): clear stale auth UI on failure and add setup regression test * Update src/agent/thread_ops.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * fix(fmt): place auth activation comment on separate line --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Illia Polosukhin <[email protected]> |
||
|
|
c4e098d4e3 |
Fix subagent monitor events being treated as user input (#1173)
* Fix subagent monitor routing to avoid LLM re-entry * Update yanked uds_windows dependency in lockfile |
||
|
|
62d16e69ac |
fix(mcp): handle 400 auth errors, clear auth mode after OAuth, trim tokens (#1158)
* fix(mcp): handle 400 auth errors, clear auth mode after OAuth, trim tokens Three bugs prevented MCP server authentication (e.g. GitHub MCP) from working correctly: 1. **400 treated as auth-required**: GitHub's MCP endpoint returns 400 "Authorization header is badly formatted" instead of 401 when auth is missing. Broadened auth detection in activate_mcp, send_request, and discover_via_401 to also match 400+authorization errors. 2. **Auth mode not cleared after OAuth callback**: The OAuth callback handler and setup submit handler did not call clear_auth_mode(), leaving pending_auth on the thread. The next user message was intercepted as a token instead of triggering an LLM turn. 3. **Token trimming**: Tokens with leading/trailing whitespace or newlines produced malformed Authorization headers. Now trimmed before storage (configure) and before use (build_request_headers). Adds E2E tests with a mock MCP server (JSON-RPC + OAuth discovery + DCR + token exchange) covering install -> activate -> OAuth callback -> LLM turn lifecycle, plus a GitHub-style 400 error variant. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(mcp): add TTL to PendingAuth and clear auth mode on all failure paths Auth mode (pending_auth on a Thread) had no timeout and several code paths that failed to clear it, causing user messages to be swallowed indefinitely. This adds defense-in-depth: - Add created_at + 5-minute TTL to PendingAuth; auto-clear on next message if expired (safety net for edge cases like user closing browser mid-OAuth) - Clear auth mode on OAuth callback failure paths (unknown/consumed state, expired flow) - Move clear_auth_mode before configure() match in setup_submit so it runs on failure too (addresses Copilot review feedback) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(ci): exclude test hunks from unwrap/assert pre-commit check The pre-commit safety script only excluded files in tests/ but not #[cfg(test)] mod tests blocks inside src/ files. Use the git diff @@ hunk header context (which includes the enclosing function name) to detect and skip test hunks. Also removes unnecessary // safety: comments from test assertions. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: restore formatting in test assertions The replace_all edit that removed // safety: comments collapsed newlines. Restore proper line breaks. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: address Copilot review - tighten pre-commit filter, document TTL sync - pre-commit-safety.sh: only exclude `mod tests` hunks (not `fn test_*`) to avoid hiding unwrap/assert in production functions like test_server() - session.rs: extract AUTH_MODE_TTL_SECS constant and add doc comment linking to OAUTH_FLOW_EXPIRY to prevent silent drift [skip-regression-check] Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(mcp): return error on expired auth input, clear auth on all OAuth paths - When auth mode TTL expires and the user sends a message (possibly a pasted token), return an explicit "expired, please retry" response instead of forwarding the content to the LLM/history - Add clear_auth_mode() to all early-return paths in oauth_callback_handler (provider error, missing state/code, no extension manager) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
716629809c |
fix: eliminate panic paths in production code (#1184)
* fix: eliminate panic paths in production code and document infallible operations PolicyRule::new() now returns Result instead of panicking on invalid caller-supplied regex. CreateJobTool returns ToolError when job_manager is unconfigured instead of panicking. Remaining infallible unwrap/expect calls (hardcoded regexes, compile-time constants, guarded accesses) are annotated with SAFETY comments. Where possible, unwraps are replaced with safer patterns: split_last(), if-let, match-destructure, and reusing peek() values. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: use inline lowercase safety comments to match CI pattern The no-panics CI check greps for '// safety:' (lowercase, inline) to suppress false positives. Switch from block SAFETY comments to inline safety comments on the .unwrap() lines. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * test: add regression tests for panic-path fixes - PolicyRule::new returns Err on invalid regex (not panic) - CreateJobTool::execute_sandbox returns ToolError when job_manager is None Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: add inline // safety: comments on all infallible unwrap/expect lines The CI no-panics check requires '// safety:' on the same line as unwrap()/expect() to suppress false positives. Move safety annotations from block comments to inline comments on every infallible production unwrap/expect across all touched files. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * chore: trigger CI with skip-regression-check label [skip-regression-check] Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * refactor: remove redundant block-level SAFETY comments Each unwrap/expect now carries its own inline // safety: annotation, making the standalone block comments above them redundant. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
c79754df28 |
Fix schema-guided tool parameter coercion (#1143)
* Fix schema-guided tool parameter coercion * Fix CI checks for coercion regression tests * Finish panic-scan annotations * Avoid redundant worker param preparation * Keep panic-scan annotations rustfmt-stable * Handle nullable WASM schema review feedback * Address param coercion review notes |
||
|
|
e291d3b6f1 |
feat(routines): human-readable cron schedule summaries in web UI (#1154)
* feat(routines): render cron triggers as human-readable summaries * test(routines): annotate multiline cron assertions for no-panics CI * test(routines): avoid multiline assert lint false positives |
||
|
|
994a0b194f |
fix: N+1 query pattern in event trigger loop (routine_engine) (#1163)
* fix: N+1 query pattern in event trigger loop (routine_engine) * fix: linter |
||
|
|
5f0ed66a6b |
perf(routines): avoid full message history clone each tool iteration (#1172)
* perf(routines): bound tool-loop history snapshot clone cost * test(ci): annotate snapshot assertions for no-panics matcher * test(ci): keep no-panics suppression on single-line assertion * test(ci): keep snapshot tail assert single-line for no-panics * Update src/agent/routine_engine.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore(deps): bump yanked uds_windows in lockfile for cargo-deny --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> |
||
|
|
3f2796b745 |
fix: Non-transactional multi-step context updates between metadata/to… (#1161)
* fix: Non-transactional multi-step context updates between metadata/token setup and DB * fix: code style |
||
|
|
757d24bd90 |
feat(web): add follow-up suggestion chips and ghost text (#1156)
* feat(web): add follow-up suggestion chips and ghost text to chat UI The LLM now always generates 1-3 follow-up command suggestions via <suggestions> tags in its response. These are extracted server-side, broadcast as SSE events, and rendered as clickable chips above the chat input. The first suggestion also appears as ghost text in the input field (Tab to accept). Includes debug logging for LLM responses in the agentic loop and removes noisy NEAR AI status logging. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: resolve deferred review items from PR #1156 [skip-regression-check] - Remove literal backslashes from raw string prompt (reasoning.rs) - Make WASM channels skip Suggestions status (no-op instead of empty callback) - Add !e.shiftKey guard to Tab-to-accept ghost text handler - Cap extracted suggestions at 3 and trim whitespace-only entries - Extract suggestions in approval-resume path (prevents tag leaking) - Remove stale .has-ghost class during showSuggestionChips reset Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
e805ec61aa |
fix: 5 critical/high-priority bugs (auth bypass, relay failures, unbounded recursion, context growth) (#1083)
* fix: address 5 critical and high-priority bugs from issue tracker - #1033: reject webhook requests when secret is cleared at runtime via update_secret(None), preventing auth bypass through SIGHUP hot-swap - #908: reset consecutive_failures counter on successful SSE stream reconnection in relay channel, so circuit breaker counts truly consecutive failures - #975: add depth limit (16) to validate_tool_schema() to prevent stack overflow on deeply nested schemas - #974: add depth limit (8) to resolve_nested() to prevent stack overflow on deeply nested capabilities wrappers - #826: truncate oversized tool outputs (>8KB) in routine lightweight loop to prevent unbounded context growth across iterations Each fix includes a regression test. Closes #1033, #908, #975, #974, #826 Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: 5 more high-priority bugs (routine cache, job signals, input limits) - #1077: recompute next_fire_at when re-enabling cron routines via web toggle, mirroring CLI behavior so cron ticker picks them up - #1076: refresh event trigger cache after web toggle/delete operations so event/system_event routines reflect changes immediately - #892: remove Stuck from check_signals() stop-states in JobDelegate since Stuck is recoverable (Stuck -> InProgress via self-repair) - #976: truncate oversized description strings in CapabilitiesFile to 4KB to prevent memory abuse from malicious capabilities files - #977: drop oversized parameters schema JSON (>64KB) in CapabilitiesFile to prevent unbounded memory growth Each fix includes regression tests where applicable. Closes #1077, #1076, #892, #976, #977 Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: prevent ReDoS in event trigger regex patterns - #825: use RegexBuilder with 64KB size limit when compiling user-supplied event trigger patterns, both at creation time (routine tool) and at cache refresh (routine engine) Note: Rust's regex crate already guarantees O(n) matching, so the size limit prevents excessive memory use during compilation rather than catastrophic backtracking at match time. Closes #825 Co-Authored-By: Claude Opus 4.6 <[email protected]> * Harden HTTP SSRF IP filtering * Apply rustfmt after staging merge --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
7a9cbb3b50 |
fix(routines): run cron checks immediately on ticker startup (#1066)
* fix(routines): run cron check immediately at ticker startup * test/ci: add routine_engine test and fix style lint drift |
||
|
|
c94ecf19db |
feat: add Slack approval buttons for tool execution in DMs (#796)
* feat: add channel-relay integration for Slack via external relay service - Add RelayChannel and RelayClient for connecting to channel-relay SSE streams - Add RelayConfig with env-based configuration (CHANNEL_RELAY_URL, CHANNEL_RELAY_API_KEY) - Add channel-relay extension lifecycle: install, OAuth auth, activate with hot-add - Add proxy message sending through channel-relay for Slack chat.postMessage - Add extension registry entry for Slack relay with OAuth auth hint - Add relay integration test with mock SSE server - Wire relay channel into app startup with reconnect on stored credentials - Add AuthRequired extension error variant for cleaner auth flow detection [skip-regression-check] * chore: apply cargo fmt * fix: remove remaining Telegram test references in relay channel * fix: address PR #790 review feedback — parser handle leak, CSRF, circuit breaker - Fix parser handle leak on reconnect by sharing Arc<RwLock> instead of creating a local copy in start() (shutdown now aborts the correct task) - Add CSRF state nonce to OAuth flow: generate in auth_channel_relay, validate in slack_relay_oauth_callback_handler, one-time use - Remove dead proxy_slack method, update integration test to use proxy_provider - Add reconnect circuit breaker (max_consecutive_failures, default 50) - Fix stale docs (Telegram refs), extract event_types constants * fix: double backoff in reconnect loop and UTF-8 chunk-boundary corruption - Remove second sleep+backoff in list_connections error branch to prevent O(4^n) backoff growth (was sleeping and doubling twice per iteration) - Buffer raw bytes in SSE parser instead of per-chunk String::from_utf8_lossy to prevent U+FFFD corruption when multi-byte chars span chunk boundaries * feat: add Slack approval buttons for tool execution in DMs Send Block Kit Approve/Deny buttons via relay when a tool requires approval in a DM context. Auto-deny approval-requiring tools in shared channels to prevent prompt injection and stuck threads. * fix: address PR #796 review — use PreflightOutcome::Rejected, add tests - Auto-deny in non-DM relay channels now uses PreflightOutcome::Rejected instead of manually pushing to reason_ctx.messages, so the post-flight handler properly records the error in the turn - Add regression tests for relay auto-deny decision logic - Remove test_clean.db artifact * feat: restore Block Kit approval buttons in send_status The send_status implementation was accidentally dropped during the staging merge. Restores Approve/Deny Block Kit buttons for DM tool approval, with required sender_id validation, payload size docs, and 4 regression tests. Also removes test_clean.db. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: apply rustfmt formatting to dispatcher test code Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
6bbf87ba3a |
feat(routines): enable tool access in lightweight routine execution (#257) (#730)
* Rebase onto staging
* fix(routines): prevent autonomy-escalation in lightweight routines
- Add ROUTINE_TOOL_DENYLIST to block routine_create/update/delete/fire
and restart from being callable by lightweight routines
- Deduplicate sentinel logic by reusing handle_text_response() in the
no-tools path
- Filter tool definitions sent to LLM to only include callable tools,
avoiding wasted tokens on tools that would be rejected
|
||
|
|
006c15e79c | style(agent): remove unnecessary Worker re-export (#923) | ||
|
|
8a26cfae73 |
fix(mcp): open MCP OAuth in same browser as gateway (#951)
* fix(mcp): use gateway callback for MCP OAuth so auth opens in same browser When MCP OAuth is triggered from the web gateway, the auth URL was being opened via `open::that()` which launches the OS default browser instead of the browser already running the gateway UI. This changes the MCP OAuth flow to use the same gateway callback pattern as WASM extensions: in gateway mode, the auth URL is returned to the frontend via SSE and opened with `window.open()`, keeping the user in the same browser. Also adds RFC 8707 `resource` parameter support to the gateway token exchange path, scoping issued tokens to the correct MCP server. Closes #299 Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: cargo fmt Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(mcp): persist DCR client_id in gateway OAuth callback for token refresh The gateway callback handler stored access and refresh tokens but not the DCR client_id. When the token expired, refresh failed with "No client ID found" because get_client_id() could not find it in secrets. Adds client_id_secret_name to PendingOAuthFlow so the gateway callback handler persists the client_id alongside the tokens, matching the behavior of the CLI flow in authorize_mcp_server(). Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(mcp): return AuthRequired on 401 so activate triggers OAuth flow activate_mcp() returned ActivationFailed for all errors including 401 auth responses, so the activate handler never triggered the OAuth flow. Now 401/auth errors return AuthRequired, which the handler detects and redirects to the OAuth flow — matching the WASM extension pattern. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(mcp): fix gateway OAuth flow, approval cards, and auto-activation - Add explicit gateway_mode flag on ExtensionManager (set at startup by web gateway) so MCP OAuth returns auth URLs to the frontend instead of calling open::that() on the server machine. - Auto-activate extensions after successful OAuth callback so the UI transitions from "Activate" to "Active" without a second click. - Send ApprovalNeeded status (not generic "Awaiting approval") from thread_ops.rs for all three NeedApproval paths so the web UI shows approval cards for deferred tool calls. - Remove duplicate ApprovalNeeded send from agent_loop.rs (thread_ops.rs is now the canonical sender). - Skip approval for tool_auth in gateway mode since it only returns a URL. - Revert fragile active-server detection heuristic from system prompt. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review findings - Use Release/Acquire ordering for gateway_mode AtomicBool instead of Relaxed to ensure visibility across threads. - Report activation failure as error in OAuth callback SSE event instead of silently falling back to the success message. - Fix EnvGuard::drop to remove env var when original was unset. - Replace hardcoded /tmp/ path with std::env::temp_dir() in test helper. Co-Authored-By: Claude Opus 4.6 <[email protected]> * test(mcp): add E2E trace test for MCP extension lifecycle with mock server Add a full MCP extension lifecycle E2E test that exercises: - Turn 1: tool_search → tool_install → text (extension discovery and install) - Token injection + activate (simulating OAuth completion) - Turn 2: MCP tool calls (notion-search → notion-fetch → text) Includes a mock MCP server (tests/support/mock_mcp_server.rs) with OAuth discovery, DCR, token exchange, and JSON-RPC endpoints. The mock server validates Bearer auth and serves pre-configured tool responses. Also adds inject_registry_entry() to ExtensionManager for test use and exposes extension_manager from TestRig. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review findings (round 2) - Only fall back to manual token entry on AuthNotSupported, propagate real errors from auth_mcp_build_url() instead of masking them - Use mcp:-prefixed provider string in PendingOAuthFlow for consistency with CLI MCP auth token storage - Only persist client_id_secret_name for DCR flows (not pre-configured OAuth) - Fix gateway_callback_redirect_uri to use /oauth/callback path - Bypass exchange proxy when flow has RFC 8707 resource parameter - Remove client_id double-prefix in oauth callback handler - Remove weak tests that didn't exercise production logic - Add clarifying comments for exchange_oauth_code delegation Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: keep OAuth success independent of activation, fix wait_for_responses scoping - OAuth success is now reported accurately even when auto-activation fails (tokens are already stored, so auth succeeded) - E2E test waits for turn1_count + 1 responses to ensure turn-2 behavior is actually observed Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
2094d6e30d |
fix(agent): block thread_id-based context pollution across users (#760)
* fix(agent): prevent forged thread UUID context/write contamination * fix(agent): close thread_id race and reject forged UUID hydration * fix(ci): satisfy clippy and fmt checks after rebase |
||
|
|
19d9562b4f |
feat(extensions): unify auth and configure into single entrypoint (#677)
* feat(extensions): unify auth and configure into single entrypoint Refactors the extension lifecycle to eliminate the divergence between chat and gateway paths that caused Telegram setup via chat to fail (missing webhook secret auto-generation, no token validation). Key changes: - Rename save_setup_secrets() → configure(): single entrypoint for providing secrets to any extension (WasmChannel, WasmTool, MCP). Validates, stores, auto-generates, and activates. - Add configure_token(): convenience wrapper for single-token callers (chat auth card, WebSocket, agent auth mode). - Refactor auth() to pure status check: remove token parameter, delete token-storing branches from auth_mcp/auth_wasm_tool, rename auth_wasm_channel → auth_wasm_channel_status. - Add ConfigureResult/MissingSecret types for structured responses. - Replace hardcoded Telegram token validation with generic validation_endpoint from capabilities.json. - Update all callers (9 files) to use the new interface. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: use ValidationFailed error variant instead of string matching Replace brittle msg.contains("Invalid token") checks with a proper ExtensionError::ValidationFailed variant. configure() now returns this variant for token validation failures, and callers match on it directly instead of parsing error message strings. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: address review — SSRF protection, error typing, missing-secret selection, WS auth 1. SSRF: call validate_fetch_url() before validation_endpoint HTTP request 2. Transport errors map to ExtensionError::Other (not ValidationFailed) 3. configure_token() picks first *missing* secret, not first non-optional 4. WebSocket error path re-emits AuthRequired on ValidationFailed Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * test: add regression tests for extension lifecycle refactoring - test_configure_token_picks_first_missing_secret: verifies multi-secret channels can be configured one secret at a time (commit ce106f4) - test_auth_is_read_only_for_wasm_channel: verifies auth() has no side effects and doesn't store secrets (commit 47f8eb6) - test_validation_failed_is_distinct_error_variant: verifies the typed error variant can be pattern-matched (commit a318161) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address review comments — activation dispatch, dead code, caps consolidation - Fix configure() fallthrough bug: dispatch activation by ExtensionKind instead of unconditionally calling activate_wasm_channel() for all non-WasmTool types (MCP servers and channel relays now use their correct activation methods) - Remove dead MissingSecret struct and missing_secrets field (never populated, flagged by reviewer) - Consolidate capabilities file parsing in configure(): parse once and reuse for allowed names, validation_endpoint, and auto-generation - Fix auth() doc comment: note MCP OAuth side effects - Fix stale save_setup_secrets reference in server.rs comment - Add regression test for activation dispatch bug Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
369741fc60 |
Add generic host-verified /webhook/tools/{tool} ingress (#757)
* Add generic host-verified webhook ingress for tools * Stabilize trace E2E test rig and approval behavior * Fix webhook security issues from review feedback - Reject tools without webhook_capability() (was unauthenticated RCE) - Remove secret-in-query-string fallback (leak via logs/referrers) - Require approval for event_emit tool (escalation via routine triggers) - Simplify header_value() (HeaderMap already case-insensitive) - Redact internal errors from webhook HTTP responses - Remove unused hmac_timestamp_tolerance_secs field - Add regression test for tool without webhook capability [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <[email protected]> * Harden webhook ingress: require auth mechanism, body limit layer, health check - Reject webhook capabilities that declare no auth mechanism (empty WebhookCapability would previously allow unauthenticated access) - Add DefaultBodyLimit layer to reject oversized payloads before buffering - Health check (GET) now verifies tool has webhook_capability(), not just existence - Add regression tests for all three fixes [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <[email protected]> * Fix auto_approve_tools inconsistency between dispatcher and thread_ops dispatcher.rs skips all approval checks (including Always) when auto_approve_tools is true, but thread_ops.rs still required approval for Always tools. This caused deferred tool calls to unexpectedly halt in test rigs and auto-approve configurations. Match dispatcher behavior: short-circuit all approval when auto_approve_tools is enabled. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
873322f2fb |
fix: staging CI review issues (batch 1) (#883)
* fix: address staging-ci-review issues (batch 1) - #811: Fix unreachable error handling in worker — restructure .await? to explicit match on nested Result so token budget errors are properly logged and marked as failed - #813: Combine metadata + token budget into single update_context() call to prevent concurrent worker observing partial state - #814: Persist max_tokens and total_tokens_used to both PostgreSQL and libSQL backends — add V12 migration, update save_job/get_job - #815: Cap user-supplied max_tokens at configured max_tokens_per_job to prevent budget bypass via metadata injection - #869: Release locks before async I/O in webhook handler (http.rs) and SIGHUP handler (main.rs) to prevent blocking concurrent requests Fixes: #811, #813, #814, #815, #869 Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR #883 review feedback - Fix min(user_val, 0) bug: guard for unlimited config (max_tokens_per_job == 0) - Remove duplicate columns from libSQL base SCHEMA (v12 migration is sole source) - Use get_i64() helper for consistency in libsql/jobs.rs - Add regression tests for scheduler token budget capping Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
1f5b582c5f |
fix: agent logging (#888)
* fix: optimize agent logging to reduce DataDog bill * fix: log permanent repair failures as ERROR not WARN RepairResult::Failed is permanent failure requiring attention (ERROR level) not a temporary/retryable condition (WARN level). [skip-regression-check] Co-Authored-By: Claude Haiku 4.5 <[email protected]> * security: remove user message content from trace logs Never log user message content at any log level (includes TRACE). Log only safe metadata: content length, message ID, image count. This prevents accidental exposure of sensitive user data in logs even at the most verbose logging level. [skip-regression-check] Co-Authored-By: Claude Haiku 4.5 <[email protected]> * security: move LLM response body logging to TRACE level Response bodies can contain user-generated content, tool outputs, and leaked secrets. Moving to TRACE (not enabled in production) prevents exposure in DEBUG logs. Status log remains at DEBUG. [skip-regression-check] Co-Authored-By: Claude Haiku 4.5 <[email protected]> * refactor: simplify URL sanitization using url::Url API Use set_query, set_fragment, set_username, set_password methods instead of manual string reconstruction. Cleaner, handles edge cases, eliminates port branching complexity. [skip-regression-check] Co-Authored-By: Claude Haiku 4.5 <[email protected]> * test: add comprehensive unit tests for sanitize_url_for_logging Add 9 test cases covering: - URL with query parameters - URL with credentials (user:pass@host) - URL with fragment - URL with port - URL with all components combined - Malformed URL fallback behavior - Short strings (pass-through) - Non-URL-like strings - Path preservation Tests verify that sanitization correctly removes sensitive components while preserving safe components like host, port, and path. Co-Authored-By: Claude Haiku 4.5 <[email protected]> * fix: libsql per-migration logs should be DEBUG, not TRACE Individual migration logs are now visible with standard debug logging (RUST_LOG=ironclaw=debug), improving debuggability when troubleshooting migration issues. Summary log remains at INFO level. Fixes behavioral change that made it harder to identify which specific migration ran or failed without enabling full TRACE logging. [skip-regression-check] --------- Co-authored-by: Claude Haiku 4.5 <[email protected]> |