Commit Graph
449 Commits
Author SHA1 Message Date
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]>
2026-03-18 20:51:21 -07:00
c8ee55ed19 feat(testing): add FaultInjector framework for StubLlm (#1233)
* feat(testing): add FaultInjector framework for StubLlm (#1220)

Adds a configurable fault injection framework for testing retry, failover,
and circuit breaker behavior. The FaultInjector attaches to StubLlm and
provides per-call control over failure type, timing, and sequencing.

Components:
- FaultType: maps to LlmError variants (RequestFailed, RateLimited,
  AuthFailed, InvalidResponse, IoError, ContextLengthExceeded, SessionExpired)
- FaultAction: Succeed, Fail(FaultType), Delay(Duration)
- FaultMode: SequenceOnce (play then succeed), SequenceLoop (repeat forever),
  Random (seeded xorshift64 PRNG for reproducibility)
- FaultInjector: thread-safe (AtomicU32 counter + Mutex RNG)

Integration:
- StubLlm gains optional fault_injector field via with_fault_injector()
- When set, takes precedence over should_fail/error_kind
- Backward compatible: existing StubLlm usage unchanged

Closes #1220

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* refactor(testing): address review feedback on FaultInjector

- Remove redundant .abs() in random fault comparison
- Extract check_faults() helper to DRY up StubLlm methods
- Guard xorshift seed=0 (fixed point) by mapping to 1
- Add StubLlm integration test (stub_llm_fault_injector_sequence)
- Remove dead seed field from FaultMode::Random
- Move pub mod fault_injection to top of mod.rs
- Add Debug impl for FaultInjector
- Add empty_sequence_always_succeeds test
- Add random_seed_zero_does_not_always_fail test

* fix(testing): address #1233 review -- seed-0 bug, reset(), Debug derive

- Store seed in FaultMode::Random so reset() can re-init the RNG
- Add reset() method for test reproducibility (re-seeds RNG, zeros counter)
- Strengthen seed=0 regression test to 100 iterations with stricter assertion
- Add reset_restores_random_rng_from_stored_seed test
- Debug impl and empty_sequence test were already present from prior commit

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* ci: re-trigger CI with latest changes

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* ci: trigger new run with skip-regression-check label

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(testing): address PR #1233 review -- error_rate validation and edge cases

- Validate error_rate is in 0.0..=1.0 and not NaN (panics on invalid input)
- Fix error_rate==1.0 edge case: use <= instead of < so 1.0 always fails
- Add regression tests for error_rate validation (NaN, negative, >1.0)
- Add tests for error_rate boundary values (0.0 never fails, 1.0 always fails)
- Add delay action test using tokio::time::pause() for deterministic timing

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-18 20:38:29 -07:00
b7a1edf346 fix: remove debug_assert guards that panic on valid error paths (#1385)
* fix: remove debug_assert guards that panic on valid error paths (#1312)

Two debug_assert! calls added in #1312 fire on expected runtime error
paths (not programmer bugs), turning graceful error returns into panics
in debug/test builds:

- state.rs: Completed→Cancelled is a user-facing error handled by
  transition_to() returning Err — not a bug
- execute.rs: empty tool_name from malformed LLM output is handled by
  ToolError::NotFound — not a bug

Removes both asserts; keeps the circuit-breaker assert (genuinely guards
a caller invariant).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix: tighten empty tool name test to assert ToolError::NotFound variant

Address review feedback: assert the specific error variant instead of
just is_err() so the regression test actually enforces the expected
error path.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* style: cargo fmt

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-18 17:02:09 -07:00
4566181f40 feat(gateway): unified settings page with subtabs (#1191)
* feat(gateway): full settings page polish with all tiers

- Backend: add ActiveConfigSnapshot to expose resolved LLM backend,
  model, and enabled channels via /api/gateway/status
- Add missing Agent settings (daily cost cap, actions/hour, local tools)
- Add Sandbox, Routines, Safety, Skills, and Search setting groups
- Settings import/export (JSON download + file upload)
- Active env defaults shown as placeholders in Inference settings
- Styled confirmation modals replace window.confirm() for remove actions
- Global restart banner persists across settings subtab switches
- Client-side validation with min/max constraints on number inputs
- Accessibility: aria-label on inputs, role=status on save indicators
- Settings search filters rows across current subtab
- Smooth CSS transitions for conditional field visibility (showWhen)
- Tunnel settings in Channels subtab
- Mobile responsive settings layout at 768px breakpoint
- i18n keys for toolbar, search, and import/export in en + zh-CN

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* feat(gateway): polish settings page and remove registered tools debug section

Remove the "Registered Tools" table from the extensions tab (debug info
not useful to end users), clean up associated CSS/i18n/JS. Additional
settings page UI polish: extension card state styling, layout refinements.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(gateway): address PR review feedback [skip-regression-check]

- Use refreshCurrentSettingsTab() in SSE event handlers to reduce duplication
- Remove unused formatGroupName/formatSettingLabel helpers
- Use i18n keys for MCP Configure/Reconfigure buttons
- Add data-i18n-placeholder to settings search input
- Remove data-i18n from confirm modal button (set dynamically by showConfirmModal)
- Fix cargo fmt in main.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(e2e): update tests for unified settings tab layout [skip-regression-check]

- Update TABS list: replace extensions/skills with settings
- Add settings_subtab/settings_subpanel selectors to helpers
- Update test_connection, test_skills, test_extensions, test_wasm_lifecycle
  to navigate via Settings > subtab instead of top-level tabs
- Move MCP card tests to use go_to_mcp() helper (MCP is now a separate subtab)
- Remove tools table tests and mock_ext_apis tools= parameter
- Fix CSP violation: replace inline onclick on confirm modal cancel button

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(gateway): address second round of PR review feedback [skip-regression-check]

- Use I18n.t() for MCP empty state, export/import toasts, confirm modal
- Fix CLI channel card using wrong channel key ('repl' -> 'cli')
- Fix settings search counting hidden rows as visible
- Add aria-label i18n for settings search input
- Add common.loadFailed i18n key (en + zh-CN)
- Update E2E tests: WASM channel tests use Channels subtab,
  remove tests use custom confirm modal instead of window.confirm

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(e2e): fix WASM channel card selector and skills remove confirm [skip-regression-check]

- WASM channel tests: filter by display name to avoid matching built-in
  channel cards in the Channels subtab
- Skills remove test: click confirm modal button instead of using
  window.confirm (skill removal now uses custom confirm modal)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(gateway): address third round of PR review feedback [skip-regression-check]

- approval_needed SSE: refresh any active settings subtab, not just
  Extensions — approvals can surface from Channels/MCP setup flows too
- renderCardsSkeleton: remove nested .extensions-list wrapper that
  caused skeleton cards to render constrained inside grid cells

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(e2e): fix auth_completed reload test race condition [skip-regression-check]

Use expect_response to deterministically wait for the /api/extensions
reload triggered by handleAuthCompleted → refreshCurrentSettingsTab,
instead of a fixed 600ms sleep that was too short under CI load.
Also remove stale /api/extensions/tools route handler.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(e2e): debug auth_completed reload test with function counter [skip-regression-check]

Inject a counter wrapper around refreshCurrentSettingsTab to verify it's
actually called, and wait for the async fetch to complete before
asserting the reload count.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* feat(gateway): localize all settings labels, descriptions, and channel cards [skip-regression-check]

Move 120+ hardcoded strings in settings definitions (INFERENCE_SETTINGS,
AGENT_SETTINGS, NETWORKING_SETTINGS) and channel card labels to i18n
keys. Render functions now resolve labels via I18n.t() so the settings
page translates when switching locales.

Covers: group titles, setting labels/descriptions, built-in channel
names/descriptions, and the "No settings found" empty state.

Both en.js and zh-CN.js updated with all new cfg.* and channels.* keys.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(gateway): localize remaining hardcoded UI strings [skip-regression-check]

- Fix export error toast using wrong i18n key (importFailed → exportFailed)
- Replace "Failed to load settings:" with I18n.t('common.loadFailed')
- Localize renderBuiltinChannelCard: "Built-in", "Active", "Inactive"
- Localize settings placeholders: "env: ", "env default", "use env default"
- Localize "✓ Saved" indicator
- Add new i18n keys to both en.js and zh-CN.js

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(gateway): confirm modal a11y, Esc/click-outside, search guard [skip-regression-check]

- Add role="dialog", aria-modal="true", aria-labelledby to confirm modal
- Focus confirm button when modal opens
- Close modal on Escape key or overlay click
- Skip settings search on non-settings panels (Extensions/MCP/Skills/Channels)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(gateway): boolean tri-state, search reset on subtab switch, stale model suggestions [skip-regression-check]

Address PR review feedback:
- Boolean settings now use a tri-state select (env default / On / Off)
  instead of a checkbox, matching the pattern used by other select settings
  and allowing users to revert to the env default
- Clear search input when switching settings subtabs so stale filters
  don't carry over to the new panel
- Always assign model suggestions (even empty array) so stale IDs from a
  previous successful /v1/models fetch don't persist when the endpoint
  later returns empty

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix(gateway): auth_completed handler, bedrock_cross_region select, integer-only number inputs [skip-regression-check]

Address PR review feedback:
- auth_completed SSE listener now delegates to handleAuthCompleted(data)
  instead of inlining logic with a bare closeConfigureModal() call, so
  only the matching extension's modal is dismissed
- bedrock_cross_region changed from free text to select with the four
  valid values (us/eu/apac/global), matching backend validation
- Number settings now use step=1 and parseInt() instead of parseFloat(),
  preventing fractional values that the backend (u32/u64) would reject

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
2026-03-18 16:18:29 -07:00
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]>
2026-03-18 15:34:05 -07:00
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]>
2026-03-18 15:33:57 -07:00
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]>
2026-03-18 12:29:58 -07:00
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]>
2026-03-18 12:29:35 -07:00
07e6e30ee3 fix: add debug_assert invariant guards to critical code paths (#1312)
* fix: add debug_assert invariant guards to critical code paths (closes #1215)

Add three debug_assert! calls to catch impossible-in-correct-code states
early in debug builds without affecting release performance:

- execute_tool_with_safety: assert tool_name is non-empty at entry
- JobContext::transition_to: assert state machine transition is valid
- CircuitBreakerProvider::record_success: assert circuit is not Open
  (check_allowed() must gate all calls before record_success())

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* test: add regression test for empty tool name invariant guard

Covers the debug_assert!(!tool_name.is_empty()) added in execute_tool_with_safety.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
2026-03-18 11:34:11 -07:00
OctopusandGitHub 2d0b195321 feat: upgrade MiniMax default model to M2.7 (#1357)
* feat: upgrade MiniMax default model to M2.7

- Add MiniMax-M2.7 and MiniMax-M2.7-highspeed to model list
- Set MiniMax-M2.7 as default model
- Keep all previous models as alternatives
- Update related tests

* fix: use canonical model name in test per review

Use MiniMax-M2.7-highspeed (canonical casing) in the reasoning
models test for consistency with the documentation and provider
configuration.

[skip-regression-check]
2026-03-18 11:34:05 -07:00
NigeandGitHub 0be591028a fix(mcp): retry after missing session id errors (#1355) 2026-03-18 11:33:51 -07:00
NigeandGitHub 33a2dd2c78 fix(telegram): preserve polling after secret-blocked updates (#1353)
* fix(telegram): preserve polling after secret-blocked updates

* style(telegram): simplify polling leak-scan guard

* style(telegram): satisfy clippy for poll leak guard
2026-03-18 11:33:45 -07:00
NigeGitHubgemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
bedc71ebdc fix(llm): cap retry-after delays (#1351)
* fix(llm): cap retry-after delays

* Update src/llm/retry.rs

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-18 11:33:38 -07:00
NigeandGitHub e9b0823db9 fix(setup): remove nonexistent webhook secret command hint (#1349)
* fix(setup): remove nonexistent webhook secret command hint

* test(setup): cover webhook secret onboarding hint
2026-03-18 11:33:31 -07:00
Henry ParkandGitHub 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
2026-03-18 09:04:00 -07:00
5c56032b88 fix: Rate limiter returns retry after None instead of a duration (#1269)
* fix: Rate limiter returns retry after None instead of a duration

linter fix

* review fixes

* fix: rate limiter returns None for retry_after duration

Add regression test to src/llm/retry.rs that verifies RateLimited errors
always have a fallback duration (never None) due to the 60-second fallback
applied in all rate limit error creation sites (nearai_chat.rs,
anthropic_oauth.rs, embeddings.rs).

The production code fix adds `.or(Some(Duration::from_secs(60)))` to ensure
the error message never displays "retry after None" to the user.

[skip-regression-check]

Co-Authored-By: Claude Haiku 4.5 <[email protected]>

---------

Co-authored-by: Claude Haiku 4.5 <[email protected]>
2026-03-16 20:51:49 -07:00
Henry ParkandGitHub 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
2026-03-16 20:19:43 -07:00
Nick PismenkovandGitHub 9065527761 fix: jobs limit (#1274) 2026-03-16 19:46:00 -07:00
Nick PismenkovandGitHub c6128f4e41 fix: misleading UI message (#1265)
* fix: misleading UI message

* review fixes

* review fixes

* enhance test
2026-03-16 16:13:02 -07:00
Henry ParkandGitHub ed0ed40dae ci: isolate heavy integration tests (#1266)
* fix staging CI coverage regressions

* ci: cover all e2e scenarios in staging

* ci: restrict staging PR checks and fix webhook assertions

* ci: keep code style checks on PRs

* ci: preserve e2e PR coverage

* test: stabilize staging e2e coverage

* fix: propagate postgres tls builder errors

* ci: isolate heavy integration tests

* fix: clean up heavy integration CI follow-up
2026-03-16 16:10:20 -07:00
Henry ParkandGitHub 026beb00f2 fix: cover staging CI all-features and routine batch regressions (#1256)
* fix staging CI coverage regressions

* ci: cover all e2e scenarios in staging

* ci: restrict staging PR checks and fix webhook assertions

* ci: keep code style checks on PRs

* ci: preserve e2e PR coverage

* test: stabilize staging e2e coverage

* fix: propagate postgres tls builder errors
2026-03-16 15:06:31 -07:00
Nick PismenkovandClaude Haiku 4.5 fc18064be9 fix: resolve merge conflict fallout and missing config fields
- Remove duplicate build_nearai_model_fetch_config() definition from setup/wizard.rs
  (function already exists in llm/models.rs and is imported)
- Add missing cheap_model and smart_routing_cascade fields to LlmConfig
  initializer in build_nearai_model_fetch_config() (llm/models.rs)
- Pass request_timeout_secs to create_registry_provider() call
  (llm/mod.rs:432)

All clippy checks pass with zero warnings (--no-default-features --features libsql).

Co-Authored-By: Claude Haiku 4.5 <[email protected]>
2026-03-16 14:53:27 -07:00
Nick PismenkovandClaude Haiku 4.5 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]>
2026-03-16 14:47:07 -07:00
Henry ParkandGitHub 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
2026-03-16 13:31:03 -07:00
Henry ParkandGitHub 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)
2026-03-16 13:28:17 -07:00
Henry ParkandGitHub f2587e1f44 Merge pull request #1193 from nearai/staging-promote/97b11ffd-23104193988
chore: promote staging to staging-promote/15ab156d-23103553911 (2026-03-15 05:30 UTC)
2026-03-16 13:27:34 -07:00
Henry ParkandGitHub 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)
2026-03-16 13:26:42 -07:00
Nick PismenkovandGitHub 971b4c2ef4 fix: web/CLI routine mutations do not refresh live event trigger cache (#1255)
* fix: web/CLI routine mutations do not refresh live event trigger cache

* review fix
2026-03-16 13:16:35 -07:00
Henry ParkandGitHub 4890e73a34 Merge pull request #1132 from nearai/staging-promote/e805ec61-23059634819
chore: promote staging to main (2026-03-13 16:09 UTC)
2026-03-16 09:22:58 -07:00
Henry ParkandGitHub 63a23550d6 feat: verify telegram owner during hot activation (#1157)
* feat(telegram): verify owner during hot activation

* fix(ci): satisfy no-panics and clippy checks

* fix(web): preserve relay activation status

* fix(telegram): redact setup errors

* fix(telegram): require owner verification code

* fix(telegram): allow code in conversational dm
2026-03-16 08:07:45 -07:00
de214c23e0 feat: add LLM_CHEAP_MODEL for generic smart routing across all backends (#1081)
* feat: add LLM_CHEAP_MODEL for generic smart routing across all backends

Add generic cheap model support that works with any LLM backend, not just
NearAI. New env vars: LLM_CHEAP_MODEL (cheap model for any backend) and
SMART_ROUTING_CASCADE (top-level cascade flag).

Resolution order: LLM_CHEAP_MODEL > NEARAI_CHEAP_MODEL (backward compat).
Registry-based providers (OpenAI, Anthropic, Groq, etc.) clone their
RegistryProviderConfig with the cheap model swapped in. Bedrock returns
an explicit error (not yet supported). All error paths use ok_or_else
with proper LlmError variants -- no unwrap/expect in production code.

* refactor: address Gemini review — remove unnecessary async, extract cheap_model_name()

- Remove async from create_cheap_provider_for_backend() and
  create_cheap_llm_provider() — neither contains .await calls
- Extract duplicated cheap model resolution logic into
  LlmConfig::cheap_model_name() helper method (DRY)
- Revert tests from tokio::test async back to sync #[test]
- Add test_cheap_model_name_resolution() unit test for the helper

---------

Co-authored-by: SMKRV <[email protected]>
2026-03-16 08:06:51 +00:00
ReidandGitHub a357972908 feat(config): unify config resolution with Settings fallback (Phase 2, #1119) (#1203)
Unify config resolution with Settings fallback (Phase 2)
2026-03-16 08:01:51 +00:00
0245c0f9e9 feat(orchestrator): read ORCHESTRATOR_PORT env var for configurable API port (#1113)
* feat(orchestrator): read ORCHESTRATOR_PORT env var for configurable API port

The orchestrator internal API port was hardcoded to 50051 in two places
(ContainerJobConfig and OrchestratorApi::start call), making it impossible
to run multiple IronClaw instances on the same host — the second instance
fails with "Address already in use".

NETWORK_SECURITY.md already documents ORCHESTRATOR_PORT as configurable,
and ContainerJobConfig.orchestrator_port is propagated to worker containers
via IRONCLAW_ORCHESTRATOR_URL, but the env var was never actually read.

Extract resolve_orchestrator_port() that reads ORCHESTRATOR_PORT and falls
back to 50051. Includes tests for valid, invalid, and out-of-range values.

* test: add ENV_LOCK mutex for env-var test serialization

Address Gemini review: add std::sync::Mutex to serialize env var access
across test threads. Keep unsafe blocks — required in Rust edition 2024
where std::env::set_var/remove_var are unsafe functions.

---------

Co-authored-by: SMKRV <[email protected]>
2026-03-16 07:58:48 +00:00
877f117096 feat(transcription): add Chat Completions API provider for audio transcription (#1130)
* feat(transcription): add Chat Completions API provider for audio transcription

The existing transcription pipeline only supports the OpenAI Whisper API
(/v1/audio/transcriptions with multipart upload). Providers like OpenRouter
expose audio transcription through the Chat Completions API instead, using
base64-encoded audio in the `input_audio` content type.

Add `ChatCompletionsTranscriptionProvider` that sends audio as base64 in
a chat completion request and extracts the transcript from the response.
Compatible with OpenRouter, OpenAI GPT-4o-audio, and any provider that
supports audio input via Chat Completions.

Config changes:
- TRANSCRIPTION_PROVIDER=chat_completions selects the new provider
- TRANSCRIPTION_API_KEY overrides provider-specific keys
- LLM_API_KEY used as fallback for chat_completions provider
- Default model per provider (whisper-1 for openai, gemini-2.0-flash for
  chat_completions)

* style: address review feedback — formatting, idiomatic patterns

- Fix rustfmt formatting for provider constructor chain
- Use or_else for resolve_api_key priority chain (Gemini review)
- Use trim_end_matches('/') instead of while loop (Gemini review)

---------

Co-authored-by: SMKRV <[email protected]>
2026-03-16 07:57:58 +00:00
0c31da46e7 feat(sandbox): add retry logic for transient container failures (#1232)
* feat(sandbox): add retry logic for transient container failures (#1224)

SandboxManager::execute_with_policy() had no retry logic. Transient Docker
errors (daemon temporarily unavailable, container creation race conditions,
container start failures) caused immediate job failure.

Adds up to 2 retries (3 total attempts) with exponential backoff (2s, 4s)
for transient error types only:
- DockerNotAvailable
- ContainerCreationFailed
- ContainerStartFailed

Non-transient errors (Timeout, ExecutionFailed, NetworkBlocked, Config)
are returned immediately without retry.

Container cleanup on retry is safe: ContainerRunner::execute() always
force-removes the container before returning.

Closes #1224

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* style: fix formatting

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-16 07:53:45 +00:00
596d17f04b fix(jobs): make completed->completed transition idempotent to prevent race errors (#1068)
* fix(jobs): make completed->completed transition idempotent to prevent race errors

Both execution_loop and the worker wrapper in execute() can race to call
mark_completed(). Previously the second call hit "Cannot transition from
completed to completed" and errored the job despite successful completion.

This narrowly allows only the Completed->Completed self-transition as
idempotent (early return with debug log, no duplicate history entry).
All other self-transitions remain rejected to preserve state machine
strictness.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* style: fix assert! formatting in idempotent completion test

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
2026-03-16 07:53:06 +00:00
9e41b8acea fix(llm): persist refreshed Anthropic OAuth token after Keychain re-read (#1213)
* fix(llm): persist refreshed Anthropic OAuth token after Keychain re-read (#1136)

The Anthropic OAuth provider stored its token as an immutable SecretString.
When a 401 triggered a Keychain re-read, the fresh token was used for a
single retry but never persisted — every subsequent request reused the
expired original token, causing repeated auth failures.

Changes:
- Wrap token in RwLock<SecretString> so it can be updated after refresh
- Persist refreshed token via update_token() on successful retry
- Add 500ms delay before Keychain re-read to give Claude Code time to
  complete its async token refresh write (reduces race window)
- Add regression test verifying token updates persist across reads

Closes #1136

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* style: fix formatting

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-16 07:52:33 +00:00
58a3eb1366 fix(worker): prevent orphaned tool_results and fix parallel merging (#1069)
* fix(worker): prevent orphaned tool_results and fix parallel merging

Two fixes for tool result handling in the Worker:

1. Preserve reasoning text from select_tools() in the RespondResult
   content field so it appears in the assistant_with_tool_calls message
   pushed by execute_tool_calls. Without this, the LLM's reasoning
   context was lost when using the select_tools path.

2. Merge consecutive tool_result messages into a single User message
   in rig_adapter's convert_messages(). When parallel tools execute,
   each produces a separate ChatMessage with role: Tool. Without
   merging, these become consecutive User messages which Anthropic
   rejects. Now consecutive tool results are merged into one User
   message with multiple ToolResult content items.

Includes regression tests for both fixes.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(worker): use find_map for first non-empty reasoning extraction

The previous code only checked the first ToolSelection's reasoning,
missing cases where the first selection has empty reasoning but
subsequent ones do not. Switch to find_map to get the first non-empty
reasoning across all selections.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
2026-03-16 07:51:36 +00:00
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]>
2026-03-16 07:46:59 +00:00
NigeandGitHub 3e0e35d1bc docs(extensions): document relay manager init order (#928) 2026-03-16 07:46:00 +00:00
ZeroTrustandGitHub 1b59eb6b39 feat: Reuse Codex CLI OAuth tokens for ChatGPT backend LLM calls (#693)
* feat: add Codex auth.json token reuse for LLM authentication

When LLM_USE_CODEX_AUTH=true, IronClaw reads the Codex CLI's auth.json
(default ~/.codex/auth.json) and extracts the API key or OAuth access
token. This lets IronClaw piggyback on a Codex login without
implementing its own OAuth flow.

New env vars:
  - LLM_USE_CODEX_AUTH: enable Codex auth fallback (default: false)
  - CODEX_AUTH_PATH: override path to auth.json

* fix: handle ChatGPT auth mode correctly

Switch base_url to chatgpt.com/backend-api/codex when auth.json
contains ChatGPT OAuth tokens. The access_token is a JWT that only
works against the private ChatGPT backend, not the public OpenAI API.

Refactored codex_auth.rs to return CodexCredentials (token +
is_chatgpt_mode) instead of just a string key.

* fix: Codex auth takes highest priority over secrets store

When LLM_USE_CODEX_AUTH=true, Codex credentials are now loaded before
checking env vars or the secrets store overlay. Previously the secrets
store key (injected during onboarding) would shadow the Codex token.

* feat: Responses API provider for ChatGPT backend

- New CodexChatGptProvider speaks the Responses API protocol
- Auto-detects model from /models endpoint (gpt-4o -> gpt-5.2-codex)
- Adds store=false (required by ChatGPT backend)
- Error handling with timeout for HTTP 400 responses
- Message format translation: Chat Completions -> Responses API
- SSE response parsing for text, tool calls, and usage stats
- 7 unit tests for message conversion and SSE parsing

* fix: SSE parser uses item_id instead of call_id for tool call deltas

The Responses API sends function_call_arguments.delta events with
item_id (e.g. fc_...) not call_id (e.g. call_...). The parser now
keys pending tool calls by item_id from output_item.added and
tracks call_id separately for result matching.

* fix: strip empty string values from tool call arguments

gpt-5.2-codex fills optional tool parameters with empty strings
(e.g. timestamp: ""), which IronClaw's tool validation rejects.
Strip them before passing to tool execution.

* fix: prevent apiKey mode fallback to ChatGPT token

When auth_mode is explicitly 'apiKey' but the key is missing/empty,
do not fall through to check for a ChatGPT access_token. This prevents
returning credentials with is_chatgpt_mode: true and routing to the
wrong LLM provider.

* refactor: reuse single reqwest::Client across model discovery and LLM calls

Create Client once in with_auto_model, pass &Client to
fetch_default_model, and move it into the provider struct.
Eliminates the redundant Client::new() that wasted a connection pool.

* fix: bump client_version to 1.0.0 to unlock gpt-5.3-codex and gpt-5.4

The /models endpoint gates newer models behind client_version.
Version 0.1.0 only returns up to gpt-5.2-codex, while 1.0.0+
also returns gpt-5.3-codex and gpt-5.4.

* feat: user-configured LLM_MODEL takes priority over auto-detection

Fetch the full model list from /models endpoint. If LLM_MODEL is set,
validate it against the supported list and warn with available models
if not found. If LLM_MODEL is not set, auto-detect the highest-priority
model. Also bumps client_version to 1.0.0 to unlock gpt-5.3/5.4.

* fix: add 10s timeout to model discovery HTTP request

Prevents startup from blocking indefinitely if chatgpt.com
is slow or unreachable. Uses reqwest per-request timeout.

* docs: add private API warning for ChatGPT backend endpoint

The chatgpt.com/backend-api/codex endpoint is private and
undocumented. Add warning in module docs and a runtime log
on first use to inform users of potential ToS implications.

* feat: implement OAuth 401 token refresh for Codex ChatGPT provider

On HTTP 401, if a refresh_token is available, the provider now
automatically refreshes the access token via auth.openai.com/oauth/token
(same protocol as Codex CLI) and retries the request once. Refreshed
tokens are persisted back to auth.json.

Changes:
- codex_auth: read refresh_token, add refresh_access_token() and
  persist_refreshed_tokens()
- codex_chatgpt: RwLock for api_key, 401 detection + retry in
  send_request, send_http_request helper
- config/llm: thread refresh_token/auth_path through RegistryProviderConfig
- llm/mod: pass refresh params to with_auto_model

* refactor: lazy model detection via OnceCell, remove block_in_place

Model is no longer resolved during provider construction. Instead,
resolve_model() uses tokio::sync::OnceCell to lazily fetch from
/models on the first LLM call. This eliminates the block_in_place
+ block_on workaround in create_codex_chatgpt_from_registry.

- with_auto_model (async) -> with_lazy_model (sync constructor)
- resolve_model() added with OnceCell-based lazy init
- build_request_body takes model as parameter
- model_name() returns resolved or configured_model as fallback

* feat: support multimodal content (images) in Codex ChatGPT provider

message_to_input_items now checks content_parts for user messages.
ContentPart::Text maps to input_text and ContentPart::ImageUrl maps
to input_image, matching the Responses API format used by Codex CLI.
Falls back to plain text when content_parts is empty.

Also updates client_version to 0.111.0 for /models endpoint.

Adds test: test_message_conversion_user_with_image

* refactor: move codex_auth module from src/ to src/llm/

codex_auth is only used by the LLM layer (codex_chatgpt provider
and config/llm). Moving it under src/llm/ reflects its actual scope.

- Remove pub mod codex_auth from lib.rs
- Add pub mod codex_auth to llm/mod.rs
- Update imports: super::codex_auth, crate::llm::codex_auth

* Fix codex provider style issues

* Use SecretString throughout codex auth refresh flow

* Use SecretString for codex access tokens

* Reuse provider client for codex token refresh

* Stream Codex SSE responses incrementally

* Fix Windows clippy and SQLite test linkage

* Trigger checks after regression skip label

* Tighten codex auth module handling
2026-03-16 07:43:45 +00:00
Nick PismenkovandGitHub 81724cad93 fix: Telegram bot token validation fails intermittently (HTTP 404) (#1166)
* fix: Telegram bot token validation fails intermittently (HTTP 404)

* fix: code style

* fix

* fix

* fix

* review fix
2026-03-15 22:06:33 -07:00
e81fb7e5cb refactor(setup): extract init logic from wizard into owning modules (#1210)
* refactor(setup): extract init logic from wizard into owning modules

Move database, LLM model discovery, and secrets initialization logic
out of the setup wizard and into their owning modules, following the
CLAUDE.md principle that module-specific initialization must live in
the owning module as a public factory function.

Database (src/db/mod.rs, src/config/database.rs):
- Add DatabaseConfig::from_postgres_url() and from_libsql_path()
- Add connect_without_migrations() for connectivity testing
- Add validate_postgres() returning structured PgDiagnostic results

LLM (src/llm/models.rs — new file):
- Extract 8 model-fetching functions from wizard.rs (~380 lines)
- fetch_anthropic_models, fetch_openai_models, fetch_ollama_models,
  fetch_openai_compatible_models, build_nearai_model_fetch_config,
  and OpenAI sorting/filtering helpers

Secrets (src/secrets/mod.rs):
- Add resolve_master_key() unifying env var + keychain resolution
- Add crypto_from_hex() convenience wrapper

Wizard restructuring (src/setup/wizard.rs):
- Replace cfg-gated db_pool/db_backend fields with generic
  db: Option<Arc<dyn Database>> + db_handles: Option<DatabaseHandles>
- Delete 6 backend-specific methods (reconnect_postgres/libsql,
  test_database_connection_postgres/libsql, run_migrations_postgres/
  libsql, create_postgres/libsql_secrets_store)
- Simplify persist_settings, try_load_existing_settings,
  persist_session_to_db, init_secrets_context to backend-agnostic
  implementations using the new module factories
- Eliminate all references to deadpool_postgres, PoolConfig,
  LibSqlBackend, Store::from_pool, refinery::embed_migrations

Net: -878 lines from wizard, +395 lines in owning modules, +378 new.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* test(settings): add wizard re-run regression tests

Add 10 tests covering settings preservation during wizard re-runs:
- provider_only rerun preserves channels/embeddings/heartbeat
- channels_only rerun preserves provider/model/embeddings
- quick mode rerun preserves prior channels and heartbeat
- full rerun same provider preserves model through merge
- full rerun different provider clears model through merge
- incremental persist doesn't clobber prior steps
- switching DB backend allows fresh connection settings
- merge preserves true booleans when overlay has default false
- embeddings survive rerun that skips step 5

These cover the scenarios where re-running the wizard would
previously risk resetting models, providers, or channel settings.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* refactor(setup): eliminate cfg(feature) gates from wizard methods

Replace compile-time #[cfg(feature)] dispatch in the wizard with
runtime dispatch via DatabaseBackend enum and cfg!() macro constants.

- Merge step_database_postgres + step_database_libsql into step_database
  using runtime backend selection
- Rewrite auto_setup_database without feature gates
- Remove cfg(feature = "postgres") from mask_password_in_url (pure fn)
- Remove cfg(feature = "postgres") from test_mask_password_in_url

Only one internal #[cfg(feature = "postgres")] remains: guarding the
call to db::validate_postgres() which is itself feature-gated.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* refactor(db): fold PG validation into connect_without_migrations

Move PostgreSQL prerequisite validation (version >= 15, pgvector)
from the wizard into connect_without_migrations() in the db module.
The validation now returns DatabaseError directly with user-facing
messages, eliminating the PgDiagnostic enum and the last
#[cfg(feature)] gate from the wizard.

The wizard's test_database_connection() is now a 5-line method that
calls the db module factory and stores the result.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix: address PR review comments [skip-regression-check]

- Use .as_ref().map() to avoid partial move of db_config.libsql_path
  (gemini-code-assist)
- Default to available backend when DATABASE_BACKEND is invalid, not
  unconditionally to Postgres which may not be compiled (Copilot)
- Match DatabaseBackend::Postgres explicitly instead of _ => wildcard
  in connect_with_handles, connect_without_migrations, and
  create_secrets_store to avoid silently routing LibSql configs through
  the Postgres path when libsql feature is disabled (Copilot)
- Upgrade Ollama connection failure log from info to warn with the
  base URL for better visibility in wizard UX (Copilot)
- Clarify crypto_from_hex doc: SecretsCrypto validates key length,
  not hex encoding (Copilot)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix: address zmanian's PR review feedback [skip-regression-check]

- Update src/setup/README.md to reflect Arc<dyn Database> flow
- Remove stale "Test PostgreSQL connection" doc comment
- Replace unwrap_or(0) in validate_postgres with descriptive error
- Add NearAiConfig::for_model_discovery() constructor
- Narrow pub to pub(crate) for internal model helpers

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix: address Copilot review comments (quick-mode postgres gate, empty env vars) [skip-regression-check]

- Gate DATABASE_URL auto-detection on POSTGRES_AVAILABLE in quick mode
  so libsql-only builds don't attempt a postgres connection
- Match empty-env-var filtering in key source detection to align with
  resolve_master_key() behavior
- Filter empty strings to None in DatabaseConfig::from_libsql_path()
  for turso_url/turso_token

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-16 04:58:17 +00:00
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]>
2026-03-15 21:33:04 +00:00
6aaa89010a fix(security): default webhook server to loopback when tunnel is configured (#1194)
When a tunnel provider (ngrok, cloudflare, tailscale, etc.) or static
TUNNEL_URL is configured, external traffic arrives through the tunnel,
so binding 0.0.0.0 is unnecessary attack surface. The webhook server
now defaults to 127.0.0.1 when a tunnel is active. Explicit HTTP_HOST
still overrides the default in all cases.

Co-authored-by: Claude Opus 4.6 <[email protected]>
2026-03-15 20:38:02 +00:00
NigeGitHubgemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>Illia Polosukhin
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]>
2026-03-15 07:08:06 +00:00
pikaxingeandGitHub 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
2026-03-15 06:00:19 +00:00
ReidandGitHub e74214dce8 fix(config): unify ChannelsConfig resolution to env > settings > default (#1124)
ChannelsConfig::resolve() ignored most ChannelSettings fields, reading
  exclusively from env vars. This made `config set` ineffective for gateway,
  HTTP, CLI, and WASM channel settings — a prerequisite blocker for #86
  (hot-reload) and CLI management commands.

  - Add gateway and CLI fields to ChannelSettings with correct defaults
  - Rewrite resolve() to fall back to settings when env var is unset
  - Keep strict boolean validation via parse_bool_env for all bool fields
  - Fix GATEWAY_PORT default divergence (3001 -> 3000) in extension manager
  - Export DEFAULT_GATEWAY_PORT constant as single source of truth
  - Add 8 tests: settings fallback, env override, DB roundtrip, invalid bool rejection

  Part of #1119 (Phase 1: Channels pilot)
[skip-regression-check]
2026-03-15 05:59:08 +00:00
NigeandGitHub dac420840d fix(web-chat): normalize chat copy to plain text (#1114)
* fix(web-chat): force plain-text clipboard copy from chat messages

* test(e2e): make chat copy test target deterministic message
2026-03-15 05:52:47 +00:00
Xing JiandGitHub 3f6d2ab6c2 fix(skill): treat empty url param as absent when installing skills (#1128)
LLMs sometimes pass "" for optional parameters instead of omitting them.
Previously, passing url: "" to skill_install would match the explicit-URL
branch and attempt to fetch from an empty string, producing an invalid URL
error instead of falling back to the catalog lookup.

Fix by adding .filter(|s| !s.is_empty()) so an empty url is treated the
same as a missing field.

A unit test verifies the parameter filtering behaviour directly; the full
execute path (catalog lookup + install) requires a real catalog and database
and cannot be covered at the unit level.
2026-03-15 05:50:39 +00:00