mixdog 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/lib/gate-scheduler.mjs +2 -2
- package/scripts/lib/run-node-tests.mjs +5 -14
- package/scripts/mobile-release.mjs +5 -4
- package/scripts/provider-pingpong-live.mjs +9 -9
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/reviewer/AGENT.md +2 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +11 -0
- package/src/defaults/skills/browser-use/SKILL.md +2 -1
- package/src/defaults/skills/computer-use/SKILL.md +3 -1
- package/src/defaults/skills/visualize/SKILL.md +3 -2
- package/src/lib/rules-builder.cjs +1 -1
- package/src/output-styles/common.md +22 -5
- package/src/output-styles/detailed.md +7 -5
- package/src/output-styles/minimal.md +7 -3
- package/src/output-styles/simple.md +2 -3
- package/src/rules/agent/AGENT.md +3 -1
- package/src/rules/lead/LEAD.md +10 -7
- package/src/rules/shared/10-tool-workflow.md +2 -1
- package/src/rules/shared/60-verification.md +4 -5
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -1
- package/src/runtime/agent/orchestrator/config.mjs +6 -0
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +10 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +53 -60
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +2 -3
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +40 -19
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/catalog-deadline.mjs +163 -0
- package/src/runtime/agent/orchestrator/providers/catalog-failures.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +38 -14
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +100 -26
- package/src/runtime/agent/orchestrator/providers/lib/send-callbacks.mjs +24 -0
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +49 -4
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +0 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +45 -16
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-connect.mjs +196 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +74 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs +194 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs +353 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +293 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +33 -100
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +23 -14
- package/src/runtime/agent/orchestrator/providers/registry.mjs +41 -27
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +2 -3
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +3 -8
- package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +74 -13
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -2
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +39 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +9 -19
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +10 -7
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +62 -22
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +81 -34
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/request-fork.mjs +47 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/side-question.mjs +5 -43
- package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +73 -6
- package/src/runtime/agent/orchestrator/session/store/load-session.mjs +40 -0
- package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-header.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +255 -32
- package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +306 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +70 -21
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +46 -30
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +144 -45
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +32 -13
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +364 -38
- package/src/runtime/agent/orchestrator/session/store.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +43 -12
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +23 -6
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -6
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +13 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +40 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +37 -34
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +0 -4
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +29 -27
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +28 -11
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +12 -3
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +203 -101
- package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +90 -41
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-delivery.mjs +199 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +165 -39
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +25 -13
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +7 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +19 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-line-collector.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +25 -7
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-render.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +12 -5
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +61 -22
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +32 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +193 -19
- package/src/runtime/agent/orchestrator/tools/builtin/shell-direct-exe.mjs +57 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +119 -32
- package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +26 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +13 -28
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +90 -11
- package/src/runtime/agent/orchestrator/tools/builtin/wait-budgets.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +54 -10
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +22 -28
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +11 -7
- package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +126 -9
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +15 -6
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +40 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promotion-results.mjs +13 -3
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +35 -4
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +39 -23
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/attachments/office-extract.mjs +11 -9
- package/src/runtime/memory/lib/embedding-tokenizer.mjs +32 -18
- package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -1
- package/src/runtime/memory/lib/tool-call-handler.mjs +7 -5
- package/src/runtime/office/authoring/pptx-script-normalize.mjs +11 -8
- package/src/runtime/office/core/tabular.mjs +1 -1
- package/src/runtime/office/design/library/design-template-inspect.mjs +7 -4
- package/src/runtime/office/portable/docx-formatting.mjs +1 -1
- package/src/runtime/office/portable/docx-runs.mjs +1 -1
- package/src/runtime/office/portable/docx-tracked-edits.mjs +2 -2
- package/src/runtime/office/portable/portable-docx-edits.mjs +11 -10
- package/src/runtime/office/portable/portable-docx-operations.mjs +21 -17
- package/src/runtime/office/portable/portable-docx-parts.mjs +15 -9
- package/src/runtime/office/portable/portable-docx-styles.mjs +4 -1
- package/src/runtime/office/portable/portable-docx-xml.mjs +9 -9
- package/src/runtime/office/portable/portable-opc.mjs +27 -13
- package/src/runtime/office/portable/portable-pivot.mjs +3 -3
- package/src/runtime/office/portable/portable-pptx-chart.mjs +3 -3
- package/src/runtime/office/portable/portable-pptx-charts.mjs +24 -18
- package/src/runtime/office/portable/portable-pptx-core.mjs +5 -5
- package/src/runtime/office/portable/portable-pptx-deck.mjs +8 -4
- package/src/runtime/office/portable/portable-pptx-package.mjs +7 -4
- package/src/runtime/office/portable/portable-pptx-shapes.mjs +18 -9
- package/src/runtime/office/portable/portable-sheet-parts.mjs +9 -3
- package/src/runtime/office/portable/portable-sheet-xml.mjs +5 -5
- package/src/runtime/office/portable/portable-xlsx-charts.mjs +4 -1
- package/src/runtime/office/portable/portable-xlsx-drawings.mjs +10 -6
- package/src/runtime/office/portable/portable-xlsx-operations.mjs +8 -5
- package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +1 -1
- package/src/runtime/office/portable/portable-xlsx-reference-shift.mjs +11 -7
- package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +6 -3
- package/src/runtime/office/portable/portable-xml.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +22 -13
- package/src/runtime/shared/background-tasks.mjs +60 -20
- package/src/runtime/shared/child-spawn-gate.mjs +26 -16
- package/src/runtime/shared/config.mjs +0 -10
- package/src/runtime/shared/file-lock.mjs +7 -1
- package/src/runtime/shared/file-permissions-worker.mjs +38 -0
- package/src/runtime/shared/file-permissions.d.mts +19 -4
- package/src/runtime/shared/file-permissions.mjs +310 -4
- package/src/runtime/shared/git-runner-worker.mjs +6 -78
- package/src/runtime/shared/llm/http-agent.mjs +7 -3
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +10 -0
- package/src/runtime/shared/llm/usage-accounting.mjs +2 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +32 -3
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +12 -5
- package/src/runtime/shared/llm/usage-ledger-worker.mjs +1 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +20 -5
- package/src/runtime/shared/memory-snapshot.mjs +5 -1
- package/src/runtime/shared/notify-trace.mjs +8 -1
- package/src/runtime/shared/pristine-execution-contract.json +5 -1
- package/src/runtime/shared/process-runner-worker.mjs +7 -0
- package/src/runtime/shared/process-runner.mjs +38 -0
- package/src/runtime/shared/provider-accounts.mjs +2 -1
- package/src/runtime/shared/provider-api-key.mjs +0 -1
- package/src/runtime/shared/read-row-numbers.mjs +1 -1
- package/src/runtime/shared/resource-admission.mjs +60 -18
- package/src/runtime/shared/run-child.mjs +84 -0
- package/src/runtime/shared/session-activity-signal.mjs +23 -0
- package/src/runtime/shared/shell-output-summary.mjs +23 -0
- package/src/runtime/shared/singleton-owner.mjs +126 -42
- package/src/runtime/shared/tool-output-limit.mjs +4 -2
- package/src/runtime/shared/tool-output-marker.mjs +58 -0
- package/src/runtime/shared/user-data-guard.mjs +28 -9
- package/src/runtime/shared/worktrees/activity-gate.mjs +33 -8
- package/src/runtime/shared/worktrees/git-head.mjs +20 -2
- package/src/runtime/shared/worktrees/lock.mjs +3 -2
- package/src/runtime/terminal-bridge/tool-defs.mjs +1 -1
- package/src/runtime/tidy/runners/biome.mjs +3 -1
- package/src/runtime/tidy/shadow.mjs +2 -1
- package/src/runtime/web-search/index.mjs +5 -7
- package/src/runtime/web-search/lib/formatter.mjs +42 -6
- package/src/runtime/web-search/tool-defs.mjs +6 -1
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +4 -3
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -4
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +3 -3
- package/src/session-runtime/lifecycle/shared.mjs +19 -2
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +2 -0
- package/src/session-runtime/lifecycle/teardown.mjs +5 -2
- package/src/session-runtime/lifecycle-api.mjs +3 -1
- package/src/session-runtime/provider-auth/login.mjs +10 -6
- package/src/session-runtime/provider-auth/setup.mjs +19 -2
- package/src/session-runtime/provider-catalog-cache.mjs +101 -25
- package/src/session-runtime/provider-models/catalog-load.mjs +14 -3
- package/src/session-runtime/provider-models/row-hydration.mjs +4 -0
- package/src/session-runtime/provider-models.mjs +76 -10
- package/src/session-runtime/provider-readiness.mjs +4 -0
- package/src/session-runtime/services/agent-tool/close-flow.mjs +2 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +10 -2
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +13 -4
- package/src/session-runtime/services/agent-tool/notify.mjs +15 -4
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +134 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/deferred-spawn.mjs +2 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -1
- package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +8 -3
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +4 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +44 -10
- package/src/session-runtime/services/agent-tool.mjs +20 -0
- package/src/session-runtime/services/provider-admin.mjs +92 -20
- package/src/session-runtime/services/usage-dashboard-model.mjs +2 -2
- package/src/session-runtime/services/usage-dashboard.mjs +27 -69
- package/src/session-runtime/turn/session-ops.mjs +2 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/usage-stats-api.mjs +6 -1
- package/src/standalone/channel-transport/http-routes.mjs +10 -9
- package/src/standalone/daemon-desktop-runtime.mjs +20 -7
- package/src/standalone/daemon-owner.mjs +117 -25
- package/src/standalone/daemon.mjs +43 -13
- package/src/standalone/desktop-service-registry.mjs +32 -5
- package/src/standalone/session-call-cache.mjs +6 -0
- package/src/standalone/session-protocol.mjs +1 -1
- package/src/standalone/session-service/retention/busy.mjs +7 -2
- package/src/standalone/session-transport/routes.mjs +7 -6
- package/src/tui/app/model-options.mjs +39 -2
- package/src/tui/app/model-picker.mjs +16 -2
- package/src/tui/app/provider-setup-picker.mjs +1 -2
- package/src/tui/dist/index.mjs +436 -123
- package/src/tui/session/notice-text.mjs +0 -1
- package/src/tui/session/session-api/intake/submission.mjs +7 -13
- package/src/tui/session/session-api/integrations.mjs +1 -5
- package/src/tui/session/session-flow/drain.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +46 -0
- package/src/tui/session/session-flow.mjs +2 -0
- package/src/tui/session/turn-stream.mjs +33 -0
- package/src/tui/session/turn.mjs +30 -3
- package/src/ui/statusline.mjs +1 -6
- package/src/ui/streaming-markdown-heal.mjs +94 -25
- package/src/vendor/statusline/bin/statusline-route.mjs +0 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +0 -1
- package/src/workflows/default/WORKFLOW.md +9 -13
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// agent-tool/restart-notify.mjs
|
|
2
|
+
// Agent tasks live in process memory, so a daemon restart ends every in-flight
|
|
3
|
+
// one without a word: the successor only sees worker rows left `failed`
|
|
4
|
+
// (process-restart). This tells each task's owner, exactly once.
|
|
5
|
+
//
|
|
6
|
+
// Order is what makes it safe: the failure notification is enqueued in the
|
|
7
|
+
// owner's persisted pending queue under a stable id (`restart:<taskId>`) and
|
|
8
|
+
// flushed to the spool FIRST; only then is the row stamped `restartNotified`.
|
|
9
|
+
// A crash or a throw before the stamp leaves the row retryable, and the retry
|
|
10
|
+
// re-enqueues the same id — the queue is id-idempotent, so the owner still
|
|
11
|
+
// sees it once.
|
|
12
|
+
import {
|
|
13
|
+
acknowledgeBackgroundTaskCompletion,
|
|
14
|
+
completeBackgroundTask,
|
|
15
|
+
getBackgroundTask,
|
|
16
|
+
registerBackgroundTask,
|
|
17
|
+
} from '../../../runtime/shared/background-tasks.mjs';
|
|
18
|
+
import { renderAgentCompletionEnvelope } from '../../../runtime/shared/task-notification-envelope.mjs';
|
|
19
|
+
import { modelVisibleToolCompletionMessage } from '../../../runtime/shared/tool-execution-contract.mjs';
|
|
20
|
+
import { clean } from './helpers.mjs';
|
|
21
|
+
|
|
22
|
+
const RESTART_FAILURE_ERROR = 'process restart: the daemon restarted while this agent task was running';
|
|
23
|
+
const SETTLE_TIMEOUT_MS = 5000;
|
|
24
|
+
|
|
25
|
+
/** Stable dedup id of one lost task's notification. A row that never recorded
|
|
26
|
+
* a task id falls back to its session and settle time. */
|
|
27
|
+
export function restartNotificationId(row) {
|
|
28
|
+
return `restart:${clean(row.task_id) || `${clean(row.sessionId)}:${clean(row.finishedAt)}`}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** `pendingRestartFailures()` lists rows whose owner is not yet told;
|
|
32
|
+
* `markRestartNotified(rows)` stamps them. `mgr` supplies the persisted
|
|
33
|
+
* pending queue (enqueuePendingMessage / markCompletionEntry /
|
|
34
|
+
* settlePendingMessageWrites). `notifySessionCompletion` is only the live,
|
|
35
|
+
* best-effort emit to attached surfaces after the durable enqueue. */
|
|
36
|
+
export function createRestartFailureNotifier({
|
|
37
|
+
pendingRestartFailures,
|
|
38
|
+
markRestartNotified,
|
|
39
|
+
mgr,
|
|
40
|
+
notifySessionCompletion,
|
|
41
|
+
}) {
|
|
42
|
+
function enqueueDurably(row) {
|
|
43
|
+
const owner = clean(row.ownerSessionId);
|
|
44
|
+
const executionId = restartNotificationId(row);
|
|
45
|
+
const meta = {
|
|
46
|
+
type: 'agent_task_result',
|
|
47
|
+
execution_surface: 'agent',
|
|
48
|
+
execution_id: executionId,
|
|
49
|
+
status: 'failed',
|
|
50
|
+
caller_session_id: owner,
|
|
51
|
+
};
|
|
52
|
+
const text = renderAgentCompletionEnvelope({
|
|
53
|
+
id: clean(row.task_id) || executionId,
|
|
54
|
+
tag: row.tag,
|
|
55
|
+
status: 'failed',
|
|
56
|
+
error: RESTART_FAILURE_ERROR,
|
|
57
|
+
});
|
|
58
|
+
const entry = mgr.markCompletionEntry(modelVisibleToolCompletionMessage(text, meta), { executionId, meta });
|
|
59
|
+
return { owner, text, meta, entry };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Lets `agent status <id>` resolve after the restart; best-effort.
|
|
63
|
+
function registerFailedTask(row) {
|
|
64
|
+
const taskId = clean(row.task_id);
|
|
65
|
+
if (!taskId || getBackgroundTask(taskId)) return;
|
|
66
|
+
const owner = clean(row.ownerSessionId);
|
|
67
|
+
registerBackgroundTask({
|
|
68
|
+
taskId,
|
|
69
|
+
surface: 'agent',
|
|
70
|
+
operation: 'restart',
|
|
71
|
+
label: row.tag,
|
|
72
|
+
input: { type: 'restart', tag: row.tag, sessionId: row.sessionId, agent: row.agent || null },
|
|
73
|
+
context: { callerSessionId: owner, ownerSessionId: owner, clientHostPid: row.clientHostPid || null },
|
|
74
|
+
meta: {
|
|
75
|
+
tag: row.tag,
|
|
76
|
+
sessionId: row.sessionId,
|
|
77
|
+
agent: row.agent || null,
|
|
78
|
+
provider: row.provider || null,
|
|
79
|
+
model: row.model || null,
|
|
80
|
+
preset: row.preset || null,
|
|
81
|
+
effort: row.effort || null,
|
|
82
|
+
},
|
|
83
|
+
resultType: 'agent_task_result',
|
|
84
|
+
});
|
|
85
|
+
completeBackgroundTask(taskId, {
|
|
86
|
+
status: 'failed',
|
|
87
|
+
error: RESTART_FAILURE_ERROR,
|
|
88
|
+
terminalReason: 'process-restart',
|
|
89
|
+
notify: false,
|
|
90
|
+
});
|
|
91
|
+
acknowledgeBackgroundTaskCompletion(taskId);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function notifyRow(row) {
|
|
95
|
+
const { owner, text, meta, entry } = enqueueDurably(row);
|
|
96
|
+
if (!(mgr.enqueuePendingMessage(owner, entry) > 0)) throw new Error(`restart notification refused for ${owner}`);
|
|
97
|
+
if (!(await mgr.settlePendingMessageWrites({ timeoutMs: SETTLE_TIMEOUT_MS }))) {
|
|
98
|
+
throw new Error(`restart notification for ${owner} did not reach the spool`);
|
|
99
|
+
}
|
|
100
|
+
markRestartNotified([row]);
|
|
101
|
+
try {
|
|
102
|
+
registerFailedTask(row);
|
|
103
|
+
} catch {
|
|
104
|
+
/* status lookup only */
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
// Same execution id as the queued entry: the queue dedups the mirror.
|
|
108
|
+
notifySessionCompletion?.(owner, text, meta);
|
|
109
|
+
} catch {
|
|
110
|
+
/* the durable queue is authoritative */
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let inFlight = null;
|
|
115
|
+
async function run() {
|
|
116
|
+
for (const row of pendingRestartFailures()) {
|
|
117
|
+
try {
|
|
118
|
+
await notifyRow(row);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
// Not stamped: the next pass or restart retries this row.
|
|
121
|
+
process.stderr.write(`[agent-tool] restart notification retry pending: ${err?.message || err}\n`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return function notifyRestartFailures() {
|
|
127
|
+
inFlight ||= run()
|
|
128
|
+
.catch((err) => process.stderr.write(`[agent-tool] restart notification pass failed: ${err?.message || err}\n`))
|
|
129
|
+
.finally(() => {
|
|
130
|
+
inFlight = null;
|
|
131
|
+
});
|
|
132
|
+
return inFlight;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -98,6 +98,8 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
|
|
|
98
98
|
registry.upsertWorkerSessionDeferred(session, tag, {
|
|
99
99
|
status: 'running',
|
|
100
100
|
stage: 'running',
|
|
101
|
+
// The send's own task: a restart that ends this turn reports it by id.
|
|
102
|
+
task_id: job?.taskId,
|
|
101
103
|
turnStartedAt: new Date().toISOString(),
|
|
102
104
|
});
|
|
103
105
|
let handoffMsgStart = 0;
|
|
@@ -224,5 +226,26 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
|
|
|
224
226
|
return renderResult({ ...extras, ...views.renderJob(job, false) });
|
|
225
227
|
}
|
|
226
228
|
|
|
227
|
-
|
|
229
|
+
// A background-task completion was queued for this worker. A busy worker
|
|
230
|
+
// takes it at its next tool boundary; an idle one runs a follow-up turn on
|
|
231
|
+
// an empty prompt, which drains the queued completion (never an empty user
|
|
232
|
+
// turn). Its answer reaches the owner like any send result.
|
|
233
|
+
function wakeIdleWorker(sessionId) {
|
|
234
|
+
const id = clean(sessionId);
|
|
235
|
+
if (!id || views.isSessionBusy(id)) return false;
|
|
236
|
+
const session = mgr.getSession(id);
|
|
237
|
+
if (!session || session.closed) return false;
|
|
238
|
+
const owner = clean(session.ownerSessionId || session.parentSessionId);
|
|
239
|
+
const notifyContext = owner
|
|
240
|
+
? { callerSessionId: owner, ownerSessionId: owner, clientHostPid: session.clientHostPid || null }
|
|
241
|
+
: null;
|
|
242
|
+
void dispatchToExistingSession({ args: {}, session, sessionId: id, prompt: '' }, notifyContext).catch((error) => {
|
|
243
|
+
try {
|
|
244
|
+
process.stderr.write(`[agent] completion wake failed session=${id}: ${error?.message || error}\n`);
|
|
245
|
+
} catch {}
|
|
246
|
+
});
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return { prepareSend, dispatchToExistingSession, wakeIdleWorker };
|
|
228
251
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* flip to running, and the cancellation checks around the turn.
|
|
6
6
|
*/
|
|
7
7
|
import { nonNegativeInt, clearAgentStatuslineRoute } from '../helpers.mjs';
|
|
8
|
+
import { closeManagedSession } from '../../../lifecycle/shared.mjs';
|
|
8
9
|
|
|
9
10
|
export function createDeferredSpawn({
|
|
10
11
|
mgr,
|
|
@@ -22,7 +23,7 @@ export function createDeferredSpawn({
|
|
|
22
23
|
function closePreparedSpawn(prepared, reason = 'agent-task-cancel') {
|
|
23
24
|
if (!prepared?.session?.id) return;
|
|
24
25
|
try {
|
|
25
|
-
Promise.resolve(mgr
|
|
26
|
+
Promise.resolve(closeManagedSession(mgr, prepared.session.id, reason)).catch(() => {});
|
|
26
27
|
} catch {}
|
|
27
28
|
try {
|
|
28
29
|
clearAgentStatuslineRoute(prepared.session.id);
|
|
@@ -7,6 +7,7 @@ import { sanitizeTaskMeta, startBackgroundTask } from '../../../../runtime/share
|
|
|
7
7
|
import { resourceAdmission } from '../../../../runtime/shared/resource-admission.mjs';
|
|
8
8
|
import { clean, terminalPidForContext } from '../helpers.mjs';
|
|
9
9
|
import { renderResult } from '../render.mjs';
|
|
10
|
+
import { closeManagedSession } from '../../../lifecycle/shared.mjs';
|
|
10
11
|
|
|
11
12
|
/** The completion callback that hands a worker's result back to its caller session; null without a handoff. */
|
|
12
13
|
function handoffNotifier(handoffSessionId, notifySessionCompletion) {
|
|
@@ -53,9 +54,9 @@ export function createJobStarter({ mgr, notifyStatusChange, notifySessionComplet
|
|
|
53
54
|
const currentMeta = task?.meta || jobMeta;
|
|
54
55
|
if (currentMeta?.sessionId) {
|
|
55
56
|
try {
|
|
56
|
-
Promise.resolve(
|
|
57
|
-
(
|
|
58
|
-
);
|
|
57
|
+
Promise.resolve(
|
|
58
|
+
closeManagedSession(mgr, currentMeta.sessionId, 'agent-task-cancel', { tombstone: false })
|
|
59
|
+
).catch(() => {});
|
|
59
60
|
} catch {}
|
|
60
61
|
}
|
|
61
62
|
setImmediate(notifyStatusChange);
|
|
@@ -39,9 +39,13 @@ export function createSpawnRunner({
|
|
|
39
39
|
emitSubagentEvent,
|
|
40
40
|
sessionSurface = null,
|
|
41
41
|
createTurnReviewCollector,
|
|
42
|
+
onWorkerCompletionQueued = null,
|
|
42
43
|
}) {
|
|
43
44
|
// Owner/worker completion notification lives in ../notify.mjs.
|
|
44
|
-
const { workerNotifyFn, notifyOwnerAgentCompletionEarly } = createNotify(mgr, {
|
|
45
|
+
const { workerNotifyFn, notifyOwnerAgentCompletionEarly } = createNotify(mgr, {
|
|
46
|
+
notifySessionCompletion,
|
|
47
|
+
onWorkerCompletionQueued,
|
|
48
|
+
});
|
|
45
49
|
|
|
46
50
|
// Idle-progress watchdogs (shared sweep timer) live in
|
|
47
51
|
// agent-watchdog-registry.mjs.
|
|
@@ -32,14 +32,16 @@ export function createTagLookup({ tags, mgr, readWorkerRows, reaper, getLiveSess
|
|
|
32
32
|
const rows = [];
|
|
33
33
|
const seen = new Set();
|
|
34
34
|
const tombstones = reaper.tagTombstoneIndex();
|
|
35
|
-
|
|
35
|
+
// `loaded`: `session` is getLiveSession's own answer (not a catalog
|
|
36
|
+
// summary), so consumers may reuse it instead of loading it again.
|
|
37
|
+
const add = (session, fallbackTag = '', loaded = false) => {
|
|
36
38
|
const tag = agentTagOf(session) || clean(fallbackTag);
|
|
37
39
|
if (!tag || !session?.id || session.closed === true) return;
|
|
38
40
|
if (isLeadPoolAgent(session.agent)) return;
|
|
39
41
|
if (!sessionMatchesContext(session, context)) return;
|
|
40
42
|
if (seen.has(session.id)) return;
|
|
41
43
|
seen.add(session.id);
|
|
42
|
-
rows.push({ tag, session });
|
|
44
|
+
rows.push(loaded ? { tag, session, loaded: true } : { tag, session });
|
|
43
45
|
};
|
|
44
46
|
const addIndexRow = (row) => {
|
|
45
47
|
const tag = clean(row?.tag);
|
|
@@ -70,9 +72,12 @@ export function createTagLookup({ tags, mgr, readWorkerRows, reaper, getLiveSess
|
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
for (const [tag, sessionId] of tags.entries()) {
|
|
75
|
+
// add() drops an id already listed; loading it first (a whole-session
|
|
76
|
+
// read for an idle worker) was pure cost on every status pulse.
|
|
77
|
+
if (seen.has(sessionId)) continue;
|
|
73
78
|
const session = getLiveSession(sessionId);
|
|
74
79
|
if (session && reaper.tombstoneBlocksScan(session, tag, tombstones)) continue;
|
|
75
|
-
add(session, tag);
|
|
80
|
+
add(session, tag, true);
|
|
76
81
|
}
|
|
77
82
|
return rows;
|
|
78
83
|
}
|
|
@@ -112,6 +112,9 @@ export function createTagRegistry({ dataDir, cfgMod, mgr }) {
|
|
|
112
112
|
readWorkerRows,
|
|
113
113
|
writeWorkerRows: index.writeWorkerRows,
|
|
114
114
|
flushWorkerIndexMutations: index.flushWorkerIndexMutations,
|
|
115
|
+
recoverStaleWorkerRows: index.recoverStaleWorkerRows,
|
|
116
|
+
pendingRestartFailures: index.pendingRestartFailures,
|
|
117
|
+
markRestartNotified: index.markRestartNotified,
|
|
115
118
|
upsertWorkerSession: index.upsertWorkerSession,
|
|
116
119
|
upsertWorkerSessionDeferred,
|
|
117
120
|
upsertLeadSession,
|
|
@@ -55,6 +55,9 @@ export function normalizeWorkerRows(value) {
|
|
|
55
55
|
// Why a `failed` row failed (row-liveness, a watchdog stop). Always
|
|
56
56
|
// present, so a later whole-row write clears a stale reason.
|
|
57
57
|
failureReason: clean(row.failureReason) || null,
|
|
58
|
+
// When the owner was told this row's turn died with a process restart;
|
|
59
|
+
// always present, so new work on the row starts unnotified.
|
|
60
|
+
restartNotified: clean(row.restartNotified) || null,
|
|
58
61
|
permission: clean(row.permission) || null,
|
|
59
62
|
toolPermission: clean(row.toolPermission) || null,
|
|
60
63
|
messages: positiveInt(row.messages) || 0,
|
|
@@ -112,6 +115,7 @@ export function workerRowFromSession(session, fallbackTag = '', extra = {}, getS
|
|
|
112
115
|
task_id: clean(extra.task_id || extra.taskId) || null,
|
|
113
116
|
error: clean(extra.error) || null,
|
|
114
117
|
failureReason: clean(extra.failureReason) || null,
|
|
118
|
+
restartNotified: clean(extra.restartNotified) || null,
|
|
115
119
|
permission: clean(session?.permission) || null,
|
|
116
120
|
toolPermission: clean(session?.toolPermission) || null,
|
|
117
121
|
messages: Array.isArray(session?.messages) ? session.messages.length : 0,
|
|
@@ -161,14 +161,19 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
|
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
// Startup recovery: stale active rows settle — as failed when
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
// restart) is
|
|
168
|
-
|
|
164
|
+
// Startup recovery: stale active rows settle — as failed when another
|
|
165
|
+
// process ran their turn (it is gone, or is the predecessor still shutting
|
|
166
|
+
// down: either way the turn never finished), otherwise to idle — and an
|
|
167
|
+
// approval wait left behind by a runtime that is gone (daemon restart) is
|
|
168
|
+
// cleared. `deadOnly` (later sweeps) settles only rows whose runtime is
|
|
169
|
+
// provably dead, never a live process's quiet turn.
|
|
170
|
+
function recoverStaleWorkerRows({ deadOnly = false } = {}) {
|
|
169
171
|
const now = Date.now();
|
|
170
172
|
const rows = store.readAll();
|
|
171
|
-
const
|
|
173
|
+
const isStale = deadOnly
|
|
174
|
+
? (row) => isActiveWorkerRow(row) && runtimeDied(row)
|
|
175
|
+
: (row) => liveness.isStaleActive(row, now);
|
|
176
|
+
const staleActive = rows.filter(isStale);
|
|
172
177
|
const staleWaits = rows.some(isStaleApprovalWait);
|
|
173
178
|
if (!staleActive.length && !staleWaits) return false;
|
|
174
179
|
if (staleActive.length) {
|
|
@@ -176,12 +181,11 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
|
|
|
176
181
|
for (const row of staleActive) {
|
|
177
182
|
const key = workerRowKey(row);
|
|
178
183
|
const current = byKey.get(key);
|
|
179
|
-
if (!current || !
|
|
184
|
+
if (!current || !isStale(current)) continue;
|
|
185
|
+
const ranElsewhere = positiveInt(current.runtimePid) && positiveInt(current.runtimePid) !== process.pid;
|
|
180
186
|
byKey.set(
|
|
181
187
|
key,
|
|
182
|
-
|
|
183
|
-
? liveness.failedRow(current, now, PROCESS_RESTART_FAILURE)
|
|
184
|
-
: liveness.idleRow(current, now)
|
|
188
|
+
ranElsewhere ? liveness.failedRow(current, now, PROCESS_RESTART_FAILURE) : liveness.idleRow(current, now)
|
|
185
189
|
);
|
|
186
190
|
}
|
|
187
191
|
});
|
|
@@ -190,6 +194,34 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
|
|
|
190
194
|
return true;
|
|
191
195
|
}
|
|
192
196
|
|
|
197
|
+
const isUnnotifiedRestartFailure = (row) =>
|
|
198
|
+
row.status === 'failed' &&
|
|
199
|
+
row.failureReason === PROCESS_RESTART_FAILURE &&
|
|
200
|
+
Boolean(clean(row.ownerSessionId)) &&
|
|
201
|
+
!row.restartNotified;
|
|
202
|
+
|
|
203
|
+
/** Rows whose turn a process restart ended and whose owner has not been told
|
|
204
|
+
* yet — whoever settled the row (this process at startup, or the
|
|
205
|
+
* predecessor's exit flush). Read-only: the caller enqueues the owner's
|
|
206
|
+
* notification durably, THEN calls `markRestartNotified`, so a crash in
|
|
207
|
+
* between retries (the notification's stable id makes the retry exactly-once). */
|
|
208
|
+
function pendingRestartFailures() {
|
|
209
|
+
return store.readAll().filter(isUnnotifiedRestartFailure);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Stamp these rows as told, once their notification is durably queued. */
|
|
213
|
+
function markRestartNotified(rows) {
|
|
214
|
+
const stamp = new Date().toISOString();
|
|
215
|
+
store.write((byKey) => {
|
|
216
|
+
for (const row of rows) {
|
|
217
|
+
const key = workerRowKey(row);
|
|
218
|
+
const current = byKey.get(key);
|
|
219
|
+
if (!current || !isUnnotifiedRestartFailure(current)) continue;
|
|
220
|
+
byKey.set(key, { ...current, restartNotified: stamp });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
193
225
|
function flushWorkerIndexOnExit() {
|
|
194
226
|
batch.flush();
|
|
195
227
|
flushActiveWorkerRows();
|
|
@@ -285,5 +317,7 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
|
|
|
285
317
|
flushActiveWorkerRows,
|
|
286
318
|
flushWorkerIndexOnExit,
|
|
287
319
|
recoverStaleWorkerRows,
|
|
320
|
+
pendingRestartFailures,
|
|
321
|
+
markRestartNotified,
|
|
288
322
|
};
|
|
289
323
|
}
|
|
@@ -9,6 +9,7 @@ import { liveAgentStatusRow } from './agent-tool/job-views/session-progress.mjs'
|
|
|
9
9
|
import { createSpawnFlow } from './agent-tool/spawn-flow.mjs';
|
|
10
10
|
import { createSendFlow } from './agent-tool/send-flow.mjs';
|
|
11
11
|
import { createCloseFlow } from './agent-tool/close-flow.mjs';
|
|
12
|
+
import { createRestartFailureNotifier } from './agent-tool/restart-notify.mjs';
|
|
12
13
|
import { createTerminalTrace } from './agent-tool/terminal-trace.mjs';
|
|
13
14
|
import { createAgentExecute } from './agent-tool/execute.mjs';
|
|
14
15
|
import { createTurnReviewCollector } from './agent-tool/turn-review.mjs';
|
|
@@ -70,8 +71,24 @@ export function createStandaloneAgent({
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
const registry = createTagRegistry({ dataDir, cfgMod, mgr });
|
|
74
|
+
// Tasks the previous process was running died with it: tell their owners.
|
|
75
|
+
// Later sweeps also catch rows the predecessor settled as it shut down.
|
|
76
|
+
const notifyRestartFailures = createRestartFailureNotifier({
|
|
77
|
+
pendingRestartFailures: registry.pendingRestartFailures,
|
|
78
|
+
markRestartNotified: registry.markRestartNotified,
|
|
79
|
+
mgr,
|
|
80
|
+
notifySessionCompletion,
|
|
81
|
+
});
|
|
82
|
+
const sweepRestartFailures = () => {
|
|
83
|
+
registry.recoverStaleWorkerRows({ deadOnly: true });
|
|
84
|
+
notifyRestartFailures();
|
|
85
|
+
};
|
|
86
|
+
notifyRestartFailures();
|
|
73
87
|
const views = createJobViews({ ...registry, mgr, reg, cfgMod, DEFAULT_SPAWN_PREP_TIMEOUT_MS });
|
|
88
|
+
// Late-bound: the send flow that wakes an idle worker is built after spawn.
|
|
89
|
+
let wakeIdleWorker = null;
|
|
74
90
|
const spawnFlow = createSpawnFlow({
|
|
91
|
+
onWorkerCompletionQueued: (sessionId) => wakeIdleWorker?.(sessionId),
|
|
75
92
|
...registry,
|
|
76
93
|
...views,
|
|
77
94
|
mgr,
|
|
@@ -96,6 +113,7 @@ export function createStandaloneAgent({
|
|
|
96
113
|
views,
|
|
97
114
|
spawnFlow,
|
|
98
115
|
});
|
|
116
|
+
wakeIdleWorker = sendFlow.wakeIdleWorker;
|
|
99
117
|
const closeFlow = createCloseFlow({ mgr, registry, views });
|
|
100
118
|
const terminalTrace = createTerminalTrace({ registry });
|
|
101
119
|
const execute = createAgentExecute({
|
|
@@ -116,6 +134,7 @@ export function createStandaloneAgent({
|
|
|
116
134
|
void awaitKeychainPrewarm();
|
|
117
135
|
return { workers: [], jobs: [], scope: null };
|
|
118
136
|
}
|
|
137
|
+
sweepRestartFailures();
|
|
119
138
|
const scopedContext = agentScope({}, context);
|
|
120
139
|
const ownerSession = callerSessionForContext(scopedContext);
|
|
121
140
|
const pid = terminalPidForContext(scopedContext);
|
|
@@ -134,6 +153,7 @@ export function createStandaloneAgent({
|
|
|
134
153
|
void awaitKeychainPrewarm();
|
|
135
154
|
return [];
|
|
136
155
|
}
|
|
156
|
+
sweepRestartFailures();
|
|
137
157
|
const scopedContext = agentScope({ recover: true }, context);
|
|
138
158
|
registry.refreshTagsFromSessions({ scanSessions: true, context: scopedContext });
|
|
139
159
|
return views.list({ scanSessions: false, context: scopedContext });
|
|
@@ -64,6 +64,15 @@ import {
|
|
|
64
64
|
beginOAuthLogin as beginCommandCodeOAuthLogin,
|
|
65
65
|
loginOAuth as loginCommandCodeOAuth,
|
|
66
66
|
} from '../../runtime/agent/orchestrator/providers/command-code-oauth-login.mjs';
|
|
67
|
+
import {
|
|
68
|
+
describeOpenCodeGoOAuthCredentials,
|
|
69
|
+
forgetOpenCodeGoOAuthCredentials,
|
|
70
|
+
hasOpenCodeGoOAuthCredentials,
|
|
71
|
+
} from '../../runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs';
|
|
72
|
+
import {
|
|
73
|
+
beginOAuthLogin as beginOpenCodeGoOAuthLogin,
|
|
74
|
+
loginOAuth as loginOpenCodeGoOAuth,
|
|
75
|
+
} from '../../runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs';
|
|
67
76
|
import {
|
|
68
77
|
beginCursorOAuthLogin,
|
|
69
78
|
describeCursorOAuthCredentials,
|
|
@@ -101,7 +110,6 @@ import {
|
|
|
101
110
|
} from '../../runtime/agent/orchestrator/providers/custom-provider-config.mjs';
|
|
102
111
|
|
|
103
112
|
const API_PROVIDERS = Object.freeze([
|
|
104
|
-
Object.freeze({ id: 'opencode-go', name: 'OpenCode Go API', env: 'OPENCODE_API_KEY', url: 'https://opencode.ai' }),
|
|
105
113
|
Object.freeze({ id: 'openrouter', name: 'OpenRouter', env: 'OPENROUTER_API_KEY', url: 'https://openrouter.ai/keys' }),
|
|
106
114
|
Object.freeze({
|
|
107
115
|
id: 'openai',
|
|
@@ -176,6 +184,16 @@ const OAUTH_PROVIDERS = Object.freeze([
|
|
|
176
184
|
begin: beginCommandCodeOAuthLogin,
|
|
177
185
|
login: loginCommandCodeOAuth,
|
|
178
186
|
}),
|
|
187
|
+
Object.freeze({
|
|
188
|
+
id: 'opencode-go-oauth',
|
|
189
|
+
name: 'OpenCode Go OAuth',
|
|
190
|
+
desc: 'Sign in with your OpenCode account (OpenCode Go subscription)',
|
|
191
|
+
has: hasOpenCodeGoOAuthCredentials,
|
|
192
|
+
describe: describeOpenCodeGoOAuthCredentials,
|
|
193
|
+
forget: forgetOpenCodeGoOAuthCredentials,
|
|
194
|
+
begin: beginOpenCodeGoOAuthLogin,
|
|
195
|
+
login: loginOpenCodeGoOAuth,
|
|
196
|
+
}),
|
|
179
197
|
Object.freeze({
|
|
180
198
|
id: 'mimo-oauth',
|
|
181
199
|
name: 'MiMo',
|
|
@@ -381,7 +399,7 @@ export function isCustomProviderConfig(entry) {
|
|
|
381
399
|
return Boolean(entry) && typeof entry === 'object' && entry.type === 'custom';
|
|
382
400
|
}
|
|
383
401
|
|
|
384
|
-
|
|
402
|
+
function isCustomProviderId(id) {
|
|
385
403
|
return CUSTOM_ID_RE.test(String(id || ''));
|
|
386
404
|
}
|
|
387
405
|
|
|
@@ -552,7 +570,11 @@ async function modelProxyProviderSetup(options) {
|
|
|
552
570
|
// Signed in but switched off on the desktop.
|
|
553
571
|
else if (!enabled && authState === 'present') status = 'Off';
|
|
554
572
|
return {
|
|
555
|
-
id,
|
|
573
|
+
id,
|
|
574
|
+
name,
|
|
575
|
+
group,
|
|
576
|
+
type,
|
|
577
|
+
enabled,
|
|
556
578
|
authenticated,
|
|
557
579
|
usable,
|
|
558
580
|
...(group === 'local' ? { detected: enabled } : {}),
|
|
@@ -847,6 +869,10 @@ export function listProviderAccounts(provider) {
|
|
|
847
869
|
let identity = null;
|
|
848
870
|
if (typeof auth.email === 'string' && auth.email.trim()) identity = auth.email.trim();
|
|
849
871
|
else if (typeof auth.accountId === 'string' && auth.accountId.trim()) identity = auth.accountId.trim();
|
|
872
|
+
// An organization-scoped login names the organization it acts for.
|
|
873
|
+
if (typeof auth.orgName === 'string' && auth.orgName.trim()) {
|
|
874
|
+
identity = identity ? `${identity} · ${auth.orgName.trim()}` : auth.orgName.trim();
|
|
875
|
+
}
|
|
850
876
|
return {
|
|
851
877
|
id: row.id,
|
|
852
878
|
label: row.label,
|
|
@@ -1128,6 +1154,69 @@ export async function updateCustomProviderModel(cfgMod, rawId, rawModelId, patch
|
|
|
1128
1154
|
return customProviderRow(id, saved);
|
|
1129
1155
|
}
|
|
1130
1156
|
|
|
1157
|
+
const MODEL_PREFERENCE_KEYS = new Set(['model', 'hidden', 'favorite']);
|
|
1158
|
+
export const MODEL_PREFERENCE_CHANGES_MAX = 5_000;
|
|
1159
|
+
|
|
1160
|
+
function modelPreferenceChanges(raw) {
|
|
1161
|
+
if (!Array.isArray(raw) || raw.length > MODEL_PREFERENCE_CHANGES_MAX) {
|
|
1162
|
+
throw new TypeError('model preference changes are invalid');
|
|
1163
|
+
}
|
|
1164
|
+
return raw.map((change) => {
|
|
1165
|
+
plainPatch(change, MODEL_PREFERENCE_KEYS, 'model preference change');
|
|
1166
|
+
const model = typeof change.model === 'string' ? change.model.trim() : '';
|
|
1167
|
+
if (!model) throw new TypeError('model id is required');
|
|
1168
|
+
for (const key of ['hidden', 'favorite']) {
|
|
1169
|
+
if (change[key] !== undefined && typeof change[key] !== 'boolean') {
|
|
1170
|
+
throw new TypeError(`model preference ${key} is invalid`);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
return { model, hidden: change.hidden, favorite: change.favorite };
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* The model management card's save for one provider: whether each model is
|
|
1179
|
+
* listed in /model (`hidden`) and whether it is a favorite. Both live in the
|
|
1180
|
+
* shared per-model store `modelSettings["provider/model"]` — except a custom
|
|
1181
|
+
* provider's visibility, which stays its own `modelOverrides[id].hidden`, the
|
|
1182
|
+
* store its edit dialog, the runtime catalog and SSH servers already honor.
|
|
1183
|
+
* `hidden: false` is an explicit choice (it also lists a model the family
|
|
1184
|
+
* limit would leave out); an absent field keeps the stored value.
|
|
1185
|
+
*/
|
|
1186
|
+
export function saveModelPreferences(cfgMod, rawProvider, rawChanges) {
|
|
1187
|
+
const provider = String(rawProvider || '').trim();
|
|
1188
|
+
const changes = modelPreferenceChanges(rawChanges);
|
|
1189
|
+
const full = cfgMod.loadConfig();
|
|
1190
|
+
if (!isKnownProvider(provider, full)) throw new TypeError(`unknown provider "${provider}"`);
|
|
1191
|
+
const custom = isCustomProviderId(provider);
|
|
1192
|
+
const modelSettings = { ...(full.modelSettings || {}) };
|
|
1193
|
+
for (const { model, hidden, favorite } of changes) {
|
|
1194
|
+
const key = `${provider}/${model}`;
|
|
1195
|
+
const setting = { ...(modelSettings[key] || {}) };
|
|
1196
|
+
if (hidden !== undefined && !custom) setting.hidden = hidden;
|
|
1197
|
+
if (favorite === true) setting.favorite = true;
|
|
1198
|
+
else if (favorite === false) delete setting.favorite;
|
|
1199
|
+
if (Object.keys(setting).length) modelSettings[key] = setting;
|
|
1200
|
+
else delete modelSettings[key];
|
|
1201
|
+
}
|
|
1202
|
+
const next = { ...full, modelSettings };
|
|
1203
|
+
const customHidden = custom ? changes.filter((change) => change.hidden !== undefined) : [];
|
|
1204
|
+
if (customHidden.length) {
|
|
1205
|
+
requireLocalProviderManagement();
|
|
1206
|
+
const entry = full.providers[provider];
|
|
1207
|
+
const current = normalizeStoredCustomConfig(entry);
|
|
1208
|
+
const overrides = new Map(Object.entries(current.modelOverrides || {}));
|
|
1209
|
+
for (const { model, hidden } of customHidden) overrides.set(model, { ...overrides.get(model), hidden });
|
|
1210
|
+
const config = normalizeStoredCustomConfig({ ...current, modelOverrides: Object.fromEntries(overrides) });
|
|
1211
|
+
next.providers = {
|
|
1212
|
+
...full.providers,
|
|
1213
|
+
[provider]: { ...config, type: 'custom', enabled: entry.enabled !== false },
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
cfgMod.saveConfig(next, { baseConfig: full });
|
|
1217
|
+
return { provider, changed: changes.length, customCatalogChanged: customHidden.length > 0 };
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1131
1220
|
// Drops every stored model setting and route that names the provider.
|
|
1132
1221
|
function withoutProviderReferences(config, id) {
|
|
1133
1222
|
const next = { ...config };
|
|
@@ -1235,23 +1324,6 @@ export async function saveOpenAIUsageSessionKey(cfgMod, secret) {
|
|
|
1235
1324
|
return { provider: 'openai', type: 'usage-auth', authenticated: true };
|
|
1236
1325
|
}
|
|
1237
1326
|
|
|
1238
|
-
export async function saveOpenCodeGoUsageAuth(cfgMod, { apiKey } = {}) {
|
|
1239
|
-
requireLocalProviderManagement();
|
|
1240
|
-
const secret = String(apiKey || '').trim();
|
|
1241
|
-
if (!secret) throw new Error('OpenCode console API key is required for usage lookup');
|
|
1242
|
-
// Subscription meters are read with a console service-account key holding the
|
|
1243
|
-
// `all` permission. The console accepts no other credential for them, so
|
|
1244
|
-
// rejecting anything else here keeps unusable auth out of the keychain.
|
|
1245
|
-
if (!/^(?:oc_sk_|sk-)/.test(secret))
|
|
1246
|
-
throw new Error('OpenCode usage auth must be a console API key (oc_sk_… or sk-…) with all permissions');
|
|
1247
|
-
await saveSecretAsync(SECRET_ACCOUNTS.opencodeGoConsoleKey, secret);
|
|
1248
|
-
// Usage auth may be a different key from the inference key. Only flip the
|
|
1249
|
-
// model provider on when a model key actually exists; otherwise usage-only
|
|
1250
|
-
// setups get routed to the provider with apiKey 'no-key' and fail with 401s.
|
|
1251
|
-
if (getAgentApiKey('opencode-go')) updateConfigProvider(cfgMod, 'opencode-go', { enabled: true });
|
|
1252
|
-
return { provider: 'opencode-go', type: 'usage-auth', authenticated: true };
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
1327
|
export async function forgetProviderAuth(cfgModOrProvider, maybeProvider, requestedAccountId) {
|
|
1256
1328
|
requireLocalProviderManagement();
|
|
1257
1329
|
const cfgMod = maybeProvider === undefined ? null : cfgModOrProvider;
|
|
@@ -242,7 +242,7 @@ function providerDescription(id, group) {
|
|
|
242
242
|
return 'MiMo Token Plan subscription quota';
|
|
243
243
|
case 'antigravity-oauth':
|
|
244
244
|
return 'Antigravity subscription quota';
|
|
245
|
-
case 'opencode-go':
|
|
245
|
+
case 'opencode-go-oauth':
|
|
246
246
|
return 'OpenCode Go subscription quota';
|
|
247
247
|
case 'openai':
|
|
248
248
|
return 'OpenAI API billing';
|
|
@@ -349,7 +349,7 @@ const PROVIDER_RANKS = {
|
|
|
349
349
|
'mimo-oauth': 34,
|
|
350
350
|
'cursor-oauth': 35,
|
|
351
351
|
'antigravity-oauth': 36,
|
|
352
|
-
'opencode-go':
|
|
352
|
+
'opencode-go-oauth': 38,
|
|
353
353
|
openai: 50,
|
|
354
354
|
anthropic: 60,
|
|
355
355
|
deepseek: 70,
|