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
|
@@ -2,11 +2,6 @@ import {
|
|
|
2
2
|
fetchOAuthUsageSnapshot,
|
|
3
3
|
readCachedOAuthUsageSnapshot,
|
|
4
4
|
} from '../../runtime/agent/orchestrator/providers/oauth-usage.mjs';
|
|
5
|
-
import {
|
|
6
|
-
fetchOpenCodeGoUsageSnapshot,
|
|
7
|
-
openCodeGoUsageConfigStatus,
|
|
8
|
-
readCachedOpenCodeGoUsageSnapshot,
|
|
9
|
-
} from '../../runtime/agent/orchestrator/providers/opencode-go-usage.mjs';
|
|
10
5
|
import {
|
|
11
6
|
fetchApiUsageSnapshot,
|
|
12
7
|
readCachedApiUsageSnapshot,
|
|
@@ -118,69 +113,32 @@ async function apiProviderRow(item, { config, providers, preview, refreshFor, sn
|
|
|
118
113
|
if (row.authenticated) {
|
|
119
114
|
let hasQuota = false;
|
|
120
115
|
let apiUsageSnapshot = null;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
row.status = 'missing';
|
|
148
|
-
row.source = 'usage-auth-missing';
|
|
149
|
-
row.sourceLabel = 'usage auth';
|
|
150
|
-
row.primary = '';
|
|
151
|
-
row.detail = usageStatus.ready
|
|
152
|
-
? 'OpenCode Go usage not found'
|
|
153
|
-
: 'Set an OpenCode console API key (all permissions) for usage';
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
} else {
|
|
157
|
-
const snapshot = await apiSnapshot(item.id, snapshotOptions(item.id));
|
|
158
|
-
apiUsageSnapshot = snapshot;
|
|
159
|
-
const known = snapshotRemaining(snapshot);
|
|
160
|
-
const usage = snapshotUsage(snapshot);
|
|
161
|
-
const tokenUsage = snapshotTokenUsage(snapshot);
|
|
162
|
-
const windows = normaliseWindows(snapshot?.quotaWindows, clean(snapshot?.source) || 'provider-api');
|
|
163
|
-
const estimated = localBudget(providerCfg);
|
|
164
|
-
row.updatedAt = num(snapshot?.cachedAt, null);
|
|
165
|
-
if (known) {
|
|
166
|
-
applyKnownRemaining(row, known, { estimated: false });
|
|
167
|
-
row.sourceLabel = 'API';
|
|
168
|
-
hasQuota = true;
|
|
169
|
-
} else if (windows.length) {
|
|
170
|
-
hasQuota = applyWindowQuota(row, windows, {
|
|
171
|
-
source: clean(snapshot?.source) || 'provider-api',
|
|
172
|
-
detail: 'provider quota',
|
|
173
|
-
});
|
|
174
|
-
} else if (estimated) {
|
|
175
|
-
applyKnownRemaining(row, estimated, { estimated: true });
|
|
176
|
-
hasQuota = true;
|
|
177
|
-
} else if (usage) {
|
|
178
|
-
applyKnownUsage(row, usage);
|
|
179
|
-
hasQuota = true;
|
|
180
|
-
} else if (tokenUsage) {
|
|
181
|
-
applyTokenUsage(row, tokenUsage);
|
|
182
|
-
hasQuota = true;
|
|
183
|
-
}
|
|
116
|
+
const snapshot = await apiSnapshot(item.id, snapshotOptions(item.id));
|
|
117
|
+
apiUsageSnapshot = snapshot;
|
|
118
|
+
const known = snapshotRemaining(snapshot);
|
|
119
|
+
const usage = snapshotUsage(snapshot);
|
|
120
|
+
const tokenUsage = snapshotTokenUsage(snapshot);
|
|
121
|
+
const windows = normaliseWindows(snapshot?.quotaWindows, clean(snapshot?.source) || 'provider-api');
|
|
122
|
+
const estimated = localBudget(providerCfg);
|
|
123
|
+
row.updatedAt = num(snapshot?.cachedAt, null);
|
|
124
|
+
if (known) {
|
|
125
|
+
applyKnownRemaining(row, known, { estimated: false });
|
|
126
|
+
row.sourceLabel = 'API';
|
|
127
|
+
hasQuota = true;
|
|
128
|
+
} else if (windows.length) {
|
|
129
|
+
hasQuota = applyWindowQuota(row, windows, {
|
|
130
|
+
source: clean(snapshot?.source) || 'provider-api',
|
|
131
|
+
detail: 'provider quota',
|
|
132
|
+
});
|
|
133
|
+
} else if (estimated) {
|
|
134
|
+
applyKnownRemaining(row, estimated, { estimated: true });
|
|
135
|
+
hasQuota = true;
|
|
136
|
+
} else if (usage) {
|
|
137
|
+
applyKnownUsage(row, usage);
|
|
138
|
+
hasQuota = true;
|
|
139
|
+
} else if (tokenUsage) {
|
|
140
|
+
applyTokenUsage(row, tokenUsage);
|
|
141
|
+
hasQuota = true;
|
|
184
142
|
}
|
|
185
143
|
if (!row.includeInTotal && !hasQuota) {
|
|
186
144
|
const estimated = localBudget(providerCfg);
|
|
@@ -189,7 +147,7 @@ async function apiProviderRow(item, { config, providers, preview, refreshFor, sn
|
|
|
189
147
|
hasQuota = true;
|
|
190
148
|
}
|
|
191
149
|
}
|
|
192
|
-
if (
|
|
150
|
+
if (!row.includeInTotal && !hasQuota) {
|
|
193
151
|
applyApiUnavailable(row, item.id, apiUsageSnapshot);
|
|
194
152
|
}
|
|
195
153
|
}
|
|
@@ -105,9 +105,9 @@ export function createSessionOps(deps) {
|
|
|
105
105
|
return mode;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
function interruptTaskWait(reason = 'user-message') {
|
|
108
|
+
function interruptTaskWait(reason = 'user-message', detail = null) {
|
|
109
109
|
const sessionId = getSession()?.id || getReservedSessionId?.() || '';
|
|
110
|
-
return interruptTaskWaitForSession(sessionId, reason);
|
|
110
|
+
return interruptTaskWaitForSession(sessionId, reason, detail);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
// Raw model-message read for LIVE transcript consumers (remote agent
|
|
@@ -86,6 +86,7 @@ export function createAskCallbacks({ transcript, notifyFnForSession, scheduleSea
|
|
|
86
86
|
},
|
|
87
87
|
onUsageDelta: options.onUsageDelta,
|
|
88
88
|
onAssistantToolCallObserved: options.onAssistantToolCallObserved,
|
|
89
|
+
onStreamItemStart: options.onStreamItemStart,
|
|
89
90
|
onToolResult: (message) => options.onToolResult?.(message),
|
|
90
91
|
onToolApproval: options.onToolApproval,
|
|
91
92
|
onCompactEvent: options.onCompactEvent,
|
|
@@ -237,7 +237,8 @@ export function createUsageStatsApi({
|
|
|
237
237
|
/**
|
|
238
238
|
* This runtime's session from its first request, compactions included; null before it has one.
|
|
239
239
|
* `sessionIds` adds the same totals for each named session (the agents it spawned) under
|
|
240
|
-
* `sessions`, each read by its own id only.
|
|
240
|
+
* `sessions`, each read by its own id only. `agents: true` adds `agents`: one total of every
|
|
241
|
+
* session ever run for this one (closed and replaced agents included) and the named sessions.
|
|
241
242
|
*/
|
|
242
243
|
async getSessionUsage(options = {}) {
|
|
243
244
|
const sessionId = getSessionId();
|
|
@@ -249,6 +250,10 @@ export function createUsageStatsApi({
|
|
|
249
250
|
const usage = await store.sessionUsageAsync(sessionUsageIdsFor(scopes, sessionId));
|
|
250
251
|
const result = { sessionId, ...sessionUsageSnapshot(usage) };
|
|
251
252
|
const others = otherSessionIds(options, sessionId);
|
|
253
|
+
if (options?.agents === true) {
|
|
254
|
+
const named = others.flatMap((id) => sessionUsageIdsFor(scopes, id));
|
|
255
|
+
result.agents = sessionUsageSnapshot(await store.sessionUsageAsync(named, [sessionId]));
|
|
256
|
+
}
|
|
252
257
|
if (!others.length) return result;
|
|
253
258
|
const totals = await Promise.all(
|
|
254
259
|
others.map(async (id) => [
|
|
@@ -31,9 +31,10 @@ export function createChannelRoutes({
|
|
|
31
31
|
// Checked on every register/call/agent dispatch, first and again right
|
|
32
32
|
// before the work is handed on (the lease may have been fenced while the
|
|
33
33
|
// body was still arriving): a daemon that lost (or cannot re-verify) its
|
|
34
|
-
// owner lease serves nothing.
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
// owner lease serves nothing. `admitWork` may answer asynchronously (a
|
|
35
|
+
// request arriving during the lease's re-verification waits for it).
|
|
36
|
+
async function refuseUnadmitted(res) {
|
|
37
|
+
if (await admitWork()) return false;
|
|
37
38
|
sendError(res, 'daemon owner lease is not held', 503);
|
|
38
39
|
return true;
|
|
39
40
|
}
|
|
@@ -54,10 +55,10 @@ export function createChannelRoutes({
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
async function register(req, res) {
|
|
57
|
-
if (refuseUnadmitted(res)) return;
|
|
58
|
+
if (await refuseUnadmitted(res)) return;
|
|
58
59
|
if (state.drainCommitted) return sendDraining(res);
|
|
59
60
|
const body = await readChannelBody(req);
|
|
60
|
-
if (refuseUnadmitted(res)) return;
|
|
61
|
+
if (await refuseUnadmitted(res)) return;
|
|
61
62
|
const clientToken = registry.registerClient({
|
|
62
63
|
leadPid: body.leadPid,
|
|
63
64
|
cwd: body.cwd,
|
|
@@ -96,11 +97,11 @@ export function createChannelRoutes({
|
|
|
96
97
|
// channels client registry alive. The broker itself owns a parallel fair
|
|
97
98
|
// scheduler and per-call cancellation.
|
|
98
99
|
async function agentDispatch(req, res) {
|
|
99
|
-
if (refuseUnadmitted(res)) return;
|
|
100
|
+
if (await refuseUnadmitted(res)) return;
|
|
100
101
|
if (state.drainCommitted) return sendDraining(res);
|
|
101
102
|
if (!agentBroker?.dispatch) return sendError(res, 'agent broker unavailable', 503);
|
|
102
103
|
const body = await readChannelBody(req);
|
|
103
|
-
if (refuseUnadmitted(res)) return;
|
|
104
|
+
if (await refuseUnadmitted(res)) return;
|
|
104
105
|
const callId = String(body.callId || '').trim();
|
|
105
106
|
if (!callId) return sendError(res, 'callId required', 400);
|
|
106
107
|
res.on('close', () => {
|
|
@@ -130,10 +131,10 @@ export function createChannelRoutes({
|
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
async function call(req, res) {
|
|
133
|
-
if (refuseUnadmitted(res)) return;
|
|
134
|
+
if (await refuseUnadmitted(res)) return;
|
|
134
135
|
if (state.drainCommitted) return sendDraining(res);
|
|
135
136
|
const body = await readChannelBody(req);
|
|
136
|
-
if (refuseUnadmitted(res)) return;
|
|
137
|
+
if (await refuseUnadmitted(res)) return;
|
|
137
138
|
const clientToken = body.token || null;
|
|
138
139
|
const c = clientToken ? clients.get(clientToken) : null;
|
|
139
140
|
if (!c) return sendError(res, 'unknown client token', 404);
|
|
@@ -12,22 +12,33 @@
|
|
|
12
12
|
* this daemon's in-memory turn activity (every session runs inline here, so
|
|
13
13
|
* it is the single source of truth; worker index files only declare work). */
|
|
14
14
|
async function loadSessionStoreWithRuntimeActivity() {
|
|
15
|
-
const [store, runtime, progress] = await Promise.all([
|
|
15
|
+
const [store, runtime, progress, tasks] = await Promise.all([
|
|
16
16
|
import('../runtime/agent/orchestrator/session/store-summary-reader.mjs'),
|
|
17
17
|
// The import-free leaf holding the runtime map: no session manager graph.
|
|
18
18
|
import('../runtime/agent/orchestrator/session/manager/runtime-state.mjs'),
|
|
19
19
|
import('../runtime/agent/orchestrator/session/agent-pool-progress.mjs'),
|
|
20
|
+
import('../runtime/shared/background-tasks.mjs'),
|
|
20
21
|
]);
|
|
21
22
|
return {
|
|
22
23
|
...store,
|
|
23
24
|
// Running rows also carry their turn's progress stamp and stage (the
|
|
24
|
-
// quiet-time cue), read from the same runtime map
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
// quiet-time cue), read from the same runtime map; an idle row whose
|
|
26
|
+
// agent handed off background shell work that still runs reads waiting.
|
|
27
|
+
// The handed-off shell work of every row comes from one task scan. The
|
|
28
|
+
// store is read with awaited I/O (a listing runs on every activity
|
|
29
|
+
// change); the running set is taken once those reads are done.
|
|
30
|
+
listStoredAgentWorkers: async () => {
|
|
31
|
+
const rows = await store.listStoredAgentWorkersAsync({
|
|
32
|
+
runningSessionIds: () => runtime.inFlightSessionActivity().working,
|
|
33
|
+
});
|
|
34
|
+
const awaiting = tasks.activeBackgroundTaskSessionIds({ surface: 'shell', awaitedOnly: true });
|
|
35
|
+
return progress.withTurnProgress(
|
|
36
|
+
rows,
|
|
28
37
|
(sessionId) => runtime.runtimeState.get(sessionId),
|
|
29
|
-
runtime.isTurnInFlight
|
|
30
|
-
|
|
38
|
+
runtime.isTurnInFlight,
|
|
39
|
+
(sessionId) => awaiting.has(sessionId)
|
|
40
|
+
);
|
|
41
|
+
},
|
|
31
42
|
};
|
|
32
43
|
}
|
|
33
44
|
|
|
@@ -53,6 +64,8 @@ export function createDesktopRuntime({ getLocalSessionBridge }) {
|
|
|
53
64
|
loadStatuslineSegments: () => import('../runtime/shared/statusline/statusline-segments.mjs'),
|
|
54
65
|
loadConfig: () => import('../runtime/shared/config.mjs'),
|
|
55
66
|
loadDocumentPreview: () => import('../runtime/office/pdf/document-preview.mjs'),
|
|
67
|
+
// The desktop service's git children start on this worker, off the loop.
|
|
68
|
+
loadProcessRunner: () => import('../runtime/shared/process-runner.mjs'),
|
|
56
69
|
async executeCodeGraphTool(name, args, cwd) {
|
|
57
70
|
const graph = await import('../runtime/agent/orchestrator/tools/code-graph/dispatch.mjs');
|
|
58
71
|
return graph.executeCodeGraphTool(name, args, cwd);
|
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
// being renewed, and every register/call asks `admit()` first. After an
|
|
16
16
|
// event-loop stall nothing is served until a refresh under the claim lock
|
|
17
17
|
// proves the record still names it — a contender's claim takes that lock.
|
|
18
|
+
// A lease that ran out DURING such a stall (live when the loop stopped) is
|
|
19
|
+
// the one exception to the fence: the holder never had a turn to refresh
|
|
20
|
+
// or to fence, and a contender's reclaim is decided under the claim lock
|
|
21
|
+
// against the record's heartbeat there (daemonOwnerIsStale re-checks it),
|
|
22
|
+
// so a refresh that finds the record still naming this pid proves no
|
|
23
|
+
// contender took it and none can on its earlier proof. Without that, a sole
|
|
24
|
+
// daemon whose loop the machine starved past the lease killed itself and
|
|
25
|
+
// every running turn with no other daemon to take over.
|
|
18
26
|
// - Holder's record names `livenessPort` from its very first claim: a tiny
|
|
19
27
|
// loopback listener started BEFORE the claim (startOwnerLivenessListener)
|
|
20
28
|
// answers /health with the pid, through boot and for the process lifetime.
|
|
@@ -32,8 +40,9 @@
|
|
|
32
40
|
// daemon.json publishes, answering with that pid, proves them alive.
|
|
33
41
|
import http from 'node:http';
|
|
34
42
|
import { readFileSync } from 'node:fs';
|
|
43
|
+
import { readFile } from 'node:fs/promises';
|
|
35
44
|
import { isPidAlive } from '../runtime/shared/pid-liveness.mjs';
|
|
36
|
-
import { readSingletonOwner,
|
|
45
|
+
import { readSingletonOwner, refreshSingletonOwnerAsync } from '../runtime/shared/singleton-owner.mjs';
|
|
37
46
|
|
|
38
47
|
export const DAEMON_OWNER_STALE_MS = 60_000;
|
|
39
48
|
export const OWNER_PROBE_TIMEOUT_MS = 3_000;
|
|
@@ -58,11 +67,23 @@ export function daemonOwnerTimings(env = process.env) {
|
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
/** daemon.json's pid and session endpoint, or null when missing/unreadable. */
|
|
70
|
+
function discoveryOf(parsed) {
|
|
71
|
+
const session = parsed?.endpoints?.session;
|
|
72
|
+
return { pid: Number(parsed?.pid) || 0, port: Number(session?.port) || 0, token: session?.token || '' };
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
export function readDiscovery(discoveryPath) {
|
|
62
76
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return
|
|
77
|
+
return discoveryOf(JSON.parse(readFileSync(discoveryPath, 'utf8')));
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** readDiscovery without blocking the event loop. */
|
|
84
|
+
export async function readDiscoveryAsync(discoveryPath) {
|
|
85
|
+
try {
|
|
86
|
+
return discoveryOf(JSON.parse(await readFile(discoveryPath, 'utf8')));
|
|
66
87
|
} catch {
|
|
67
88
|
return null;
|
|
68
89
|
}
|
|
@@ -243,7 +264,10 @@ export function daemonOwnerIsStale(
|
|
|
243
264
|
* `tickMs` it checks the lease, notices stalls, and every `heartbeatMs`
|
|
244
265
|
* rewrites `owner` with a fresh wall-clock `heartbeatAt` under the claim lock
|
|
245
266
|
* (recreating a deleted record), then calls `onBeat`.
|
|
246
|
-
* - No successful refresh for `fenceMs`: fenced, never renewed (`onFenced`)
|
|
267
|
+
* - No successful refresh for `fenceMs`: fenced, never renewed (`onFenced`) —
|
|
268
|
+
* unless the lease ran out inside one stall (live when the loop stopped):
|
|
269
|
+
* then the refresh is retried every tick for `heartbeatMs` after the loop
|
|
270
|
+
* resumes, and fenced only if none succeeds by then.
|
|
247
271
|
* - The record names another pid: `onSuperseded(record)`.
|
|
248
272
|
* - A turn gap over `stallMs`: `admit()` refuses work until a refresh succeeds.
|
|
249
273
|
* The gap is logged once, with `describeStall(fromMono, toMono)` appended.
|
|
@@ -258,7 +282,7 @@ export function createOwnerHeartbeat({
|
|
|
258
282
|
stallMs,
|
|
259
283
|
now = Date.now,
|
|
260
284
|
monoNow = () => performance.now(),
|
|
261
|
-
refresh =
|
|
285
|
+
refresh = refreshSingletonOwnerAsync,
|
|
262
286
|
log = () => {},
|
|
263
287
|
describeStall = null,
|
|
264
288
|
onBeat = () => {},
|
|
@@ -272,6 +296,13 @@ export function createOwnerHeartbeat({
|
|
|
272
296
|
let lastBeatAt = leaseFrom;
|
|
273
297
|
let lastTurnAt = leaseFrom;
|
|
274
298
|
let needsVerify = false;
|
|
299
|
+
// Monotonic time the loop resumed from a stall that outlived the lease.
|
|
300
|
+
let stallResumedAt = null;
|
|
301
|
+
// The refresh in flight: a promise of its settled status, else null.
|
|
302
|
+
let pending = null;
|
|
303
|
+
// When the latest stall was noticed: only a refresh started after it can
|
|
304
|
+
// re-verify the record (an older one may have read it before the stall).
|
|
305
|
+
let stallNotedAt = Number.NEGATIVE_INFINITY;
|
|
275
306
|
let timer = null;
|
|
276
307
|
|
|
277
308
|
function halt(next) {
|
|
@@ -284,6 +315,12 @@ export function createOwnerHeartbeat({
|
|
|
284
315
|
return t - leaseFrom >= fenceMs;
|
|
285
316
|
}
|
|
286
317
|
|
|
318
|
+
// Expired and past re-verification: the refreshes failed while the loop
|
|
319
|
+
// ran, or no refresh landed within `heartbeatMs` of resuming from a stall.
|
|
320
|
+
function mustFence(t) {
|
|
321
|
+
return leaseExpired(t) && !(stallResumedAt !== null && t - stallResumedAt < heartbeatMs);
|
|
322
|
+
}
|
|
323
|
+
|
|
287
324
|
function fence(t) {
|
|
288
325
|
if (state !== 'active') return;
|
|
289
326
|
halt('fenced');
|
|
@@ -298,6 +335,8 @@ export function createOwnerHeartbeat({
|
|
|
298
335
|
lastTurnAt = t;
|
|
299
336
|
if (t - from <= stallMs) return;
|
|
300
337
|
needsVerify = true;
|
|
338
|
+
stallNotedAt = t;
|
|
339
|
+
if (state === 'active' && !leaseExpired(from) && leaseExpired(t)) stallResumedAt = t;
|
|
301
340
|
let detail = '';
|
|
302
341
|
try {
|
|
303
342
|
detail = describeStall?.(from, t) || '';
|
|
@@ -310,13 +349,53 @@ export function createOwnerHeartbeat({
|
|
|
310
349
|
);
|
|
311
350
|
}
|
|
312
351
|
|
|
352
|
+
// A refresh's verdict, applied once it is known. Fenced, superseded or
|
|
353
|
+
// stopped while it ran: nothing is renewed. A renewal counts from the
|
|
354
|
+
// moment the refresh started (`t`), never from when it answered.
|
|
355
|
+
function settle(t, heartbeatAt, result) {
|
|
356
|
+
if (state !== 'active') return result.status;
|
|
357
|
+
if (result.status === 'refreshed') {
|
|
358
|
+
if (leaseExpired(t)) {
|
|
359
|
+
log(
|
|
360
|
+
`owner lease re-verified ${Math.round(t - leaseFrom)}ms after its last refresh (event-loop stall)` +
|
|
361
|
+
' — the record still names this process; serving again'
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
leaseFrom = t;
|
|
365
|
+
lastRefreshAt = heartbeatAt;
|
|
366
|
+
if (t >= stallNotedAt) {
|
|
367
|
+
needsVerify = false;
|
|
368
|
+
stallResumedAt = null;
|
|
369
|
+
}
|
|
370
|
+
onBeat();
|
|
371
|
+
} else if (result.status === 'superseded') {
|
|
372
|
+
halt('superseded');
|
|
373
|
+
onSuperseded(result.owner);
|
|
374
|
+
} else if (result.status === 'busy') {
|
|
375
|
+
log('owner heartbeat skipped: claim lock busy');
|
|
376
|
+
}
|
|
377
|
+
return result.status;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function failedRefresh(error) {
|
|
381
|
+
log(`owner heartbeat failed: ${error?.message || error}`);
|
|
382
|
+
return { status: 'failed' };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// One refresh at a time. An asynchronous refresh (the default: its file
|
|
386
|
+
// work and lock retries never block the loop) answers 'pending' here and
|
|
387
|
+
// settles later; the lease and fence keep their monotonic deadlines while
|
|
388
|
+
// it runs — a refresh still unanswered at the fence does not hold it off.
|
|
313
389
|
function beat() {
|
|
314
390
|
if (state !== 'active') return null;
|
|
391
|
+
if (pending) return 'pending';
|
|
315
392
|
const t = monoNow();
|
|
316
393
|
lastBeatAt = t;
|
|
317
|
-
// Checked before every renewal: an overdue beat
|
|
318
|
-
//
|
|
319
|
-
|
|
394
|
+
// Checked before every renewal: an overdue beat must fence, not extend a
|
|
395
|
+
// lease a contender may already have reclaimed — except right after a
|
|
396
|
+
// stall that outlived the lease, where the refresh itself (under the
|
|
397
|
+
// claim lock) answers whether one did.
|
|
398
|
+
if (mustFence(t)) {
|
|
320
399
|
fence(t);
|
|
321
400
|
return 'fenced';
|
|
322
401
|
}
|
|
@@ -325,28 +404,26 @@ export function createOwnerHeartbeat({
|
|
|
325
404
|
try {
|
|
326
405
|
result = refresh(ownerPath, { ...record, heartbeatAt });
|
|
327
406
|
} catch (error) {
|
|
328
|
-
|
|
329
|
-
result = { status: 'failed' };
|
|
330
|
-
}
|
|
331
|
-
if (result.status === 'refreshed') {
|
|
332
|
-
leaseFrom = t;
|
|
333
|
-
lastRefreshAt = heartbeatAt;
|
|
334
|
-
needsVerify = false;
|
|
335
|
-
onBeat();
|
|
336
|
-
} else if (result.status === 'superseded') {
|
|
337
|
-
halt('superseded');
|
|
338
|
-
onSuperseded(result.owner);
|
|
339
|
-
} else if (result.status === 'busy') {
|
|
340
|
-
log('owner heartbeat skipped: claim lock busy');
|
|
407
|
+
result = failedRefresh(error);
|
|
341
408
|
}
|
|
342
|
-
return result
|
|
409
|
+
if (typeof result?.then !== 'function') return settle(t, heartbeatAt, result);
|
|
410
|
+
pending = result
|
|
411
|
+
.then(
|
|
412
|
+
(value) => value,
|
|
413
|
+
(error) => failedRefresh(error)
|
|
414
|
+
)
|
|
415
|
+
.then((value) => {
|
|
416
|
+
pending = null;
|
|
417
|
+
return settle(t, heartbeatAt, value);
|
|
418
|
+
});
|
|
419
|
+
return 'pending';
|
|
343
420
|
}
|
|
344
421
|
|
|
345
422
|
function tick() {
|
|
346
423
|
if (state !== 'active') return;
|
|
347
424
|
const t = monoNow();
|
|
348
425
|
noteTurn(t);
|
|
349
|
-
if (
|
|
426
|
+
if (mustFence(t)) fence(t);
|
|
350
427
|
else if (needsVerify ? t - lastBeatAt >= tickMs : t - lastBeatAt >= heartbeatMs) beat();
|
|
351
428
|
}
|
|
352
429
|
|
|
@@ -363,7 +440,7 @@ export function createOwnerHeartbeat({
|
|
|
363
440
|
// Shutting down: no ticks run to measure stalls; the lease still bounds it.
|
|
364
441
|
if (state === 'stopped') return !leaseExpired(t);
|
|
365
442
|
noteTurn(t);
|
|
366
|
-
if (
|
|
443
|
+
if (mustFence(t)) {
|
|
367
444
|
fence(t);
|
|
368
445
|
return false;
|
|
369
446
|
}
|
|
@@ -372,8 +449,23 @@ export function createOwnerHeartbeat({
|
|
|
372
449
|
if (needsVerify && t - lastBeatAt >= tickMs) beat();
|
|
373
450
|
return state === 'active' && !needsVerify;
|
|
374
451
|
},
|
|
452
|
+
/** admit(), but a request arriving while the lease is being re-verified
|
|
453
|
+
* waits for the verifying refresh instead of being refused outright: at
|
|
454
|
+
* most two refreshes (one started before the stall cannot verify). */
|
|
455
|
+
async admitAsync() {
|
|
456
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
457
|
+
if (this.admit()) return true;
|
|
458
|
+
if (!pending) return false;
|
|
459
|
+
await pending;
|
|
460
|
+
}
|
|
461
|
+
return this.admit();
|
|
462
|
+
},
|
|
375
463
|
beat,
|
|
376
464
|
tick,
|
|
465
|
+
/** Settles with the in-flight refresh's status (null when none runs). */
|
|
466
|
+
settled() {
|
|
467
|
+
return pending ?? Promise.resolve(null);
|
|
468
|
+
},
|
|
377
469
|
/** Stop beating (shutdown). admit() keeps refusing once the lease runs out. */
|
|
378
470
|
stop() {
|
|
379
471
|
if (state === 'active') halt('stopped');
|
|
@@ -35,7 +35,8 @@ import path from 'node:path';
|
|
|
35
35
|
import { rmSync } from 'node:fs';
|
|
36
36
|
import { fileURLToPath } from 'node:url';
|
|
37
37
|
import { monitorEventLoopDelay, performance } from 'node:perf_hooks';
|
|
38
|
-
import {
|
|
38
|
+
import { writeJsonAtomicAsync } from '../runtime/shared/atomic-file.mjs';
|
|
39
|
+
import { prewarmOwnerOnlyAclWin32 } from '../runtime/shared/file-permissions.mjs';
|
|
39
40
|
import { ensurePrivateRuntimeRoot, resolveRuntimeRoot } from '../runtime/shared/runtime-root.mjs';
|
|
40
41
|
import { ensureProcessListenerHeadroom } from '../runtime/shared/process-listener-headroom.mjs';
|
|
41
42
|
import { listenerLeakWarningLine } from '../runtime/shared/listener-leak-warning.mjs';
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
daemonOwnerTimings,
|
|
47
48
|
discoveryPid,
|
|
48
49
|
proveDaemonOwnerRecord,
|
|
50
|
+
readDiscoveryAsync,
|
|
49
51
|
startOwnerLivenessListener,
|
|
50
52
|
} from './daemon-owner.mjs';
|
|
51
53
|
import { remoteIntentPath } from '../runtime/shared/remote-intent.mjs';
|
|
@@ -607,11 +609,12 @@ function daemonStatusSnapshot() {
|
|
|
607
609
|
}
|
|
608
610
|
|
|
609
611
|
/** The file every attacher reads to find this daemon: wire identity plus both
|
|
610
|
-
* loopback endpoints and their privileged tokens.
|
|
612
|
+
* loopback endpoints and their privileged tokens. Written asynchronously
|
|
613
|
+
* (the secret file's owner-only ACL included), never on the event loop. */
|
|
611
614
|
function publishDaemonDiscovery({ channel, session, modelProxy = null }) {
|
|
612
615
|
// A re-publish keeps the first publication's startedAt.
|
|
613
616
|
publishedEndpoints = { channel, session, modelProxy, startedAt: publishedEndpoints?.startedAt ?? Date.now() };
|
|
614
|
-
|
|
617
|
+
return writeJsonAtomicAsync(
|
|
615
618
|
DAEMON_DISCOVERY_PATH,
|
|
616
619
|
{
|
|
617
620
|
protocol: SESSION_PROTOCOL,
|
|
@@ -658,6 +661,13 @@ function admitOwnerWork() {
|
|
|
658
661
|
return ownerHeartbeat ? ownerHeartbeat.admit() : true;
|
|
659
662
|
}
|
|
660
663
|
|
|
664
|
+
/** admitOwnerWork for the HTTP front doors: a request that arrives while the
|
|
665
|
+
* lease is being re-verified (the refresh is asynchronous) waits for that
|
|
666
|
+
* refresh instead of being refused. */
|
|
667
|
+
function admitOwnerWorkAsync() {
|
|
668
|
+
return ownerHeartbeat ? ownerHeartbeat.admitAsync() : true;
|
|
669
|
+
}
|
|
670
|
+
|
|
661
671
|
/** This process no longer holds (or is about to lose) the owner lease: refuse
|
|
662
672
|
* new work at once, unpublish its endpoint so launchers stop finding it, then
|
|
663
673
|
* shut down within the shutdown budget — before a contender may reclaim. */
|
|
@@ -678,14 +688,31 @@ function fenceDaemon(reason) {
|
|
|
678
688
|
/** Heartbeat follow-up while this process still owns the lock: a daemon.json
|
|
679
689
|
* deleted (TEMP cleanup) or overwritten while it runs is written again, so
|
|
680
690
|
* new clients keep finding the one daemon that serves this data dir. */
|
|
691
|
+
// Read and rewritten asynchronously, one check at a time: this runs after
|
|
692
|
+
// every heartbeat, and its synchronous read of daemon.json was measured
|
|
693
|
+
// blocking the loop for 5.9 s under disk contention.
|
|
694
|
+
let republishing = false;
|
|
681
695
|
function republishMissingDiscovery() {
|
|
682
|
-
if (!publishedEndpoints || shuttingDown ||
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
696
|
+
if (!publishedEndpoints || shuttingDown || republishing) return;
|
|
697
|
+
republishing = true;
|
|
698
|
+
void (async () => {
|
|
699
|
+
try {
|
|
700
|
+
if ((await readDiscoveryAsync(DAEMON_DISCOVERY_PATH))?.pid === process.pid || shuttingDown) return;
|
|
701
|
+
await publishDaemonDiscovery(publishedEndpoints);
|
|
702
|
+
// Fenced while it was written: take it back down (fenceDaemon already ran).
|
|
703
|
+
if (shuttingDown) {
|
|
704
|
+
if ((await readDiscoveryAsync(DAEMON_DISCOVERY_PATH))?.pid === process.pid) {
|
|
705
|
+
rmSync(DAEMON_DISCOVERY_PATH, { force: true });
|
|
706
|
+
}
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
log('daemon.json was missing or named another pid — re-published');
|
|
710
|
+
} catch (error) {
|
|
711
|
+
log(`daemon.json re-publish failed: ${error?.message || error}`);
|
|
712
|
+
} finally {
|
|
713
|
+
republishing = false;
|
|
714
|
+
}
|
|
715
|
+
})();
|
|
689
716
|
}
|
|
690
717
|
|
|
691
718
|
/** Ready handshake for the spawner, plus the log-sink handoff pinned to the
|
|
@@ -757,7 +784,7 @@ async function startChannelFrontDoor({ channelsRuntime, bootPhases }) {
|
|
|
757
784
|
transport = createChannelTransport({
|
|
758
785
|
handleCall: channelsRuntime.handleCall,
|
|
759
786
|
agentBroker: agentDispatchBroker,
|
|
760
|
-
admitWork:
|
|
787
|
+
admitWork: admitOwnerWorkAsync,
|
|
761
788
|
log,
|
|
762
789
|
// The durable channel link lives in this file. Without it a restart loses
|
|
763
790
|
// the pinned session (nothing to restore) and every catalog reports Remote
|
|
@@ -974,7 +1001,7 @@ async function main() {
|
|
|
974
1001
|
} catch {}
|
|
975
1002
|
},
|
|
976
1003
|
onUpgradeRequested: requestDaemonReplacement,
|
|
977
|
-
admitWork:
|
|
1004
|
+
admitWork: admitOwnerWorkAsync,
|
|
978
1005
|
});
|
|
979
1006
|
const sessionEndpoint = await bootPhases.measure('session-transport-start', () => sessionTransport.start());
|
|
980
1007
|
const modelProxyEndpoint = await startModelProxyBridge(bootPhases);
|
|
@@ -988,7 +1015,7 @@ async function main() {
|
|
|
988
1015
|
exitRecorder.setReason(`owner claim superseded by pid=${ownerPid} before publish`, 0);
|
|
989
1016
|
process.exit(0);
|
|
990
1017
|
}
|
|
991
|
-
publishDaemonDiscovery({ channel: { port, token }, session: sessionEndpoint, modelProxy: modelProxyEndpoint });
|
|
1018
|
+
await publishDaemonDiscovery({ channel: { port, token }, session: sessionEndpoint, modelProxy: modelProxyEndpoint });
|
|
992
1019
|
bootPhases.mark('discovery-published');
|
|
993
1020
|
log(`session front door on 127.0.0.1:${sessionEndpoint.port}`);
|
|
994
1021
|
|
|
@@ -1000,6 +1027,9 @@ async function main() {
|
|
|
1000
1027
|
void sessionRuntimeHost?.warmLocalRuntime?.();
|
|
1001
1028
|
});
|
|
1002
1029
|
startPostgresInBackground();
|
|
1030
|
+
// Secret writes (token stores, config) find the ACL helpers already running
|
|
1031
|
+
// instead of starting one under their file lock.
|
|
1032
|
+
void prewarmOwnerOnlyAclWin32();
|
|
1003
1033
|
|
|
1004
1034
|
// Automation may spawn the shared daemon; keep schedules/webhooks live.
|
|
1005
1035
|
// A TUI-spawned daemon keeps the historical eager start (its channels client
|