Skip to content

chore(deps): bump next from 16.0.10 to 16.1.7 in /dashboard in the npm_and_yarn group across 1 directory#1

Open
dependabot[bot] wants to merge 1 commit into
masterfrom
dependabot/npm_and_yarn/dashboard/npm_and_yarn-229c191f58
Open

chore(deps): bump next from 16.0.10 to 16.1.7 in /dashboard in the npm_and_yarn group across 1 directory#1
dependabot[bot] wants to merge 1 commit into
masterfrom
dependabot/npm_and_yarn/dashboard/npm_and_yarn-229c191f58

Conversation

@dependabot
Copy link
Copy Markdown

@dependabot dependabot Bot commented on behalf of github Mar 18, 2026

Bumps the npm_and_yarn group with 1 update in the /dashboard directory: next.

Updates next from 16.0.10 to 16.1.7

Release notes

Sourced from next's releases.

v16.1.7

[!NOTE] This release is backporting bug fixes. It does not include all pending features/changes on canary.

Core Changes

  • [Cache Components] Prevent streaming fetch calls from hanging in dev (#89194)
  • Apply server actions transform to node_modules in route handlers (#89380)
  • ensure maxPostponedStateSize is always respected (See: CVE-2026-27979)
  • feat(next/image): add lru disk cache and images.maximumDiskCacheSize (See: CVE-2026-27980)
  • Allow blocking cross-site dev-only websocket connections from privacy-sensitive origins (See: CVE-2026-27977)
  • Disallow Server Action submissions from privacy-sensitive contexts by default (See: CVE-2026-27978)
  • fix: patch http-proxy to prevent request smuggling in rewrites (See: CVE-2026-29057)

Credits

Huge thanks to @​unstubbable, @​styfle, @​eps1lon, and @​ztanner for helping!

v16.1.6

[!NOTE] This release is backporting bug fixes. It does not include all pending features/changes on canary.

Core Changes

  • Upgrade to swc 54 (#88207)
  • implement LRU cache with invocation ID scoping for minimal mode response cache (#88509)
  • tweak LRU sentinel key (#89123)

Credits

Huge thanks to @​mischnic, @​wyattjoh, and @​ztanner for helping!

v16.1.5

Please refer the following changelogs for more information about this security release:

https://vercel.com/changelog/summaries-of-cve-2025-59471-and-cve-2025-59472 https://vercel.com/changelog/summary-of-cve-2026-23864

v16.0.11

Please see this changelog for more information about this security patch.

Commits
  • bdf3e35 v16.1.7
  • dc98c04 [backport]: fix: patch http-proxy to prevent request smuggling in rewrites (#...
  • 9023c0a [backport] Disallow Server Action submissions from privacy-sensitive contexts...
  • 36a97b9 Allow blocking cross-site dev-only websocket connections from privacy-sensiti...
  • 93c3993 [backport]: feat(next/image): add lru disk cache and `images.maximumDiskCache...
  • c68d62d Backport documentation fixes for 16.1.x (#90655)
  • 5214ac1 [backport]: ensure maxPostponedStateSize is always respected (#90060) (#90471)
  • c95e357 Backport/docs fixes 16.1.x (#90125)
  • cba6144 [backport] Apply server actions transform to node_modules in route handlers...
  • 3db9063 [backport] [Cache Components] Prevent streaming fetch calls from hanging in d...
  • Additional commits viewable in compare view

Dependabot compatibility score

You can trigger a rebase of this PR by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore <dependency name> major version will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)
  • @dependabot ignore <dependency name> minor version will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)
  • @dependabot ignore <dependency name> will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)
  • @dependabot unignore <dependency name> will remove all of the ignore conditions of the specified dependency
  • @dependabot unignore <dependency name> <ignore condition> will remove the ignore condition of the specified dependency and ignore conditions
    You can disable automated security fix PRs for this repo from the Security Alerts page.

Note
Automatic rebases have been disabled on this pull request as it has been open for over 30 days.

Bumps the npm_and_yarn group with 1 update in the /dashboard directory: [next](https://github.com/vercel/next.js).


Updates `next` from 16.0.10 to 16.1.7
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](vercel/next.js@v16.0.10...v16.1.7)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 16.1.7
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code labels Mar 18, 2026
@socket-security
Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednpm/​next@​16.0.10 ⏵ 16.1.76299 +22919770

View full report

danielbodnar pushed a commit that referenced this pull request Apr 21, 2026
This commit fixes two critical UX bugs:

**Bug #1: Agents not appearing after connection until restart**
- Root cause: SWR cache with aggressive settings (30s dedup, no revalidation on focus)
- Fixed by reducing dedupingInterval from 30s to 5s
- Enabled revalidateOnFocus for automatic refresh on tab switching
- Added manual refresh button in new mission dialog header
- Exposed SWR mutate functions for programmatic cache invalidation

**Bug #2: Password prompt missing after API URL change**
- Root cause: AuthGate component only checked auth once at mount
- Fixed by adding custom event 'openagent:api:url-changed'
- Settings page dispatches event when API URL is saved
- AuthGate listens for event and re-validates authentication
- Improved error handling to assume auth required on health check failure

Changes:
- dashboard/src/components/new-mission-dialog.tsx: Updated SWR cache settings, added refresh mechanism
- dashboard/src/components/auth-gate.tsx: Added API URL change detection
- dashboard/src/app/settings/system/page.tsx: Dispatch event on URL change
- Cargo.toml: Bump version to 0.7.5
- dashboard/package.json: Bump version to 0.7.5

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
danielbodnar pushed a commit that referenced this pull request Apr 21, 2026
* Fix automation toggle, iOS layout, and warnings

* chore: format rust sources

* Respect manual thinking panel toggle

* Fix clippy warnings for CI

* Add model override support

* Fix OpenCode completion and add backend model lists

* docs: document model override in new mission flow

* feat: improve model override UX and validation

**What changed:**
1. Added backend validation for model overrides with clear error messages
2. Replaced datalist with proper select dropdown (grouped by provider)
3. Exposed custom provider IDs in UI for transparency

**Backend validation (src/api/providers.rs):**
- New `validate_model_override()` function validates model IDs per backend
- Validates known providers against their model catalogs
- Provides escape hatch for custom/new models (claude-*, gpt-*, o1-*)
- Returns user-friendly errors with available model suggestions

**Frontend improvements (dashboard/):**
- Replaced `<input list>` with native `<select>` for better UX
- Groups models by provider with optgroup
- Shows descriptions inline with model names
- Displays custom provider IDs in group labels for clarity

**Type safety:**
- Added `provider_id?: string` to BackendModelOption interface
- Populates provider_id for custom providers (billing === "custom")

**Integration:**
- Validates model override in create_mission endpoint (src/api/control.rs)
- Returns 400 Bad Request with validation error if invalid model provided

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: resolve bugbot review findings

**Fixed Issues:**

1. **High Severity**: Wrong variable name in useEffect dependency
   - Changed `missionId` to `viewingMissionId` in thinking panel reset effect
   - Fixes state not resetting when switching missions

2. **Medium Severity**: Interval automations fire immediately despite toggle
   - Set `last_triggered_at` to current time when `start_immediately=false`
   - Prevents scheduler from treating `None` as "trigger now"
   - Interval automations now wait for full interval before first trigger

3. **Low Severity**: Escape sequence order corruption
   - Fixed backslash unescaping using placeholder approach
   - Prevents `\\n` from being incorrectly converted to newline

4. **Low Severity**: Duplicate API types in api.ts
   - Removed duplicate BackendModelOption interfaces and functions
   - These are already exported from api/providers.ts (line 17)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* chore: bump version to 0.7.7 and fix formatting

- Bump version from 0.7.6 to 0.7.7 in Cargo.toml and package.json
- Fix rustfmt formatting issues in control.rs and mission_runner.rs
- Resolve CI formatting check failures

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: remove unused configured variable in validate_model_override

Removes unnecessary file I/O by deleting the unused `configured` variable
that was calling `get_configured_provider_ids()` but never used. This was
copy-pasted from `list_backend_model_options` where it IS needed.

Fixes bugbot finding: d0f44ddf-cfd7-4079-a698-466a7f921833

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: apply rustfmt formatting to providers.rs

- Format closure definition for push_options
- Break long iterator chains across multiple lines
- Fixes remaining CI formatting check failures

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: format provider_id ternary expression

Multi-line formatting for provider_id if-else expression to match rustfmt style.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: fix Anthropic OAuth 24-hour reconnection issue

Implements three solutions to prevent OAuth token expiration:

**Solution #1: Proactive Background Token Refresher**
- Added background task that runs every 15 minutes
- Checks all OAuth providers and refreshes tokens expiring within 1 hour
- Prevents the 24-hour reconnection issue by refreshing at ~23 hours
- Spawned in init_control_state() alongside automation scheduler

**Solution #2: Refresh Token Rotation Handling**
- New refresh_oauth_token_internal() function in ai_providers.rs
- Properly captures new refresh_token from OAuth responses
- Critical for Anthropic which rotates refresh tokens on each refresh
- Ensures old refresh token doesn't get reused after rotation

**Solution Th0rgal#3: Multi-Tier Token Sync**
- New sync_oauth_to_all_tiers() function atomically syncs to all storage:
  * Tier 1: Open Agent credentials (~/.sandboxed-sh/credentials.json)
  * Tier 2: OpenCode auth.json (~/.local/share/opencode/auth.json)
  * Tier 3: Claude CLI credentials (~/.claude/.credentials.json)
- Updated all refresh functions to use unified sync
- Prevents desync between storage locations

**Files modified:**
- src/api/control.rs: Added oauth_token_refresher_loop() background task
- src/api/ai_providers.rs: Added refresh_oauth_token_internal() and
  sync_oauth_to_all_tiers(), updated all refresh functions

**Testing:**
- Created comprehensive testing guide: output/TOKEN_REFRESH_TESTING.md
- Covers 7 test scenarios including edge cases
- Documents monitoring and troubleshooting

**Impact:**
- Users no longer need to reconnect Anthropic account every 24 hours
- Providers stay green instead of showing orange warning
- Missions continue working without manual intervention

Fixes #TBD (24-hour Anthropic OAuth reconnection issue)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: format long lines in OAuth token refresh code

Break up long lines to pass rustfmt checks:
- control.rs: Split tracing::info! call
- ai_providers.rs: Split format! error messages and ok_or_else closures

This ensures CI Format check passes.

* fix: move OAuth token refresher to routes.rs and fix compilation errors

- Moved oauth_token_refresher_loop from control.rs to routes.rs
- Spawn refresher alongside desktop cleanup task in serve()
- Fixed OAuthCredential -> OAuthCredentials (correct struct name)
- Fixed module references (ai_providers_api instead of super::ai_providers)
- Fixed oauth borrow (use &provider.oauth instead of ref)

This resolves compilation errors E0422, E0308, E0609.

* feat: add container memory monitoring

Adds API endpoints and shell script to monitor container memory usage.
This helps diagnose OOM issues and containers being killed.

**New API endpoints:**
- GET /api/workspaces/:id/memory - Get memory stats for one workspace
- GET /api/workspaces/memory/all - Get memory stats for all workspaces

**New shell script:**
- scripts/check-memory.sh - Monitor all containers or current container

**Stats provided:**
- Current memory usage (bytes and MB)
- Peak memory usage
- Memory limit (currently unlimited for all containers)
- Memory available
- Container name

**Use case:**
Investigate missions failing with 'signal: Some("Killed")' error.
Example: mission 82ccf525-9ec5-47c5-96f1-f4c6c7a8b307

Containers currently have NO memory limits (MemoryMax=infinity).
This can cause OOM kills when containers use excessive RAM.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: handle non-numeric memory values in check-memory script

Add validation for numeric values before arithmetic operations.
Prevents errors when systemd returns '[not set]' or empty values.

* debug: add instrumentation to track response.incomplete bug

Add detailed logging to distinguish between:
- response.completed (normal completion) ✅
- response.incomplete (truncated/interrupted) ❌

The bug: Both events currently trigger mission completion, but
response.incomplete means the message was cut off (e.g., hit token
limit, stream interrupted). This causes missions to show as 'completed'
with incomplete final messages.

Example: Mission 34c869bb completed with 'Let me fetch the PR
information directly:' instead of continuing.

This instrumented version logs prominently when response.incomplete
is received so we can:
1. Confirm frequency of the bug
2. Correlate with mission characteristics
3. Verify the fix once implemented

For now, keeping the buggy behavior (completing on incomplete) to
match prod, but logging as ERROR so it's easy to spot.

Next step: Deploy to dev backend and run test missions to confirm.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Fix compilation error in OAuth token refresher

- Change ai_providers.update() error handling from Result to Option style
- update() returns Option<AIProvider>, not Result

* Fix rustfmt formatting issues

- Format long function chains in ai_providers.rs
- Format long function chains in routes.rs
- Format long function chains in workspaces.rs

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
danielbodnar pushed a commit that referenced this pull request Apr 21, 2026
…el routing UI (v0.9.1)

* Fix provider slot held forever when LLM API hangs

Two bugs caused ZAI missions to hold the concurrency permit indefinitely,
blocking all other ZAI missions:

1. Heartbeats on stderr (`server.heartbeat` every ~30s) kept resetting
   `last_activity`, preventing the 120s global inactivity timeout from
   ever firing when the LLM API hung without responding.

2. The permit was held for the entire CLI process lifetime, including
   during tool execution when no LLM call is in flight.

Fix:
- Filter server noise (heartbeat/connected/listening) from stderr
  activity tracking so the inactivity timeout works correctly
- Track active tool call depth via a watch channel (ToolCall increments,
  ToolResult decrements)
- Release the provider permit when tools start executing, re-acquire
  (best-effort via try_acquire) when tools finish
- Skip the global inactivity timeout while tools are actively running
  to avoid killing legitimate long-running commands

* Fix "Unknown error" on Claude Code session resume after restart

Two bugs caused a generic "Unknown error" when resuming a Claude Code
session whose data was lost (e.g. after service restart):

1. Claude Code returns errors in an `errors: [...]` array field, but
   ResultEvent only checked `result`, `error`, and `message` — never
   `errors`. The actual message "No conversation found with session ID:
   ..." was silently dropped.

2. `is_session_corruption_error` didn't recognize session-not-found
   errors, so the auto-retry (which creates a fresh session with
   conversation history) never fired.

Fix:
- Add `errors: Vec<String>` field to ResultEvent
- Check `errors[0]` in error_message() before falling through to
  "Unknown error"
- Add "No conversation found with session ID" to session corruption
  detection for auto-retry

* Fix premature 30s timeout on OpenCode missions with unresponsive LLMs

User message echoes via SSE were treated as assistant TextDelta events
when the message role hadn't been recorded yet (message.part.updated
arriving before message.updated). This set text_output_at, triggering
the 30s text idle timeout even though no real assistant output existed.

Fix: only emit TextDelta and capture final_result for messages with a
confirmed "assistant" role. Unknown roles are now skipped instead of
assumed to be assistant messages.

* Fix Codex using wrong default model and update model catalog

Two issues:

1. Global DEFAULT_MODEL env var (e.g. claude-opus-4-6) leaked into Codex
   when no explicit model was selected. Only claudecode and opencode had
   special handling to clear/override the global default. Added a codex
   branch that clears config.default_model when no model_override is given.

2. The Codex model picker showed generic GPT models (gpt-5.3-extra-high)
   that don't work with Codex CLI / ChatGPT accounts. Updated the OpenAI
   catalog with correct Codex model names (gpt-5.3-codex, gpt-5.3-codex-spark,
   gpt-5.2-codex, etc.) and filtered the Codex backend picker to only show
   models containing "codex" in the ID. Also added codex-* prefix to the
   model validation escape hatch.

* Fix bugbot issues: timeout guards, stale tool depth, and text capture

- Add tools_active guard to 30s text idle timeout (matches 120s guard)
  to prevent killing processes mid-tool-execution (bugbot #2)
- Fix stale tool depth permanently disabling inactivity timeout by
  dropping original sender and checking SSE handler liveness (bugbot Th0rgal#4)
- Fix stdout text capture dropping text when message ID is absent,
  consistent with SSE path behavior (bugbot Th0rgal#3)
- Bugbot #1 (permit re-acquisition) resolved by master merge removing
  provider semaphores entirely

* Improve model routing UI: native selects, chain defaults, copy feedback, and chain options in model override

- Replace datalist inputs with native <select> dropdowns in fallback chain editor
- Update builtin/smart chain to zai/glm-5 + minimax/MiniMax-M2.5
- Add builtin/cheap chain with zai/glm-4.7
- Protect all builtin/* chains from deletion
- Add copy-to-clipboard visual feedback (green checkmark) on proxy URL
- Prepend model routing chains to OpenCode model override selector

* Fix duplicate thinking blocks and add model info to debug dropdown

- Track finalized thinking block indices during streaming to prevent
  ContentBlock::Thinking from re-sending already-finalized blocks, which
  caused duplicate entries in the thinking panel
- Apply same fix to both Claude Code and Amp harness paths
- Add Backend, Model override, and Resolved model rows to the Running
  debug dropdown, with the resolved model derived from assistant_message
  events showing the actual model used after chain resolution

* Fix bugbot issues: SSE tool depth reset, error message, and comment

- Reset sse_tool_depth_tx to 0 on SSE reconnect to prevent stale tool
  depth from permanently disabling the inactivity timeout
- Add 'codex-*' to the model validation error message for Codex backend
- Fix misleading comment about errors array priority in error_message()

* Fix OpenCode missions marked success with no assistant output

When OpenCode produced only runner banner/status lines (e.g. "Starting
opencode server...", "Using port...", "All tasks completed.") but no
actual model response, the mission was incorrectly marked as completed
successfully. This happened because:

1. Banner lines from non-JSON stdout were appended to final_result,
   making it non-empty even without model output.
2. The final validation only checked trim().is_empty(), not whether the
   content was banner-only.
3. The had_error clearing logic at line 7995 would clear errors whenever
   final_result was non-empty, even if it contained only banners.

Fix:
- Extract is_opencode_banner_line() from opencode_output_needs_fallback()
  for reuse across both filtering and validation paths.
- Filter banner lines from final_result in the non-JSON stdout path so
  they don't pollute the model response.
- Replace the empty-check with opencode_output_needs_fallback() at the
  final validation point to catch banner-only output.
- Use opencode_output_needs_fallback() in the had_error clearing logic
  so errors aren't silently cleared when output is banner-only.

Fixes Th0rgal#147, Th0rgal#151.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Surface OpenCode provider error details in mission events

When OpenCode provider failures occurred (e.g. Google/Gemini returning
"Requested entity was not found"), the error details only appeared in
server logs but were never surfaced to the frontend. Users saw generic
failures with no indication of what went wrong.

Root cause: The stderr error handler only matched two narrow patterns
(session.error and "session ended with error") and only stored the
error in a mutex — it never emitted AgentEvent::Error events.

Fix:
- Pass events_tx to the stderr handler so it can emit real-time error
  events to the frontend.
- Broaden error detection to catch:
  - session.error / session ended with error (existing)
  - response.error patterns from provider responses
  - JSON error payloads with structured error/message fields, including
    nested formats like {"error": {"message": "...", "status": "..."}}
- Emit AgentEvent::Error immediately when a provider error is detected,
  so the frontend shows the error reason in real-time instead of only
  at mission completion.
- Log detected errors at warn level for server-side observability.

Fixes Th0rgal#146.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix JSON error extraction fallback and parallel cancel status

Two fixes:

1. JSON error extraction (bugbot review): Restructure the error field
   extraction chain so each JSON shape is tried independently.
   Previously, when "message" existed but was non-string (e.g. null),
   it won the .or() over "error" and the chain silently lost the
   error message. Now tries: top-level "message" string, "error" as
   plain string, "error" as nested object, then raw stringify.

2. Parallel mission cancel (fixes Th0rgal#149): The cancel handler removed
   the parallel runner without updating the mission status in the
   store, leaving it as "pending". Resume validation only accepts
   interrupted/blocked/failed, so cancelled missions couldn't be
   resumed. Now sets status to Interrupted and emits a
   MissionStatusChanged event before removing the runner.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* Tighten is_opencode_banner_line to prevent false-positive model text drops

The banner line filter used overbroad substring matches like
contains("completed") and contains("session:") that would silently
drop real model response lines containing those common English words.

This is a latent correctness bug: when the SSE path is unavailable
and stdout is the only source of model text, any response containing
"completed" (e.g. "Task completed successfully") would be filtered
out as a banner line, causing the mission to fall back to storage
recovery or report "no assistant output".

Changes:
- Remove standalone contains("completed") — it's redundant with the
  more specific "all tasks completed" match and far too broad
- Convert contains() to starts_with() for patterns that always appear
  at the start of a line: "using port", "sending prompt",
  "waiting for completion", "all tasks completed", "session:",
  "session id:"
- Add documentation explaining why tight patterns matter
- Keep contains() only for patterns that genuinely appear mid-line
  (e.g. "starting opencode server", "event stream did not close")

Verified against real oh-my-opencode stdout on dev deployment — all 6
known banner lines are still correctly filtered.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* Fix stdout path allowing text from unknown-role messages

The SSE path in handle_part_update correctly skips text when a
message_id is present but the role hasn't been recorded yet (the
message.updated event hasn't arrived). However, the stdout JSON
parsing path used .unwrap_or(false) which treated unknown roles as
"not non-assistant", letting the text through. This inconsistency
meant user-message echoes via stdout could still set text_output_at
and trigger the premature 30-second text-idle timeout — the exact
bug the SSE fix was designed to prevent.

Fix: Replace the is_non_assistant / unwrap_or(false) pattern with an
is_confirmed_assistant match that mirrors the SSE path's three-way
logic:
  - msg_id present, role = non-assistant → skip
  - msg_id present, role not yet known   → skip
  - msg_id present, role = "assistant"   → process text
  - msg_id absent                        → allow through

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* Accumulate SSE text deltas as fallback for truncated assistant output

When OpenCode's SSE stream delivers text deltas successfully but all
three traditional final_result sources fail (stdout JSON, session
storage, stderr text buffer), the assistant_message was constructed
from the stderr buffer which truncates long content with "..." —
producing an 83-char response despite the full text having been
streamed to the dashboard moments earlier.

Root cause: SSE TextDelta events were fire-and-forget to the event
stream. They powered live streaming to the UI but were never captured
for use in final_result construction.

Fix:
- Add sse_text_buffer (Arc<Mutex<String>>) that captures the latest
  full-text snapshot from each TextDelta event in the SSE task.
  TextDelta content from handle_part_update is already a full
  accumulated snapshot (not a delta), so simple replacement works.
- Insert SSE text buffer as a fallback between session storage and
  stderr in the recovery chain. This is the most reliable source
  after storage since it contains exactly what was streamed.
- Include SSE recovery in the had_error clearing logic so recovered
  content isn't marked as a failure.

Fallback chain is now:
  1. stdout JSON (message.part.updated)
  2. Session storage (load from disk)
  3. SSE text buffer (accumulated from streamed deltas) ← NEW
  4. stderr text buffer (truncated by OpenCode logger)

Fixes Th0rgal#158.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* Detect tool-call-only output and surface error instead of raw JSON

When the model emits tool calls but no final text response, the
tool-call JSON fragment can end up in final_result via three paths:
  - response.output_text.delta (SSE handler has no item-type awareness)
  - message.part.updated with a text part containing tool JSON
  - stdout non-JSON fallback (JSON lines pass banner filter)

In all cases, opencode_output_needs_fallback returns false (the JSON
is not a banner line), so the mission silently succeeds with a raw
JSON tool-call fragment as its "assistant message" content.

Fix:
- Add is_tool_call_only_output() that checks whether every non-banner
  line in final_result parses as a JSON object with tool-call markers:
    - type == function_call / tool_use / tool-call / tool_call
    - name + arguments/input pattern (common across all formats)
- Insert this check after the banner-only guard in the final validation
  block, setting had_error=true with a clear user-facing message.
- Log the detected fragment at warn level for debugging.

Normal text responses (including those containing JSON snippets mixed
with prose) are not affected — the check only triggers when ALL
non-banner lines are tool-call fragments.

Fixes Th0rgal#148.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* Fix three latent race conditions in OpenCode mission execution

1. SSE task abort race: handle.abort() was called without awaiting
   completion, so the SSE task could still be mid-write to
   sse_text_buffer when the fallback chain reads it. Now awaits
   the abort to ensure the task is fully stopped before reading.

2. Session idle kills during tool execution: the session idle
   handler (10s timer) had no tools_active guard, unlike the 30s
   text-idle and 120s global inactivity timeouts. If OpenCode sent
   session.idle while a long tool (build, test) was running, the
   process was killed mid-execution. Now checks sse_tool_depth and
   defers the kill until tools finish.

3. had_error cleared despite real SSE error: when the SSE stream
   surfaced a session.error but the SSE text buffer also had partial
   text from before the error, recovered_from_sse unconditionally
   cleared had_error. This caused truncated partial responses to be
   returned as successful. Now checks sse_error_message before
   clearing, matching the guard already used in the second clearing
   block.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* Prevent targeted messages from being silently sent to wrong mission

The UserMessage handler had two fallthrough paths where a message
targeted at a specific mission (e.g., from an automation) could be
silently queued to the main session instead:

1. When max parallel missions capacity was exceeded (line 3477), the
   code fell through to Case 3 which queues to the main session.
   A message meant for Mission 1 would end up on Mission 2.

2. When load_mission_record failed (line 3551), same fallthrough
   occurred — the message was sent to whatever mission was running
   as main.

Both paths now emit an AgentEvent::Error with a clear message,
respond with false (not queued), and continue — preventing the
message from reaching the wrong mission. This fixes the root cause
of automation prompts leaking between unrelated missions.

Fixes Th0rgal#159.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* Fix complete_mission targeting wrong mission, cancelled status, and parallel desktop cleanup

Three correctness fixes:

1. SetStatus command now carries the mission_id captured at send time.
   Previously the handler read `current_mission` which could have changed
   if the user created a new mission while the agent was still running,
   causing the completion status to be applied to the wrong mission.

2. TerminalReason::Cancelled now maps to MissionStatus::Interrupted
   instead of falling through to Failed. This keeps cancelled missions
   resumable and consistent with the explicit cancel path.

3. Parallel runner completion now calls close_mission_desktop_sessions
   to clean up desktop sessions. Previously only the main runner and
   the cancel path performed this cleanup.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Separate stderr error capture from SSE error signal to prevent false-positive recovery blocking

The broad stderr error detection (lines containing "error" or "failed"
with JSON) previously wrote into the same sse_error_message mutex used
by the SSE handler. A false positive — e.g. a log line about "error
recovery" containing a JSON object — would permanently set
sse_error_message, causing the recovery guards at the end of the
function to never clear had_error. Valid recovered content from SSE
text buffer or session storage would then be wrapped in
AgentResult::failure instead of being returned as a success.

Fix: introduce a separate stderr_error_message mutex. Stderr-detected
errors still set had_error and surface their message to the user, but
they don't write into sse_error_message. The recovery guards only
check sse_error_message (genuine SSE-level errors like session.error),
so stderr false positives no longer block recovery.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add RTK integration for CLI output compression

- Add RTK command wrapping in terminal.rs with allowlisted commands
- Add atomic stats tracking for RTK token savings
- Add RTK stats API endpoint in model_routing.rs
- Add RTK stats UI section in model-routing dashboard page
- Update frontend API types to match RTK stats response format

RTK reduces token consumption by 60-90% on common dev commands
by compressing CLI output before returning to the LLM.

* Fix mission stuck Active on task panic, parallel Interrupted guard, and resume stall detection

Three correctness fixes in the control actor loop:

1. Task JoinError leaves mission Active forever: when a tokio task
   panics (JoinError), the Err branch emitted an Error event but never
   updated the mission's DB status. The mission stayed Active
   permanently, couldn't be resumed (requires Failed/Interrupted/
   Blocked), and the user saw an error with no status change. Now
   marks the mission as Failed and emits MissionStatusChanged.

2. Parallel runner completion skips Interrupted missions: the
   should_update guard only matched Pending|Active, so a mission that
   was previously cancelled (Interrupted) and then restarted via
   StartParallel would never have its terminal status set on
   completion. Added Interrupted to the guard.

3. ResumeMission doesn't reset stall detection: main_runner_last_activity
   was not reset when spawning a resumed mission, so stale timestamps
   from a prior run could trigger false stall alerts. Now resets the
   timestamp, matching the pattern used in the normal message dequeue path.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix compilation errors in RTK integration

- Make terminal module public to allow rtk_stats export
- Add explicit type annotation for rtk_stats tuple
- Remove unused mut warning in terminal.rs

* Fix formatting issues

* Fix session corruption retry, history contamination, and idle runner polling

Three correctness fixes:

1. Session corruption retry passes is_continuation=true to a fresh
   session: after detecting a corrupt session and creating a new one,
   the retry call still passed is_continuation=true. For the Amp
   backend this causes `threads continue <session_id>` to be used on
   an empty session. Now passes false since the session is brand new
   (history context is already embedded in the retry message).

2. Error output contaminates history for future turns: poll_completion
   unconditionally pushed assistant output to history, even when it
   was an error string like "Claude Code produced no output..." or
   "OpenCode CLI exited with status: ...". These error strings then
   polluted the context sent to the model on subsequent turns. Now
   only pushes assistant output when the turn succeeded and produced
   non-empty content.

3. check_finished returned true for idle runners (no running_handle),
   causing the 100ms poll loop to call poll_completion on every idle
   parallel runner every tick. Now returns false when no handle
   exists, matching the semantic intent: "no task running" means
   "nothing to poll".

Also fixes RTK build: makes terminal module pub(crate) visible and
adds type annotation for saturating_sub inference.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix SSE session.idle not reset on reconnect causing premature kill

When an SSE connection drops and reconnects, the watch channels for
session_idle, tool_depth, and retry_count retained stale values from
the previous connection. A stale session_idle=true would trigger the
10-second kill timer immediately after reconnect, prematurely
terminating the mission.

Three-part fix:
- Sender side: reset session_idle to false and retry counter to 0
  on SSE reconnect (alongside existing tool_depth reset)
- Receiver side (idle handler): detect false→true transition reset
  and clear session_idle_seen/session_idle_at
- Receiver side (retry handler): detect counter reset to 0 and
  clear local tracking to prevent stale accumulation

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* Fix inactivity timeout firing during SSE reconnect and cancel teardown race

Two fixes for OpenCode mission stability:

1. Reset last_activity on SSE reconnect: When the SSE connection drops
   and reconnects, tool_depth is reset to 0 (disabling the tools_active
   guard). Without also resetting last_activity, the 120s global and 30s
   text idle timers continue counting from the last event on the dead
   connection and can fire during the reconnect window, prematurely
   killing the mission.

2. Await background tasks on cancellation: The cancel path aborted the
   SSE and stderr handles without awaiting them, unlike the normal exit
   path which carefully awaits to let in-flight mutex writes complete.
   This asymmetry could cause data races on shared state (sse_text_buffer,
   sse_error_message, etc.). Now uses the same teardown discipline: stderr
   gets a 2s timeout before abort, SSE handle is awaited after abort.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* Fix RTK integration: working PreToolUse hooks, accurate stats, container support

RTK was architecturally disconnected from actual agent execution — the
terminal.rs wrapping only applied to the MCP bash tool, but OpenCode and
Claude Code use their own native Bash tools. Additionally, RTK stats
showed ~0% savings because both "original" and "compressed" measurements
used the already-compressed output.

Changes:
- Add PreToolUse hook system that intercepts Claude Code's native Bash
  tool calls and rewrites eligible commands with RTK subcommands
- Copy RTK binary into container workspaces from host
- Translate hook paths to container-relative for nspawn containers
- Write .claude/settings.local.json with hooks for both claudecode and
  opencode backends
- Replace broken atomic-based stats with `rtk gain -f json` which
  queries RTK's own SQLite database for accurate pre/post measurements
- Remove dead rtk_stats.rs module (RtkStatsTracker was never used)
- Fix syntax error from bad merge (orphaned lines in terminal.rs)
- Map only RTK-supported subcommands (ls, git, gh, grep, cargo, etc.)
  and use -- separator to prevent flag conflicts

Tested: claudecode backend mission with RTK producing 54% token savings
on ls and git commands. Dashboard RTK card renders correctly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix overly broad session: banner pattern and missing codex-* in error message

- Tighten banner filter: `starts_with("session:")` matched model text
  discussing sessions; narrowed to `starts_with("session: ses_")` which
  only matches the actual OpenCode runner line (e.g. "Session: ses_abc123")
- Same fix in OPENCODE_STATUS_PATTERNS: replaced bare "session:" with
  "session id: ses_" to avoid stripping legitimate model output
- Add codex-* to unconfigured-OpenAI error message (was already accepted
  by validation but not mentioned in the error hint)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Bump version to 0.9.1

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: root <root@sandboxed-sh-dev.gazella-vector.ts.net>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants