mixdog 1.2.5 → 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/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 +10 -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 -6
- 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 +4 -3
- 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 +258 -43
- 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/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 +385 -104
- 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 +24 -7
- 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
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { refreshKeychainStatus, retryKeychain } from '../../runtime/shared/config.mjs';
|
|
5
5
|
import { keychainIssueFor } from '../services/provider-admin.mjs';
|
|
6
6
|
import { modelProxyClientMode } from '../../runtime/shared/model-proxy-mode.mjs';
|
|
7
|
+
import { providerCatalogFailure } from '../../runtime/agent/orchestrator/providers/catalog-failures.mjs';
|
|
7
8
|
|
|
8
9
|
// The keyring alert must reflect the live state (it can lock or unlock while a
|
|
9
10
|
// cached setup is served), so overlay it on every ready read.
|
|
@@ -14,6 +15,22 @@ function withLiveKeychain(setup) {
|
|
|
14
15
|
return keychain ? { ...rest, keychain } : rest;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
// A provider whose model list failed says why on its row (`catalogError`), so a
|
|
19
|
+
// view never shows its empty list as if nothing were wrong. Overlaid on every
|
|
20
|
+
// read like the keyring alert: the cached setup predates the failure.
|
|
21
|
+
export function withCatalogErrors(setup) {
|
|
22
|
+
if (!setup || typeof setup !== 'object') return setup;
|
|
23
|
+
const out = { ...setup };
|
|
24
|
+
for (const group of ['api', 'oauth', 'local']) {
|
|
25
|
+
if (!Array.isArray(setup[group])) continue;
|
|
26
|
+
out[group] = setup[group].map((row) => {
|
|
27
|
+
const { message, errorKind } = providerCatalogFailure(row?.id) ?? {};
|
|
28
|
+
return message ? { ...row, catalogError: { message, errorKind } } : row;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
17
34
|
export function createSetupApi({
|
|
18
35
|
awaitKeychainPrewarm,
|
|
19
36
|
isKeychainPrewarmReady,
|
|
@@ -41,7 +58,7 @@ export function createSetupApi({
|
|
|
41
58
|
void Promise.resolve(awaitKeychainPrewarm())
|
|
42
59
|
.then(() => cachedProviderSetup({}))
|
|
43
60
|
.catch(() => {});
|
|
44
|
-
return { ...quick, pendingSecrets: !isKeychainPrewarmReady() };
|
|
61
|
+
return withCatalogErrors({ ...quick, pendingSecrets: !isKeychainPrewarmReady() });
|
|
45
62
|
}
|
|
46
63
|
await awaitKeychainPrewarm();
|
|
47
64
|
// A keyring the user has since unlocked: rebuild the setup that was cached
|
|
@@ -53,7 +70,7 @@ export function createSetupApi({
|
|
|
53
70
|
// `recheckKeychain` (window focus) skips the snapshot TTL.
|
|
54
71
|
const changed = await refreshKeychainStatus({ force: options?.recheckKeychain === true });
|
|
55
72
|
if (changed) reloadFullConfig();
|
|
56
|
-
return withLiveKeychain(await cachedProviderSetup({ force: force || recovered || changed }));
|
|
73
|
+
return withCatalogErrors(withLiveKeychain(await cachedProviderSetup({ force: force || recovered || changed })));
|
|
57
74
|
},
|
|
58
75
|
async getUsageDashboard(options = {}) {
|
|
59
76
|
return await getUsageDashboard(options);
|
|
@@ -1,52 +1,128 @@
|
|
|
1
1
|
// Provider registries are process-global. Share raw catalog rows across
|
|
2
2
|
// sessions, while each session owns its saved-settings projection.
|
|
3
|
-
|
|
3
|
+
//
|
|
4
|
+
// One slow or offline provider must not hold, or force a reload of, everyone
|
|
5
|
+
// else's rows. Each provider's model list runs through catalog-deadline.mjs:
|
|
6
|
+
// bounded per provider (a timed-out list becomes an error entry and is
|
|
7
|
+
// aborted where listModels honors `{ signal }`), and single-flight per
|
|
8
|
+
// provider until the underlying work settles. When some but not all providers
|
|
9
|
+
// answer, the answered entries are kept for the catalog state, and each
|
|
10
|
+
// failed entry carries a process-wide `retryAt`: non-forced reads by any
|
|
11
|
+
// session reuse the partial answer and ask a missing provider again only once
|
|
12
|
+
// its backoff passed. Forced reads ask every provider. A load where every
|
|
13
|
+
// provider failed is never kept, so it stays immediately retryable. A
|
|
14
|
+
// provider whose previous list ignored its deadline and is still running is
|
|
15
|
+
// not asked at all (catalog-deadline.mjs): it gets a timed-out entry at
|
|
16
|
+
// once, with the same backoff, until that list settles.
|
|
17
|
+
//
|
|
18
|
+
// The catalog state is the registry revision plus a local epoch that
|
|
19
|
+
// invalidateSharedProviderCatalog() bumps on auth/account changes, so rows
|
|
20
|
+
// listed under the previous account are never served again.
|
|
21
|
+
import {
|
|
22
|
+
boundedProviderCatalogOp,
|
|
23
|
+
bumpProviderCatalogGeneration,
|
|
24
|
+
PROVIDER_CATALOG_TIMEOUT_MS,
|
|
25
|
+
} from '../runtime/agent/orchestrator/providers/catalog-deadline.mjs';
|
|
26
|
+
|
|
27
|
+
export { PROVIDER_CATALOG_TIMEOUT_MS };
|
|
28
|
+
|
|
29
|
+
/** Backoff before a provider missing from a partial catalog is asked again. */
|
|
30
|
+
export const PROVIDER_CATALOG_RETRY_MS = 30_000;
|
|
31
|
+
|
|
32
|
+
let catalogEpoch = 0;
|
|
33
|
+
let sharedCatalogKey = null;
|
|
4
34
|
let sharedCatalogEntries = null;
|
|
35
|
+
let sharedPartialKey = null;
|
|
36
|
+
let sharedPartialEntries = null;
|
|
5
37
|
let sharedCatalogPromise = null;
|
|
6
|
-
let
|
|
38
|
+
let sharedCatalogPromiseKey = null;
|
|
39
|
+
let sharedCatalogPromiseForced = false;
|
|
7
40
|
|
|
8
41
|
export function catalogRevision(registry) {
|
|
9
42
|
const value = Number(registry?.providerCatalogRevision?.());
|
|
10
43
|
return Number.isFinite(value) ? value : 0;
|
|
11
44
|
}
|
|
12
45
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
46
|
+
const catalogKey = (registry) => `${catalogRevision(registry)}:${catalogEpoch}`;
|
|
47
|
+
|
|
48
|
+
/** Drops every shared entry (complete or partial) and opens a new epoch: the
|
|
49
|
+
* next read asks every provider again. Called on auth/account changes. */
|
|
50
|
+
export function invalidateSharedProviderCatalog() {
|
|
51
|
+
catalogEpoch += 1;
|
|
52
|
+
// Pending refreshes and lists of the previous account, of either kind, must
|
|
53
|
+
// not block or fail the new account's work.
|
|
54
|
+
bumpProviderCatalogGeneration();
|
|
55
|
+
sharedCatalogKey = null;
|
|
56
|
+
sharedCatalogEntries = null;
|
|
57
|
+
sharedPartialKey = null;
|
|
58
|
+
sharedPartialEntries = null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const answered = (entry) => !Object.hasOwn(entry, 'error');
|
|
62
|
+
|
|
63
|
+
async function providerEntry(name, provider, timeoutMs, epoch) {
|
|
64
|
+
if (typeof provider?.listModels !== 'function') return { name, models: [], ms: 0 };
|
|
65
|
+
const startedAt = performance.now();
|
|
66
|
+
const outcome = await boundedProviderCatalogOp({
|
|
67
|
+
name,
|
|
68
|
+
provider,
|
|
69
|
+
kind: 'list',
|
|
70
|
+
run: (signal) => provider.listModels({ signal }),
|
|
71
|
+
timeoutMs,
|
|
72
|
+
epoch,
|
|
73
|
+
});
|
|
74
|
+
const ms = performance.now() - startedAt;
|
|
75
|
+
if (outcome.error) {
|
|
76
|
+
return { name, models: [], error: outcome.error, ms, retryAt: Date.now() + PROVIDER_CATALOG_RETRY_MS };
|
|
77
|
+
}
|
|
78
|
+
return { name, models: Array.isArray(outcome.value) ? outcome.value : [], ms };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function sharedProviderCatalog(registry, { force = false, timeoutMs = PROVIDER_CATALOG_TIMEOUT_MS } = {}) {
|
|
82
|
+
const key = catalogKey(registry);
|
|
83
|
+
if (!force && sharedCatalogKey === key && Array.isArray(sharedCatalogEntries)) {
|
|
16
84
|
return sharedCatalogEntries;
|
|
17
85
|
}
|
|
18
|
-
if (sharedCatalogPromise &&
|
|
86
|
+
if (sharedCatalogPromise && sharedCatalogPromiseKey === key && (sharedCatalogPromiseForced || !force)) {
|
|
19
87
|
return await sharedCatalogPromise;
|
|
20
88
|
}
|
|
89
|
+
const kept = new Map();
|
|
90
|
+
if (!force && sharedPartialKey === key) {
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
for (const entry of sharedPartialEntries) {
|
|
93
|
+
if (answered(entry) || now < entry.retryAt) kept.set(entry.name, entry);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
21
96
|
const providers = [...registry.getAllProviders()];
|
|
22
|
-
|
|
97
|
+
// Every missing provider is still inside its backoff: the partial answer is
|
|
98
|
+
// the answer, without asking anyone.
|
|
99
|
+
if (providers.length > 0 && providers.every(([name]) => kept.has(name))) {
|
|
100
|
+
return providers.map(([name]) => kept.get(name));
|
|
101
|
+
}
|
|
102
|
+
// A pending list from an older catalog state (revision or account epoch) is
|
|
103
|
+
// not joined: its answer may describe the previous catalog.
|
|
104
|
+
const epoch = key;
|
|
105
|
+
sharedCatalogPromiseKey = key;
|
|
106
|
+
sharedCatalogPromiseForced = force;
|
|
23
107
|
const request = Promise.all(
|
|
24
|
-
providers.map(
|
|
25
|
-
if (typeof provider?.listModels !== 'function') return { name, models: [], ms: 0 };
|
|
26
|
-
const startedAt = performance.now();
|
|
27
|
-
try {
|
|
28
|
-
const models = await provider.listModels();
|
|
29
|
-
return { name, models: Array.isArray(models) ? models : [], ms: performance.now() - startedAt };
|
|
30
|
-
} catch (error) {
|
|
31
|
-
return { name, models: [], error, ms: performance.now() - startedAt };
|
|
32
|
-
}
|
|
33
|
-
})
|
|
108
|
+
providers.map(([name, provider]) => kept.get(name) || providerEntry(name, provider, timeoutMs, epoch))
|
|
34
109
|
)
|
|
35
110
|
.then((entries) => {
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
111
|
+
if (sharedCatalogPromise === request && catalogKey(registry) === key) {
|
|
112
|
+
const complete = entries.every(answered);
|
|
113
|
+
const partial = !complete && entries.some(answered);
|
|
114
|
+
sharedCatalogKey = complete ? key : null;
|
|
115
|
+
sharedCatalogEntries = complete ? entries : null;
|
|
116
|
+
sharedPartialKey = partial ? key : null;
|
|
117
|
+
sharedPartialEntries = partial ? entries : null;
|
|
43
118
|
}
|
|
44
119
|
return entries;
|
|
45
120
|
})
|
|
46
121
|
.finally(() => {
|
|
47
122
|
if (sharedCatalogPromise === request) {
|
|
48
123
|
sharedCatalogPromise = null;
|
|
49
|
-
|
|
124
|
+
sharedCatalogPromiseKey = null;
|
|
125
|
+
sharedCatalogPromiseForced = false;
|
|
50
126
|
}
|
|
51
127
|
});
|
|
52
128
|
sharedCatalogPromise = request;
|
|
@@ -17,6 +17,7 @@ export function createCatalogLoader({
|
|
|
17
17
|
ensureFullConfig,
|
|
18
18
|
awaitKeychainPrewarm,
|
|
19
19
|
ensureProvidersReady,
|
|
20
|
+
providerTimeoutMs,
|
|
20
21
|
}) {
|
|
21
22
|
function rowsForEntry({ name, models, error, ms }) {
|
|
22
23
|
const out = [];
|
|
@@ -76,7 +77,10 @@ export function createCatalogLoader({
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
/** `request` ({ seq }) identifies the caller's load; `request.complete`
|
|
79
|
-
* reports whether every provider answered.
|
|
80
|
+
* reports whether every provider answered, `request.missing` names the
|
|
81
|
+
* providers that failed or timed out, `request.partial` marks a load
|
|
82
|
+
* where some but not all providers answered, and `request.retryAt` is when
|
|
83
|
+
* the first missing provider may be asked again. */
|
|
80
84
|
return async function loadProviderModelsFresh({ forceRefresh = false, loadSecrets = true, request = null } = {}) {
|
|
81
85
|
const startedAt = performance.now();
|
|
82
86
|
profile('load:start', { forceRefresh, loadSecrets });
|
|
@@ -93,8 +97,15 @@ export function createCatalogLoader({
|
|
|
93
97
|
// forward only for the still-current request, never for a superseded load.
|
|
94
98
|
if (ownsLoad) request.seq = caches.providerModelsLoadSeq;
|
|
95
99
|
const seq = request?.seq ?? caches.providerModelsLoadSeq;
|
|
96
|
-
const catalogEntries = await sharedProviderCatalog(reg());
|
|
97
|
-
if (request)
|
|
100
|
+
const catalogEntries = await sharedProviderCatalog(reg(), { force: forceRefresh, timeoutMs: providerTimeoutMs });
|
|
101
|
+
if (request) {
|
|
102
|
+
const failed = catalogEntries.filter((entry) => Object.hasOwn(entry, 'error'));
|
|
103
|
+
request.missing = failed.map((entry) => entry.name);
|
|
104
|
+
request.complete = failed.length === 0;
|
|
105
|
+
request.partial = !request.complete && failed.length < catalogEntries.length;
|
|
106
|
+
// The shared, process-wide backoff of the soonest missing provider.
|
|
107
|
+
request.retryAt = request.partial ? Math.min(...failed.map((entry) => entry.retryAt)) : null;
|
|
108
|
+
}
|
|
98
109
|
const results = [];
|
|
99
110
|
const seen = new Set();
|
|
100
111
|
for (const row of catalogEntries.flatMap(rowsForEntry)) {
|
|
@@ -69,6 +69,10 @@ export function hydrateProviderModelRow(cfg, row) {
|
|
|
69
69
|
savedModelParameters: saved.modelParameters || {},
|
|
70
70
|
savedContextPercent: Number(saved.contextPercent) || undefined,
|
|
71
71
|
savedContextTokens: Number(saved.contextTokens) || undefined,
|
|
72
|
+
// Model management: an explicit /model visibility choice and the favorite
|
|
73
|
+
// mark (absent = the picker's default listing, not a favorite).
|
|
74
|
+
...(typeof saved.hidden === 'boolean' ? { savedHidden: saved.hidden } : {}),
|
|
75
|
+
...(saved.favorite === true ? { favorite: true } : {}),
|
|
72
76
|
};
|
|
73
77
|
}
|
|
74
78
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// row-hydration — saved model settings layered onto catalog rows
|
|
11
11
|
// web-search-catalog — the native web-search model list
|
|
12
12
|
// catalog-load — one authoritative provider catalog load
|
|
13
|
+
import { PROVIDER_CATALOG_RETRY_MS } from './provider-catalog-cache.mjs';
|
|
13
14
|
import { createCatalogLoader } from './provider-models/catalog-load.mjs';
|
|
14
15
|
import { createModelMetaIndex, modelMetaKey } from './provider-models/model-meta.mjs';
|
|
15
16
|
import { createRowHydration } from './provider-models/row-hydration.mjs';
|
|
@@ -19,6 +20,25 @@ const PROVIDER_MODELS_PROFILE_ENABLED = /^(1|true|yes|on)$/i.test(
|
|
|
19
20
|
String(process.env.MIXDOG_PROVIDER_MODELS_PROFILE || process.env.MIXDOG_BOOT_PROFILE || '')
|
|
20
21
|
);
|
|
21
22
|
|
|
23
|
+
/** How long a partial catalog (some provider failed or timed out) is served
|
|
24
|
+
* as-is before a read retries the missing providers in the background. The
|
|
25
|
+
* backoff is process-wide (provider-catalog-cache.mjs), not per session. */
|
|
26
|
+
export const PARTIAL_CATALOG_RETRY_MS = PROVIDER_CATALOG_RETRY_MS;
|
|
27
|
+
|
|
28
|
+
/** Rows of a partial answer say so explicitly, on quick and full reads alike:
|
|
29
|
+
* `catalogPartial`, the `catalogMissing` providers and `catalogRetryAt`
|
|
30
|
+
* (epoch ms) after which a reader should ask again. Complete answers carry
|
|
31
|
+
* none of these. */
|
|
32
|
+
function withCatalogState(models, partial) {
|
|
33
|
+
if (!partial) return models;
|
|
34
|
+
return models.map((row) => ({
|
|
35
|
+
...row,
|
|
36
|
+
catalogPartial: true,
|
|
37
|
+
catalogMissing: [...partial.missing],
|
|
38
|
+
catalogRetryAt: partial.retryAt,
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
22
42
|
export function createProviderModels({
|
|
23
43
|
caches,
|
|
24
44
|
modelMetaByRoute,
|
|
@@ -37,6 +57,8 @@ export function createProviderModels({
|
|
|
37
57
|
scheduleProviderModelWarmup,
|
|
38
58
|
// Quick-row helpers wired in after createQuickModelRows resolves.
|
|
39
59
|
quickHelpers,
|
|
60
|
+
// Per-provider model-list bound; defaults to PROVIDER_CATALOG_TIMEOUT_MS.
|
|
61
|
+
providerCatalogTimeoutMs,
|
|
40
62
|
}) {
|
|
41
63
|
const config = () => getConfig();
|
|
42
64
|
const route = () => getRoute();
|
|
@@ -66,17 +88,56 @@ export function createProviderModels({
|
|
|
66
88
|
collectProviderModels,
|
|
67
89
|
...readiness,
|
|
68
90
|
});
|
|
69
|
-
const loadProviderModelsFresh = createCatalogLoader({
|
|
91
|
+
const loadProviderModelsFresh = createCatalogLoader({
|
|
92
|
+
caches,
|
|
93
|
+
config,
|
|
94
|
+
reg,
|
|
95
|
+
meta,
|
|
96
|
+
rows,
|
|
97
|
+
profile,
|
|
98
|
+
providerTimeoutMs: providerCatalogTimeoutMs,
|
|
99
|
+
...readiness,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Partial-catalog state of a loaded rows array, for readers that joined the
|
|
103
|
+
// shared load instead of reading the adopted cache.
|
|
104
|
+
const partialLoads = new WeakMap();
|
|
105
|
+
const partialOf = (request) => (request.partial ? { missing: request.missing, retryAt: request.retryAt } : null);
|
|
70
106
|
|
|
71
107
|
function adoptProviderModelCache(models, request, loadSecrets = true) {
|
|
72
108
|
const revision = meta.syncCatalogRevision();
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (request.seq
|
|
76
|
-
|
|
109
|
+
const partial = partialOf(request);
|
|
110
|
+
if (partial) partialLoads.set(models, partial);
|
|
111
|
+
if (request.seq !== caches.providerModelsLoadSeq || !loadSecrets) return;
|
|
112
|
+
const at = Date.now();
|
|
113
|
+
if (request.complete) {
|
|
114
|
+
caches.providerModelsCache = { models, at, revision };
|
|
115
|
+
} else if (partial) {
|
|
116
|
+
// Some providers answered. Serving that answer beats reloading every
|
|
117
|
+
// provider on each read while one is down; it stays marked partial and
|
|
118
|
+
// its missing providers are retried once the shared backoff passes. A
|
|
119
|
+
// no-secrets, empty or all-failed load stays uncached and retryable
|
|
120
|
+
// rather than becoming an authoritative empty catalog.
|
|
121
|
+
caches.providerModelsCache = { models, at, revision, partial: true, ...partial };
|
|
77
122
|
}
|
|
78
123
|
}
|
|
79
124
|
|
|
125
|
+
/** Background refill of a partial catalog: only its missing providers are
|
|
126
|
+
* asked again (provider-catalog-cache keeps the ones that answered, and
|
|
127
|
+
* holds back providers still inside the shared backoff), and readers keep
|
|
128
|
+
* the partial rows until the refill lands. */
|
|
129
|
+
function refillPartialCatalog(cached) {
|
|
130
|
+
if (!cached.partial || Date.now() < cached.retryAt || caches.providerModelsPromise) return;
|
|
131
|
+
profile('partial:retry', { missing: cached.missing.join(',') });
|
|
132
|
+
startSharedLoad({
|
|
133
|
+
loadSecrets: true,
|
|
134
|
+
onFailed: (err) => {
|
|
135
|
+
profile('partial:retry-failed', { error: err instanceof Error ? err.message : String(err) });
|
|
136
|
+
return cached.models;
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
80
141
|
/** The shared in-flight load every concurrent reader joins. `onFailed`, when
|
|
81
142
|
* given, is part of the shared promise so joiners see its value too;
|
|
82
143
|
* `caches.providerModelsPromise` is cleared once it settles so a later read
|
|
@@ -97,13 +158,17 @@ export function createProviderModels({
|
|
|
97
158
|
|
|
98
159
|
async function collectProviderModels({ force = false, quick = false } = {}) {
|
|
99
160
|
meta.syncCatalogRevision();
|
|
100
|
-
|
|
101
|
-
|
|
161
|
+
const cached = caches.providerModelsCache;
|
|
162
|
+
if (!force && Array.isArray(cached.models)) {
|
|
163
|
+
refillPartialCatalog(cached);
|
|
164
|
+
const models = rows.providerModelsFromCacheRows(cached.models);
|
|
102
165
|
// A quick read answered from the loaded catalog already IS the full
|
|
103
166
|
// catalog. Saying so lets a caller skip the full read it would otherwise
|
|
104
167
|
// follow up with (a phone paid the whole catalog twice at boot). Only
|
|
105
168
|
// this answer carries the mark: a full read is complete by definition,
|
|
106
|
-
// and an unmarked quick answer keeps meaning "follow up".
|
|
169
|
+
// and an unmarked quick answer keeps meaning "follow up". A partial
|
|
170
|
+
// catalog is never marked complete; it carries its partial state.
|
|
171
|
+
if (cached.partial) return withCatalogState(models, cached);
|
|
107
172
|
return quick ? models.map((row) => ({ ...row, catalogComplete: true })) : models;
|
|
108
173
|
}
|
|
109
174
|
if (!force && quick) {
|
|
@@ -117,10 +182,11 @@ export function createProviderModels({
|
|
|
117
182
|
const request = { seq: ++caches.providerModelsLoadSeq };
|
|
118
183
|
const models = await loadProviderModelsFresh({ forceRefresh: true, loadSecrets: true, request });
|
|
119
184
|
adoptProviderModelCache(models, request);
|
|
120
|
-
return rows.providerModelsFromCacheRows(models);
|
|
185
|
+
return withCatalogState(rows.providerModelsFromCacheRows(models), partialOf(request));
|
|
121
186
|
}
|
|
122
187
|
if (!caches.providerModelsPromise) startSharedLoad({ loadSecrets: true });
|
|
123
|
-
|
|
188
|
+
const loaded = await caches.providerModelsPromise;
|
|
189
|
+
return withCatalogState(rows.providerModelsFromCacheRows(loaded), partialLoads.get(loaded));
|
|
124
190
|
}
|
|
125
191
|
|
|
126
192
|
function warmProviderModelCache({ loadSecrets = false } = {}) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// registry import and the provider-models factory resolve after this gate is
|
|
6
6
|
// created, while ensureProvidersReady only reaches them on demand.
|
|
7
7
|
import { bootProfile } from './boot-profile.mjs';
|
|
8
|
+
import { invalidateSharedProviderCatalog } from './provider-catalog-cache.mjs';
|
|
8
9
|
import { providerInitCacheKey } from './provider-init-key.mjs';
|
|
9
10
|
|
|
10
11
|
const KEYCHAIN_PREWARM_WAIT_MS = 5000;
|
|
@@ -52,6 +53,9 @@ export function createProviderReadiness({ rt, keychain, getReg, getWarmProviderM
|
|
|
52
53
|
const providerInitPromises = new Map();
|
|
53
54
|
|
|
54
55
|
function invalidateProviderCaches(options = {}) {
|
|
56
|
+
// Auth/account/config changes alter what providers list: the process-wide
|
|
57
|
+
// catalog (partial rows and retry backoffs included) starts a new epoch.
|
|
58
|
+
invalidateSharedProviderCatalog();
|
|
55
59
|
Object.assign(providerModelCaches, emptyModelCaches());
|
|
56
60
|
providerModelCaches.providerModelsLoadSeq += 1;
|
|
57
61
|
Object.assign(providerUsageCaches, emptyUsageCaches());
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { agentScope, clean, clearAgentStatuslineRoute } from './helpers.mjs';
|
|
9
9
|
import { isLeadPoolAgent } from './worker-rows.mjs';
|
|
10
10
|
import { createCloseAll } from './close-flow/close-all.mjs';
|
|
11
|
+
import { closeManagedSession } from '../../lifecycle/shared.mjs';
|
|
11
12
|
|
|
12
13
|
const CLOSE_REASON = 'cancelled by agent close';
|
|
13
14
|
|
|
@@ -68,7 +69,7 @@ export function createCloseFlow({ mgr, registry, views }) {
|
|
|
68
69
|
// below is an idempotent no-op fallback once terminal.
|
|
69
70
|
// Closing a runtime cancels its work; only explicit session deletion
|
|
70
71
|
// may tombstone the conversation.
|
|
71
|
-
const ok = await mgr
|
|
72
|
+
const ok = await closeManagedSession(mgr, sessionId, 'cli-agent-close', { tombstone: false });
|
|
72
73
|
if (task?.taskId) cancelBackgroundTask(task.taskId, CLOSE_REASON);
|
|
73
74
|
// Drop the row only once the cancellation is DURABLY recorded: a failed
|
|
74
75
|
// close that still forgot the row would leave no cancelled state behind.
|
|
@@ -76,13 +76,21 @@ export function createAgentList({ mgr, refreshTagsFromSessions, agentSessionEntr
|
|
|
76
76
|
return function list({ scanSessions = false, context = {}, includeTerminal = false } = {}) {
|
|
77
77
|
refreshTagsFromSessions({ scanSessions, context });
|
|
78
78
|
const now = Date.now();
|
|
79
|
-
const rows = agentSessionEntries({ scanSessions, context }).map(({ tag, session, indexRow }) =>
|
|
79
|
+
const rows = agentSessionEntries({ scanSessions, context }).map(({ tag, session, indexRow, loaded }) =>
|
|
80
80
|
agentRow({
|
|
81
81
|
tag,
|
|
82
82
|
session,
|
|
83
83
|
indexRow,
|
|
84
84
|
runtime: mgr.getSessionRuntime?.(session.id),
|
|
85
|
-
|
|
85
|
+
// A tag-bound entry is the getSession answer itself; an index row or
|
|
86
|
+
// a scanned summary is only a projection and still reads the session.
|
|
87
|
+
progress: progress.sessionProgressExtras(
|
|
88
|
+
session.id,
|
|
89
|
+
session.agent || null,
|
|
90
|
+
now,
|
|
91
|
+
null,
|
|
92
|
+
loaded ? { loaded: session } : {}
|
|
93
|
+
),
|
|
86
94
|
now,
|
|
87
95
|
})
|
|
88
96
|
);
|
|
@@ -58,9 +58,16 @@ export function liveAgentStatusRow(row) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export function createSessionProgress({ mgr }) {
|
|
61
|
-
|
|
61
|
+
/** `loaded`: the caller's own getSession(sessionId) answer from this same
|
|
62
|
+
* read, so the session is not loaded a second time. */
|
|
63
|
+
function sessionProgressExtras(sessionId, role, now = Date.now(), taskStatus = null, { loaded } = {}) {
|
|
62
64
|
if (!sessionId) return {};
|
|
63
|
-
|
|
65
|
+
// Only the session's status is read here: the manager's stamp-validated
|
|
66
|
+
// status lookup spares a whole-transcript read per idle worker per pulse.
|
|
67
|
+
let sessionStatus;
|
|
68
|
+
if (loaded !== undefined) sessionStatus = loaded?.status || null;
|
|
69
|
+
else if (typeof mgr.loadSessionStatus === 'function') sessionStatus = mgr.loadSessionStatus(sessionId) || null;
|
|
70
|
+
else sessionStatus = mgr.getSession(sessionId)?.status || null;
|
|
64
71
|
const runtime = mgr.getSessionRuntime?.(sessionId) || null;
|
|
65
72
|
const snapshot =
|
|
66
73
|
typeof mgr.getSessionProgressSnapshot === 'function' ? mgr.getSessionProgressSnapshot(sessionId) : null;
|
|
@@ -72,8 +79,8 @@ export function createSessionProgress({ mgr }) {
|
|
|
72
79
|
typeof mgr.getSessionPendingMessageDepth === 'function' ? mgr.getSessionPendingMessageDepth(sessionId) : null;
|
|
73
80
|
return buildAgentTaskProgressFields({
|
|
74
81
|
now,
|
|
75
|
-
sessionStatus
|
|
76
|
-
runtimeStage: runtime?.stage || snapshot?.stage ||
|
|
82
|
+
sessionStatus,
|
|
83
|
+
runtimeStage: runtime?.stage || snapshot?.stage || sessionStatus,
|
|
77
84
|
snapshot,
|
|
78
85
|
runtime,
|
|
79
86
|
policy,
|
|
@@ -96,6 +103,8 @@ export function createSessionProgress({ mgr }) {
|
|
|
96
103
|
stage: progress.worker_stage || runtime?.stage || status,
|
|
97
104
|
// Additive: present only while the worker's turn waits on a user approval.
|
|
98
105
|
...(runtime?.awaitingUser ? { awaitingUser: runtime.awaitingUser } : {}),
|
|
106
|
+
// Additive: present only while the worker's turn sits in `task wait`.
|
|
107
|
+
...(runtime?.taskWaits > 0 ? { awaitingTask: 'wait' } : {}),
|
|
99
108
|
clientHostPid: session.clientHostPid || null,
|
|
100
109
|
lastStreamDeltaAt: runtime?.lastStreamDeltaAt ? new Date(runtime.lastStreamDeltaAt).toISOString() : null,
|
|
101
110
|
...progress,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { clean } from './helpers.mjs';
|
|
10
10
|
import { createEarlyCompletionNotice } from './notify/early-completion.mjs';
|
|
11
11
|
|
|
12
|
-
export function createNotify(mgr, { notifySessionCompletion } = {}) {
|
|
12
|
+
export function createNotify(mgr, { notifySessionCompletion, onWorkerCompletionQueued = null } = {}) {
|
|
13
13
|
function enqueueCompletionMessage(sessionId, text, meta = {}) {
|
|
14
14
|
const target = clean(sessionId);
|
|
15
15
|
if (!target || typeof mgr.enqueuePendingMessage !== 'function') return false;
|
|
@@ -28,8 +28,8 @@ export function createNotify(mgr, { notifySessionCompletion } = {}) {
|
|
|
28
28
|
logDuplicateSkip('notify-enqueue', { executionId: meta?.execution_id, text: visible });
|
|
29
29
|
return true;
|
|
30
30
|
}
|
|
31
|
-
// Mark this as a
|
|
32
|
-
//
|
|
31
|
+
// Mark this as a completion/task notification: delivery dedupes it by
|
|
32
|
+
// execution id, and it stays queued (durably) until the model gets it.
|
|
33
33
|
return Boolean(
|
|
34
34
|
mgr.enqueuePendingMessage(
|
|
35
35
|
target,
|
|
@@ -67,7 +67,18 @@ export function createNotify(mgr, { notifySessionCompletion } = {}) {
|
|
|
67
67
|
// appear as a second Lead-level completion card. Agent task completion
|
|
68
68
|
// has its own owner delivery path below; only that terminal handoff
|
|
69
69
|
// crosses the parent boundary.
|
|
70
|
-
|
|
70
|
+
if (!workerId) return false;
|
|
71
|
+
const queued = enqueueCompletionMessage(workerId, text, meta);
|
|
72
|
+
// A busy worker takes the completion at its next tool boundary; an idle
|
|
73
|
+
// one is woken so the result reaches it now, not at its next message.
|
|
74
|
+
if (queued && typeof onWorkerCompletionQueued === 'function') {
|
|
75
|
+
setImmediate(() => {
|
|
76
|
+
try {
|
|
77
|
+
onWorkerCompletionQueued(workerId);
|
|
78
|
+
} catch {}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return queued;
|
|
71
82
|
};
|
|
72
83
|
}
|
|
73
84
|
|