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
|
@@ -8,6 +8,7 @@ import { noEnabledProvidersError } from './registry-errors.mjs';
|
|
|
8
8
|
import { providerTakesNativePdf } from '../../../attachments/store.mjs';
|
|
9
9
|
import { modelProxyClientMode } from '../../../shared/model-proxy-mode.mjs';
|
|
10
10
|
import { getModelProxyChannel } from './model-proxy-channel.mjs';
|
|
11
|
+
import { boundedProviderCatalogOp } from './catalog-deadline.mjs';
|
|
11
12
|
// OpenAI-compat provider names are self-declared by openai-compat-presets.mjs via
|
|
12
13
|
// OPENAI_COMPAT_PRESETS. No parallel list maintained here.
|
|
13
14
|
const providers = new Map();
|
|
@@ -33,6 +34,7 @@ export const OAUTH_PROVIDER_NAMES = new Set([
|
|
|
33
34
|
'mimo-oauth',
|
|
34
35
|
'cursor-oauth',
|
|
35
36
|
'antigravity-oauth',
|
|
37
|
+
'opencode-go-oauth',
|
|
36
38
|
]);
|
|
37
39
|
// Providers the most recently applied config declared with enabled:false for a
|
|
38
40
|
// USER reason. A deliberate opt-out (logout / disable in settings) must survive
|
|
@@ -178,7 +180,7 @@ const PROVIDER_MODULES = {
|
|
|
178
180
|
'cursor-api': ['./cursor.mjs', 'CursorApiProvider'],
|
|
179
181
|
openai: ['./openai-ws.mjs', 'OpenAIDirectProvider'],
|
|
180
182
|
'mixdog-local': ['./mixdog-local.mjs', 'MixdogLocalProvider'],
|
|
181
|
-
'opencode-go': ['./opencode-go.mjs', '
|
|
183
|
+
'opencode-go-oauth': ['./opencode-go-oauth.mjs', 'OpenCodeGoOAuthProvider'],
|
|
182
184
|
// Multi-wire preset gateways (wire-route.mjs picks the wire per model).
|
|
183
185
|
openrouter: ['./openrouter.mjs', 'OpenRouterProvider'],
|
|
184
186
|
deepseek: ['./deepseek.mjs', 'DeepSeekProvider'],
|
|
@@ -288,6 +290,8 @@ async function _initProvidersUnsynchronized(config, signal = null, proxyChannel
|
|
|
288
290
|
const enabledResults = await Promise.all(
|
|
289
291
|
entries.map(async ([name, cfg]) => {
|
|
290
292
|
if (!cfg.enabled) return null;
|
|
293
|
+
// A retired provider's saved config is inert: its replacement is its own entry.
|
|
294
|
+
if (Object.hasOwn(RETIRED_PROVIDER_ALIASES, name)) return null;
|
|
291
295
|
// Idempotent reuse: an enabled provider whose config signature is
|
|
292
296
|
// unchanged from the live registry is carried forward as-is. Only
|
|
293
297
|
// added or changed providers are (re)constructed below.
|
|
@@ -438,7 +442,14 @@ function _scheduleOAuthSelfHeal(name) {
|
|
|
438
442
|
_oauthSelfHealLoads.delete(name);
|
|
439
443
|
});
|
|
440
444
|
}
|
|
441
|
-
|
|
445
|
+
// Provider ids that were retired: a saved route or session naming one is served
|
|
446
|
+
// by its replacement account. When that account is not connected the lookup
|
|
447
|
+
// misses like any other unconnected provider.
|
|
448
|
+
const RETIRED_PROVIDER_ALIASES = Object.freeze({ 'opencode-go': 'opencode-go-oauth' });
|
|
449
|
+
export function getProvider(requestedName) {
|
|
450
|
+
const name = Object.hasOwn(RETIRED_PROVIDER_ALIASES, requestedName)
|
|
451
|
+
? RETIRED_PROVIDER_ALIASES[requestedName]
|
|
452
|
+
: requestedName;
|
|
442
453
|
const cached = providers.get(name);
|
|
443
454
|
if (cached) return cached;
|
|
444
455
|
// OAuth lazy fallback. Covers the boot-time race where the credential probe
|
|
@@ -560,10 +571,23 @@ export async function _withRegisteredProviderForTestAsync(name, instance, fn) {
|
|
|
560
571
|
// Shared by the startup refresh and the 24h/forced refresh below.
|
|
561
572
|
function providerCatalogRefreshFn(provider) {
|
|
562
573
|
if (typeof provider?._refreshModelCache === 'function') return () => provider._refreshModelCache();
|
|
563
|
-
if (typeof provider?.listModels === 'function') return () => provider.listModels();
|
|
574
|
+
if (typeof provider?.listModels === 'function') return (signal) => provider.listModels({ signal });
|
|
564
575
|
return null;
|
|
565
576
|
}
|
|
566
577
|
|
|
578
|
+
// One provider's refresh, bounded per provider (catalog-deadline.mjs): a hung
|
|
579
|
+
// or offline provider is reported and left out after the deadline, so the
|
|
580
|
+
// refresh epoch still completes for everyone else. Its late answer lands in
|
|
581
|
+
// its own model cache; the session catalog retries its listing after the
|
|
582
|
+
// partial-catalog backoff.
|
|
583
|
+
function boundedCatalogRefresh(name, provider, refreshFn, label) {
|
|
584
|
+
return boundedProviderCatalogOp({ name, provider, kind: 'refresh', run: refreshFn }).then(({ error }) => {
|
|
585
|
+
if (!error) return;
|
|
586
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
587
|
+
process.stderr.write(`[provider:${name}] ${label} failed: ${msg}\n`);
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
567
591
|
function armNextCatalogRefresh(startedAt = Date.now()) {
|
|
568
592
|
_catalogRefreshNotBefore = startedAt + CATALOG_REFRESH_INTERVAL_MS;
|
|
569
593
|
if (_catalogRefreshTimer) clearTimeout(_catalogRefreshTimer);
|
|
@@ -589,14 +613,7 @@ export function refreshProviderCatalogsOnStartup() {
|
|
|
589
613
|
for (const [name, provider] of providers) {
|
|
590
614
|
const refreshFn = providerCatalogRefreshFn(provider);
|
|
591
615
|
if (!refreshFn) continue;
|
|
592
|
-
pending.push(
|
|
593
|
-
Promise.resolve()
|
|
594
|
-
.then(() => refreshFn())
|
|
595
|
-
.catch((err) => {
|
|
596
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
597
|
-
process.stderr.write(`[provider:${name}] startup catalog refresh failed: ${msg}\n`);
|
|
598
|
-
})
|
|
599
|
-
);
|
|
616
|
+
pending.push(boundedCatalogRefresh(name, provider, refreshFn, 'startup catalog refresh'));
|
|
600
617
|
}
|
|
601
618
|
// Returns a completion promise so callers can invalidate stale model
|
|
602
619
|
// caches once the fresh catalogs land. Still fire-and-forget: unawaited
|
|
@@ -621,25 +638,22 @@ export function refreshCatalogs(options = {}) {
|
|
|
621
638
|
}
|
|
622
639
|
armNextCatalogRefresh();
|
|
623
640
|
const pending = [];
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
641
|
+
// The shared metadata fetch is bounded the same way: providers wait for it
|
|
642
|
+
// at most one deadline before refreshing without fresh metadata.
|
|
643
|
+
const metadataReady = boundedProviderCatalogOp({
|
|
644
|
+
name: '(model-metadata)',
|
|
645
|
+
provider: null,
|
|
646
|
+
kind: 'refresh',
|
|
647
|
+
run: () => refreshMetadataCatalog(),
|
|
648
|
+
}).then(({ error }) => {
|
|
649
|
+
if (!error) return;
|
|
650
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
651
|
+
process.stderr.write(`[model-catalog] metadata refresh failed: ${msg}\n`);
|
|
652
|
+
});
|
|
631
653
|
for (const [name, provider] of providers) {
|
|
632
654
|
const refreshFn = providerCatalogRefreshFn(provider);
|
|
633
655
|
if (!refreshFn) continue;
|
|
634
|
-
pending.push(
|
|
635
|
-
Promise.resolve()
|
|
636
|
-
.then(() => metadataReady)
|
|
637
|
-
.then(() => refreshFn())
|
|
638
|
-
.catch((err) => {
|
|
639
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
640
|
-
process.stderr.write(`[provider:${name}] catalog refresh failed: ${msg}\n`);
|
|
641
|
-
})
|
|
642
|
-
);
|
|
656
|
+
pending.push(metadataReady.then(() => boundedCatalogRefresh(name, provider, refreshFn, 'catalog refresh')));
|
|
643
657
|
}
|
|
644
658
|
// Completion promise: lets callers drop stale model caches after refresh.
|
|
645
659
|
_catalogRefreshPromise = Promise.allSettled([metadataReady, ...pending])
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
const ASK_USER_MAX_QUESTIONS = 4;
|
|
2
2
|
export const ASK_USER_MAX_TEXT = 4000;
|
|
3
|
-
export const ASK_USER_MAX_LABEL = 80;
|
|
4
3
|
|
|
5
4
|
export const ASK_USER_TOOL_DEFS = Object.freeze([
|
|
6
5
|
{
|
|
@@ -9,7 +8,7 @@ export const ASK_USER_TOOL_DEFS = Object.freeze([
|
|
|
9
8
|
trigger:
|
|
10
9
|
'Ask the user 1–4 multiple-choice questions and wait; when a plan needs the user to pick between clear options, instead of listing them in chat.',
|
|
11
10
|
description:
|
|
12
|
-
'Ask the user multiple-choice questions and wait. Only
|
|
11
|
+
'Ask the user multiple-choice questions and wait. Only for a decision only the user can make, with clear options; never for minor choices or approval of requested work, and open questions go in chat. Do not repeat the options in your message.',
|
|
13
12
|
annotations: {
|
|
14
13
|
title: 'Ask User',
|
|
15
14
|
readOnlyHint: true,
|
|
@@ -29,8 +29,7 @@ ${BATCHING[mode]}
|
|
|
29
29
|
The mode sets how work splits; an agent's description sets its role and when
|
|
30
30
|
it runs. Splitting applies to every role alike — implementation, review,
|
|
31
31
|
investigation, writing — by file, module, commit range or risk area.
|
|
32
|
-
If the plan has only one implementation scope, Lead executes it directly
|
|
33
|
-
role tied to a point in the work, such as the final check, still runs then.
|
|
32
|
+
If the plan has only one implementation scope, Lead executes it directly.
|
|
34
33
|
|
|
35
34
|
Delegation is for work whose size justifies a brief. When the brief would have
|
|
36
35
|
to spell out the edits themselves — a few localized changes whose sites Lead
|
|
@@ -41,11 +40,7 @@ Dispatch all ready independent scopes in one turn. Only a scope that requires
|
|
|
41
40
|
another's output waits. Brief each agent as the Lead rules describe.
|
|
42
41
|
A running agent receives only changes within its own scope; a new independent
|
|
43
42
|
need goes to a new agent. When scopes touch the same file, name one owner for
|
|
44
|
-
that file in each brief.
|
|
45
|
-
Lead runs the full suite once after integration.
|
|
43
|
+
that file in each brief.
|
|
46
44
|
Use only available agents; when none fits a role, Lead does that work itself
|
|
47
|
-
and never changes settings to enable one
|
|
48
|
-
unavailable, Lead alone reviews the result against the approved scope and
|
|
49
|
-
performs the required verification, without a substitute agent; never skip
|
|
50
|
-
review or describe Lead's own review as independent.`;
|
|
45
|
+
and never changes settings to enable one.`;
|
|
51
46
|
}
|
|
@@ -6,14 +6,26 @@ import { displayProgressStage, lastProgressAt } from '../../../shared/quiet-prog
|
|
|
6
6
|
import { resolveAgentStallThresholds } from '../stall-policy.mjs';
|
|
7
7
|
|
|
8
8
|
/** `rows` with progress fields on every row `runtimeEntry(sessionId)` reports
|
|
9
|
-
* in flight
|
|
10
|
-
|
|
9
|
+
* in flight. `awaitingTask` marks a row whose agent waits on its background
|
|
10
|
+
* work: 'wait' while its turn sits in `task wait`, 'background' while it is
|
|
11
|
+
* idle with a background task it handed off still running. Other rows pass
|
|
12
|
+
* through untouched. */
|
|
13
|
+
export function withTurnProgress(rows, runtimeEntry, isTurnInFlight, hasRunningTasks = () => false) {
|
|
11
14
|
return rows.map((row) => {
|
|
12
15
|
const entry = runtimeEntry(row?.sessionId);
|
|
13
|
-
if (!isTurnInFlight(entry))
|
|
16
|
+
if (!isTurnInFlight(entry)) {
|
|
17
|
+
return row?.sessionId && hasRunningTasks(row.sessionId) ? { ...row, awaitingTask: 'background' } : row;
|
|
18
|
+
}
|
|
14
19
|
const stage = typeof entry.stage === 'string' ? entry.stage : null;
|
|
20
|
+
// The in-flight turn's own start is the elapsed origin. The index row's
|
|
21
|
+
// stamp is written deferred (and a Lead row may never get one), so right
|
|
22
|
+
// after a wake it still holds the PREVIOUS turn's start or none.
|
|
23
|
+
const askStartedAt = Number(entry.askStartedAt) || 0;
|
|
24
|
+
const rowStart = Number(row.turnStartedAt) || Date.parse(row.turnStartedAt) || 0;
|
|
15
25
|
return {
|
|
16
26
|
...row,
|
|
27
|
+
...(askStartedAt > rowStart ? { turnStartedAt: new Date(askStartedAt).toISOString() } : {}),
|
|
28
|
+
...(entry.taskWaits > 0 ? { awaitingTask: 'wait' } : {}),
|
|
17
29
|
progressAt: lastProgressAt(entry) || null,
|
|
18
30
|
// `model_thinking` while the sent request waits for its headers.
|
|
19
31
|
progressStage: displayProgressStage(entry),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
1
2
|
import { estimateMessagesTokens } from '../context-utils.mjs';
|
|
2
3
|
import { estimateTokens } from '../token-estimate.mjs';
|
|
3
4
|
import { persistToolResultArtifactSync, previewToolResultArtifact } from '../tool-result-offload.mjs';
|
|
@@ -12,9 +13,64 @@ import { carryMessageId, withoutMessageIdsReplacer } from '../message-ids.mjs';
|
|
|
12
13
|
export const EXECUTION_RECOVERY_SOURCE = 'compact-execution-recovery';
|
|
13
14
|
const TOOL_HISTORY_CONTEXT_RATIO = 0.05;
|
|
14
15
|
// The working set of recent execution evidence does not grow with the window:
|
|
15
|
-
// above
|
|
16
|
-
const TOOL_HISTORY_MAX_TOKENS =
|
|
16
|
+
// above 800k, 5% kept a large block of old tool output after every Compact.
|
|
17
|
+
export const TOOL_HISTORY_MAX_TOKENS = 40_000;
|
|
17
18
|
const ARCHIVE_THRESHOLD_TOKENS = 512;
|
|
19
|
+
// Only the newest reminder survives a Compact; it names the dumps it replaced
|
|
20
|
+
// (each dump holds the reminder that preceded it, so older ones stay reachable).
|
|
21
|
+
const MAX_EARLIER_DUMPS = 4;
|
|
22
|
+
const RECOVERY_PATH_RE = /available at (.+?) \(sha256:/;
|
|
23
|
+
const EARLIER_DUMPS_RE = /^Earlier dumps \(older first\): (.+)$/m;
|
|
24
|
+
const EARLIER_DUMPS_SEPARATOR = ' | ';
|
|
25
|
+
// Below this a reference line saves little and every blob is one more file
|
|
26
|
+
// for the offload prune walk to stat and read.
|
|
27
|
+
const ARCHIVE_BLOB_MIN_BYTES = 16_384;
|
|
28
|
+
const ARCHIVED_MESSAGE_KEY = 'archivedMessage';
|
|
29
|
+
|
|
30
|
+
// One message per line (JSONL): grep returns whole messages, and line N + 1
|
|
31
|
+
// holds message index N. JSON.stringify never emits a raw newline.
|
|
32
|
+
// A message whose line reaches ARCHIVE_BLOB_MIN_BYTES is stored once per
|
|
33
|
+
// session as its own content-addressed one-line file (`storeLine`); its dump
|
|
34
|
+
// line is a reference to that file. Every later dump that still carries the
|
|
35
|
+
// message (a retained tool result, an unchanged summary) repeats only the
|
|
36
|
+
// reference, so a session's dumps no longer re-save their large bodies.
|
|
37
|
+
function serializeExecutionArchive(messages, storeLine = null) {
|
|
38
|
+
return messages
|
|
39
|
+
.map((message) => {
|
|
40
|
+
const line = `${JSON.stringify(message, withoutMessageIdsReplacer)}\n`;
|
|
41
|
+
if (!storeLine || Buffer.byteLength(line, 'utf8') < ARCHIVE_BLOB_MIN_BYTES) return line;
|
|
42
|
+
const blob = storeLine(line);
|
|
43
|
+
if (!blob) throw new Error('compact: execution history could not be archived; original context preserved');
|
|
44
|
+
// The file name is the line's sha256; the call id keeps the line greppable.
|
|
45
|
+
const reference = { path: blob.path, toolCallId: message?.toolCallId };
|
|
46
|
+
return `${JSON.stringify({ [ARCHIVED_MESSAGE_KEY]: reference })}\n`;
|
|
47
|
+
})
|
|
48
|
+
.join('');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function archivedReference(value) {
|
|
52
|
+
return value && !Object.hasOwn(value, 'role') ? value[ARCHIVED_MESSAGE_KEY] : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The archived transcript, with every referenced message read back.
|
|
56
|
+
export function parseExecutionArchive(text) {
|
|
57
|
+
return text
|
|
58
|
+
.split('\n')
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.map((line) => {
|
|
61
|
+
const value = JSON.parse(line);
|
|
62
|
+
const reference = archivedReference(value);
|
|
63
|
+
return reference ? JSON.parse(readFileSync(reference.path, 'utf8')) : value;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Dump paths a recovery reminder names, oldest first.
|
|
68
|
+
function recoveryArchivePaths(message) {
|
|
69
|
+
const content = String(message?.content ?? '');
|
|
70
|
+
const own = RECOVERY_PATH_RE.exec(content)?.[1];
|
|
71
|
+
const earlier = EARLIER_DUMPS_RE.exec(content)?.[1].split(EARLIER_DUMPS_SEPARATOR) ?? [];
|
|
72
|
+
return own ? [...earlier, own] : earlier;
|
|
73
|
+
}
|
|
18
74
|
|
|
19
75
|
export function toolHistoryBudget(contextWindow) {
|
|
20
76
|
const window = Number(contextWindow);
|
|
@@ -89,7 +145,7 @@ function withoutStaleReplay(messages) {
|
|
|
89
145
|
// call keeps its paired result.
|
|
90
146
|
function shrinkGroup(group, archive, level) {
|
|
91
147
|
const reference = (offset, what, id) =>
|
|
92
|
-
`[${what} archived without interpretation: ${archive.path}; message index ${group.start + offset}; toolCallId=${id}.`;
|
|
148
|
+
`[${what} archived without interpretation: ${archive.path}; message index ${group.start + offset} (line ${group.start + offset + 1}); toolCallId=${id}.`;
|
|
93
149
|
return group.messages.map((message, offset) => {
|
|
94
150
|
if (message.role === 'tool') {
|
|
95
151
|
if (executionTokens([message]) < ARCHIVE_THRESHOLD_TOKENS) return message;
|
|
@@ -157,22 +213,27 @@ export function buildExecutionTail(
|
|
|
157
213
|
const kept = new Map();
|
|
158
214
|
let tokens = 0;
|
|
159
215
|
let archive = null;
|
|
160
|
-
|
|
216
|
+
// One reminder at most: the newest, which replaces any earlier ones.
|
|
217
|
+
let recovery = previousRecovery.slice(-1);
|
|
161
218
|
const ensureArchive = () => {
|
|
162
219
|
if (archive) return;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
channel: 'compact-execution',
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
220
|
+
const persist = preview ? previewToolResultArtifact : persistToolResultArtifactSync;
|
|
221
|
+
// Model-readable evidence: display ids stay out of it.
|
|
222
|
+
const text = serializeExecutionArchive(transcript, (line) =>
|
|
223
|
+
persist({ sessionId, toolCallId: 'compact-execution', channel: 'compact-execution-message', content: line })
|
|
224
|
+
);
|
|
225
|
+
const dump = persist({ sessionId, toolCallId: 'compact-execution', channel: 'compact-execution', content: text });
|
|
226
|
+
if (!dump) throw new Error('compact: execution history could not be archived; original context preserved');
|
|
227
|
+
archive = dump;
|
|
228
|
+
const earlier = [...new Set(previousRecovery.flatMap(recoveryArchivePaths))]
|
|
229
|
+
.filter((path) => path !== archive.path)
|
|
230
|
+
.slice(-MAX_EARLIER_DUMPS);
|
|
231
|
+
const earlierLine = earlier.length ? `\nEarlier dumps (older first): ${earlier.join(EARLIER_DUMPS_SEPARATOR)}` : '';
|
|
171
232
|
recovery = [
|
|
172
233
|
{
|
|
173
234
|
role: 'user',
|
|
174
235
|
meta: { source: EXECUTION_RECOVERY_SOURCE, synthetic: true },
|
|
175
|
-
content: `<system-reminder>\nSome execution history was omitted or shortened to fit the tool-history budget. This is not an instruction to repeat those calls. The original calls, outcomes, and earlier recovery references are available at ${archive.path} (sha256:${archive.sha256}). Read only missing evidence when needed
|
|
236
|
+
content: `<system-reminder>\nSome execution history was omitted or shortened to fit the tool-history budget. This is not an instruction to repeat those calls. The original calls, outcomes, and earlier recovery references are available at ${archive.path} (sha256:${archive.sha256}). The dump is JSONL: line N+1 is message index N; an "archivedMessage" line names the file holding that message, so search its directory too. Read only missing evidence when needed.${earlierLine}\n</system-reminder>`,
|
|
176
237
|
},
|
|
177
238
|
];
|
|
178
239
|
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// The conversation summary as a cache-sharing fork of the session's latest
|
|
2
|
+
// provider request: the same provider, model, system prompt, tools, send
|
|
3
|
+
// options and message prefix, with the summary instruction appended as the
|
|
4
|
+
// final user turn. The provider's prompt cache serves the whole prefix, so
|
|
5
|
+
// the request pays cache-read prices for the context instead of sending a
|
|
6
|
+
// separate text-only transcript uncached.
|
|
7
|
+
//
|
|
8
|
+
// The fork is skipped (and the isolated summary request runs instead) when
|
|
9
|
+
// there is no settled request to fork, the summary route is a different
|
|
10
|
+
// provider or model (a cache is never shared across them), or the fork would
|
|
11
|
+
// not fit the window. A fork that fails, answers with a tool call or returns
|
|
12
|
+
// no text falls back the same way; cancellation and timeout propagate.
|
|
13
|
+
import { estimateMessagesTokens, estimateRequestReserveTokens, providerTokenCalibration } from '../context-utils.mjs';
|
|
14
|
+
import { disposeTransportScope, forkTransportScope } from '../../providers/transport-scope.mjs';
|
|
15
|
+
import { buildForkedRequest } from '../request-fork.mjs';
|
|
16
|
+
import { compactionForkRequest } from '../side-question-store.mjs';
|
|
17
|
+
import { HANDOFF_TIMEOUT_MAX_MS, SUMMARY_OUTPUT_TOKENS } from './constants.mjs';
|
|
18
|
+
import { enforceCompactSummarySchema, extractResponseText, forkedCompactionPrompt } from './summary.mjs';
|
|
19
|
+
|
|
20
|
+
const FORK_TOOL_STUB = 'Result omitted from this summary request; the call and its result are kept after the summary.';
|
|
21
|
+
|
|
22
|
+
function forkSignal(parent, timeoutMs) {
|
|
23
|
+
const timeout = AbortSignal.timeout(Number(timeoutMs) > 0 ? Math.floor(timeoutMs) : HANDOFF_TIMEOUT_MAX_MS);
|
|
24
|
+
return parent ? AbortSignal.any([parent, timeout]) : timeout;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function forkUsage(usage) {
|
|
28
|
+
if (!usage) return null;
|
|
29
|
+
return {
|
|
30
|
+
inputTokens: Number(usage.inputTokens) || 0,
|
|
31
|
+
outputTokens: Number(usage.outputTokens) || 0,
|
|
32
|
+
cachedTokens: Number(usage.cachedTokens) || 0,
|
|
33
|
+
cacheWriteTokens: Number(usage.cacheWriteTokens) || 0,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Why the fork cannot run on this route, or null when it can.
|
|
38
|
+
function forkSkipReason(request, route) {
|
|
39
|
+
if (!request) return 'no_settled_request';
|
|
40
|
+
const sameProvider = request.provider === route.provider || request.provider?.name === route.providerName;
|
|
41
|
+
if (!sameProvider || request.model !== route.model) return 'different_route';
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Summarize `conversationInput`'s history through a fork of the session's
|
|
47
|
+
* latest request on `route`. Returns `{ generated }` in the shape of
|
|
48
|
+
* generateFreshHandoffSummary's result, or `{ fallback }` naming why the
|
|
49
|
+
* isolated request must run instead.
|
|
50
|
+
*/
|
|
51
|
+
export async function summarizeOnSharedPrefix({ sessionId, route, conversationInput, signal, timeoutMs }) {
|
|
52
|
+
const request = compactionForkRequest(sessionId);
|
|
53
|
+
const skip = forkSkipReason(request, route);
|
|
54
|
+
if (skip) return { fallback: skip };
|
|
55
|
+
const startedAt = Date.now();
|
|
56
|
+
const { messages, opts } = buildForkedRequest(request, forkedCompactionPrompt(), FORK_TOOL_STUB);
|
|
57
|
+
const promptTokens = estimateMessagesTokens(messages) + estimateRequestReserveTokens(request.tools);
|
|
58
|
+
const window = Math.floor(Number(route.contextWindow) / providerTokenCalibration(route.providerName));
|
|
59
|
+
if (!(promptTokens + SUMMARY_OUTPUT_TOKENS <= window)) return { fallback: 'window' };
|
|
60
|
+
const requestSignal = forkSignal(signal, timeoutMs);
|
|
61
|
+
// The fork keeps the session id (prompt-cache routing) on its own transport
|
|
62
|
+
// scope: no pooled socket or response chain of the main session is touched.
|
|
63
|
+
const transportScope = forkTransportScope(sessionId, 'compact');
|
|
64
|
+
let response;
|
|
65
|
+
try {
|
|
66
|
+
response = await route.provider.send(messages, request.model, request.tools.length ? request.tools : undefined, {
|
|
67
|
+
...opts,
|
|
68
|
+
transportScope,
|
|
69
|
+
admissionOwner: sessionId,
|
|
70
|
+
signal: requestSignal,
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (requestSignal.aborted) throw requestSignal.reason ?? error;
|
|
74
|
+
return { fallback: 'error', error };
|
|
75
|
+
} finally {
|
|
76
|
+
disposeTransportScope(transportScope, route.provider);
|
|
77
|
+
}
|
|
78
|
+
requestSignal.throwIfAborted();
|
|
79
|
+
const usage = forkUsage(response?.usage);
|
|
80
|
+
if (response?.toolCalls?.length) return { fallback: 'tool_call', usage };
|
|
81
|
+
const raw = extractResponseText(response);
|
|
82
|
+
if (!raw) return { fallback: 'empty', usage };
|
|
83
|
+
const enforced = enforceCompactSummarySchema(raw, { head: conversationInput, tail: [] });
|
|
84
|
+
return {
|
|
85
|
+
generated: {
|
|
86
|
+
summary: enforced.summary,
|
|
87
|
+
summaryRepaired: enforced.repaired === true,
|
|
88
|
+
usage,
|
|
89
|
+
handoffGenerated: true,
|
|
90
|
+
diagnostics: {
|
|
91
|
+
mode: 'shared-prefix',
|
|
92
|
+
calls: 1,
|
|
93
|
+
sharedPrefixMessages: request.messages.length,
|
|
94
|
+
promptTokens,
|
|
95
|
+
summaryChars: enforced.summary.length,
|
|
96
|
+
rawSummaryChars: raw.length,
|
|
97
|
+
summaryRepaired: enforced.repaired === true,
|
|
98
|
+
durationMs: Date.now() - startedAt,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
splitProtectedContext,
|
|
19
19
|
} from './messages.mjs';
|
|
20
20
|
import { activeTurnContinuationMessage } from './continuation.mjs';
|
|
21
|
-
import { buildExecutionTail } from './execution-tail.mjs';
|
|
21
|
+
import { EXECUTION_RECOVERY_SOURCE, buildExecutionTail } from './execution-tail.mjs';
|
|
22
22
|
import { latestSkillBodies } from '../../context/skill-state.mjs';
|
|
23
23
|
import { effectiveBudget } from './budget.mjs';
|
|
24
24
|
import { stripRuntimeUserContext, withRuntimeUserContext } from '../runtime-user-context.mjs';
|
|
@@ -48,7 +48,13 @@ function combinedSignal(parent, timeoutMs) {
|
|
|
48
48
|
|
|
49
49
|
function splitFreshSource(messages) {
|
|
50
50
|
const sanitized = redactToolCallSecretsInMessages(reconcileDedupStubs(sanitizeToolPairs(messages)));
|
|
51
|
-
const
|
|
51
|
+
const split = splitProtectedContext(sanitized);
|
|
52
|
+
// A recovery reminder right after the system prompt reads as protected
|
|
53
|
+
// prefix and would outlive every pass. It belongs to the conversation, where
|
|
54
|
+
// buildExecutionTail replaces it with the newest compaction's reminder.
|
|
55
|
+
const isRecovery = (message) => message?.meta?.source === EXECUTION_RECOVERY_SOURCE;
|
|
56
|
+
const protectedPrefix = split.protectedPrefix.filter((message) => !isRecovery(message));
|
|
57
|
+
const conversation = [...split.protectedPrefix.filter(isRecovery), ...split.conversation];
|
|
52
58
|
let previousSummaryMessage = null;
|
|
53
59
|
let previousAck = null;
|
|
54
60
|
for (let index = conversation.length - 1; index >= 0; index -= 1) {
|
|
@@ -61,6 +61,22 @@ Rules:
|
|
|
61
61
|
- Use the same language as the active user thread when it is clear.
|
|
62
62
|
- Do not mention the summary process or that context was compacted.`;
|
|
63
63
|
|
|
64
|
+
// The final user turn of a summary forked from the session's own cached
|
|
65
|
+
// request: the conversation itself is the history, so the instruction carries
|
|
66
|
+
// what COMPACTION_SYSTEM_PROMPT says for the isolated request.
|
|
67
|
+
export function forkedCompactionPrompt() {
|
|
68
|
+
return [
|
|
69
|
+
'<compaction-request>',
|
|
70
|
+
'Pause the task for this one response. No tools are available here: do not call any.',
|
|
71
|
+
'Write the anchored handoff summary this work continues from. The latest user request and the recent tool calls and results are kept verbatim after it, so focus on the older context that still matters.',
|
|
72
|
+
'If the conversation holds an earlier summary, update it: preserve still-true details, remove stale details, and merge in the new facts.',
|
|
73
|
+
'Do not answer the conversation itself. Do not mention that you are summarizing, compacting, or merging context. Respond in the same language as the conversation.',
|
|
74
|
+
'',
|
|
75
|
+
SUMMARY_TEMPLATE,
|
|
76
|
+
'</compaction-request>',
|
|
77
|
+
].join('\n');
|
|
78
|
+
}
|
|
79
|
+
|
|
64
80
|
// The index-independent part of one transcript line. A batch fit rebuilds the
|
|
65
81
|
// prompt for many overlapping slices of the same head; `lineCache` (scoped to
|
|
66
82
|
// one fit sequence at a fixed perMessageChars) keeps each message's rendering
|
|
@@ -189,6 +189,15 @@ export function readTopLevelLifecycleRecord(raw) {
|
|
|
189
189
|
// — for EVERY top-level key, since consumers reserialize and act on the
|
|
190
190
|
// whole record, not only on the lifecycle three.
|
|
191
191
|
if (!hasUniqueTopLevelKeys(raw)) return LIFECYCLE_SCAN_CONFLICT;
|
|
192
|
+
return lifecycleRecordOf(doc);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Step 3 alone, for a reader that has already proven `doc` is the complete,
|
|
197
|
+
* well-formed, duplicate-free top-level object of its document (see
|
|
198
|
+
* store-record-strict-scan.mjs): the record, or LIFECYCLE_SCAN_CONFLICT.
|
|
199
|
+
*/
|
|
200
|
+
export function lifecycleRecordOf(doc) {
|
|
192
201
|
const has = (key) => Object.hasOwn(doc, key);
|
|
193
202
|
const record = { doc, id: undefined, closed: undefined, generation: undefined };
|
|
194
203
|
if (has('id')) {
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
SUMMARY_OUTPUT_TOKENS,
|
|
16
16
|
COMPACT_TARGET_MIN_TOKENS,
|
|
17
17
|
} from '../compact.mjs';
|
|
18
|
+
import { summarizeOnSharedPrefix } from '../compact/forked-summary.mjs';
|
|
19
|
+
import { retireCompactionFork } from '../side-question-store.mjs';
|
|
18
20
|
|
|
19
21
|
// Conversation summarization triggers independently of the post-compact target:
|
|
20
22
|
// at 5% of the window, capped so a large window does not carry tens of
|
|
@@ -110,6 +112,39 @@ function summarizeConversation(target, ctx) {
|
|
|
110
112
|
);
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
function sumUsage(a, b) {
|
|
116
|
+
if (!a || !b) return a || b || null;
|
|
117
|
+
return Object.fromEntries(Object.keys(a).map((key) => [key, (a[key] || 0) + (Number(b[key]) || 0)]));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Summary policy. On the session's own provider and model the summary forks
|
|
121
|
+
// the latest cached request (compact/forked-summary.mjs): the context is
|
|
122
|
+
// already in the prompt cache, so reading it costs a tenth to a twentieth of
|
|
123
|
+
// sending the text-only transcript uncached, and the summary sees the full
|
|
124
|
+
// execution context. A different summary route (the maintenance model)
|
|
125
|
+
// cannot share that cache; there the isolated text-only request runs: on
|
|
126
|
+
// the usage ledger it cost a median $0.16 and 24.5 s against a projected
|
|
127
|
+
// $0.19 and 27 s for a cache-read fork of the main model's median 500k-token
|
|
128
|
+
// context (the fork is cheaper than a same-model isolated request, $0.20).
|
|
129
|
+
// A fork that cannot run or fails falls back to the isolated request.
|
|
130
|
+
async function summarizeOnRoute(route, ctx) {
|
|
131
|
+
const shared = await summarizeOnSharedPrefix({
|
|
132
|
+
sessionId: ctx.sessionId,
|
|
133
|
+
route,
|
|
134
|
+
conversationInput: ctx.conversationInput,
|
|
135
|
+
signal: ctx.signal,
|
|
136
|
+
timeoutMs: ctx.compactPolicy.handoffTimeoutMs,
|
|
137
|
+
});
|
|
138
|
+
if (shared.generated) return shared.generated;
|
|
139
|
+
ctx.signal?.throwIfAborted();
|
|
140
|
+
const generated = await summarizeConversation(route, ctx);
|
|
141
|
+
return {
|
|
142
|
+
...generated,
|
|
143
|
+
usage: sumUsage(generated.usage, shared.usage),
|
|
144
|
+
diagnostics: { ...generated.diagnostics, mode: 'isolated', sharedPrefixFallback: shared.fallback },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
113
148
|
// A maintenance summary model that is out of quota must not take the whole
|
|
114
149
|
// turn down: the conversation's own model still has budget and can
|
|
115
150
|
// summarize. Only exhaustion reroutes — every other compaction failure
|
|
@@ -118,7 +153,7 @@ function summarizeConversation(target, ctx) {
|
|
|
118
153
|
async function summarizeWithQuotaFallback(ctx, routeInput) {
|
|
119
154
|
const route = await resolveCompactionRoute(routeInput);
|
|
120
155
|
try {
|
|
121
|
-
return { route, generated: await
|
|
156
|
+
return { route, generated: await summarizeOnRoute(route, ctx) };
|
|
122
157
|
} catch (error) {
|
|
123
158
|
ctx.signal?.throwIfAborted();
|
|
124
159
|
const status = Number(error?.status || error?.httpStatus || error?.response?.status || 0);
|
|
@@ -132,7 +167,7 @@ async function summarizeWithQuotaFallback(ctx, routeInput) {
|
|
|
132
167
|
throw error;
|
|
133
168
|
}
|
|
134
169
|
try {
|
|
135
|
-
return { route: fallback, generated: await
|
|
170
|
+
return { route: fallback, generated: await summarizeOnRoute(fallback, ctx) };
|
|
136
171
|
} catch (fallbackError) {
|
|
137
172
|
fallbackError.compactRoute = { provider: fallback.providerName, model: fallback.model };
|
|
138
173
|
throw fallbackError;
|
|
@@ -236,6 +271,8 @@ export async function runFreshContextCompact({
|
|
|
236
271
|
diagnostics: { ...result.diagnostics, noOp: true, reason: 'no_token_reduction' },
|
|
237
272
|
};
|
|
238
273
|
}
|
|
274
|
+
// The rewritten transcript is no longer what the saved request carried.
|
|
275
|
+
if (result.freshContext) retireCompactionFork(sessionId);
|
|
239
276
|
return result;
|
|
240
277
|
};
|
|
241
278
|
const pipeline = {
|
|
@@ -9,7 +9,7 @@ import { beginSessionApprovalWait } from '../../manager/runtime-liveness.mjs';
|
|
|
9
9
|
|
|
10
10
|
const isArgsObject = (value) => value && typeof value === 'object' && !Array.isArray(value);
|
|
11
11
|
|
|
12
|
-
async function askApproval(call, { callerSessionId, executeOpts, toolApprovalHook, decision
|
|
12
|
+
async function askApproval(call, { callerSessionId, executeOpts, toolApprovalHook, decision }) {
|
|
13
13
|
const askReason = String(decision?.reason || 'approval requested by hook').trim();
|
|
14
14
|
// While the user decides, the session is waiting on them, not working.
|
|
15
15
|
const releaseApprovalWait = beginSessionApprovalWait(callerSessionId);
|
|
@@ -31,33 +31,23 @@ async function askApproval(call, { callerSessionId, executeOpts, toolApprovalHoo
|
|
|
31
31
|
}
|
|
32
32
|
if (askOutcome.denial) return { denial: askOutcome.denial };
|
|
33
33
|
const approval = askOutcome.approval;
|
|
34
|
-
if (
|
|
34
|
+
if (approval && typeof approval === 'object' && isArgsObject(approval.args)) call.args = approval.args;
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
40
|
-
* decision is 'ask' (elevated launch). Runs after the final call is known.
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* @returns {
|
|
39
|
+
* Elevation pass for a shell call (or a terminal `run`) whose exec policy
|
|
40
|
+
* decision is 'ask' (elevated launch). Runs after the final call is known.
|
|
41
|
+
* Mixdog does not prompt first: the OS elevation prompt (Windows UAC) is the
|
|
42
|
+
* user's confirmation. A deny is never lifted.
|
|
43
|
+
* @returns {{ elevationApproved: boolean }}
|
|
44
44
|
*/
|
|
45
|
-
export
|
|
45
|
+
export function applyShellElevationGate(call) {
|
|
46
46
|
const gated = call.name === 'shell' || (call.name === 'terminal' && call.args?.action === 'run');
|
|
47
47
|
if (!gated || !isArgsObject(call.args) || call.args.command == null) {
|
|
48
48
|
return { elevationApproved: false };
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
if (decision.decision !== 'ask') return { elevationApproved: false };
|
|
52
|
-
const denied = await askApproval(call, {
|
|
53
|
-
callerSessionId,
|
|
54
|
-
executeOpts,
|
|
55
|
-
toolApprovalHook,
|
|
56
|
-
decision,
|
|
57
|
-
keepArgs: true,
|
|
58
|
-
});
|
|
59
|
-
if (denied) return denied;
|
|
60
|
-
return { elevationApproved: true };
|
|
50
|
+
return { elevationApproved: evaluateExecPolicy(String(call.args.command)).decision === 'ask' };
|
|
61
51
|
}
|
|
62
52
|
|
|
63
53
|
/**
|
|
@@ -74,9 +74,8 @@ async function executeToolOwned(name, args, cwd, callerSessionId, sessionRef, ex
|
|
|
74
74
|
const unavailable = bridgeTargetUnavailable(sessionRef, finalCall.schemaName || finalCall.name);
|
|
75
75
|
if (unavailable) return unavailable;
|
|
76
76
|
}
|
|
77
|
-
// Elevated launches
|
|
78
|
-
const elevation =
|
|
79
|
-
if (elevation.denial) return elevation.denial;
|
|
77
|
+
// Elevated launches skip Mixdog's own prompt; the OS elevation prompt confirms them.
|
|
78
|
+
const elevation = applyShellElevationGate(call);
|
|
80
79
|
throwIfAborted(executeOpts.signal);
|
|
81
80
|
const result = await runReadOnlyIoWithDeadline(call.name, executeOpts.signal || null, async (deadlineSignal) => {
|
|
82
81
|
let callOpts = executeOpts;
|
|
@@ -130,6 +130,9 @@ export function buildAgentLoopOptions({
|
|
|
130
130
|
checkpoint.schedule(true);
|
|
131
131
|
relay(askOpts?.onAssistantToolCallObserved, call, detail);
|
|
132
132
|
},
|
|
133
|
+
// A provider output item started (a tool call, a phased message): display
|
|
134
|
+
// only, so it bypasses the interruption journal.
|
|
135
|
+
onStreamItemStart: optionalFn(askOpts?.onStreamItemStart),
|
|
133
136
|
onProviderSendStarted: () => {
|
|
134
137
|
interruption.markProviderSendStarted();
|
|
135
138
|
checkpoint.schedule(true);
|