mixdog 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/lib/gate-scheduler.mjs +2 -2
- package/scripts/lib/run-node-tests.mjs +5 -14
- package/scripts/mobile-release.mjs +5 -4
- package/scripts/provider-pingpong-live.mjs +9 -9
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/reviewer/AGENT.md +2 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +11 -0
- package/src/defaults/skills/browser-use/SKILL.md +2 -1
- package/src/defaults/skills/computer-use/SKILL.md +3 -1
- package/src/defaults/skills/visualize/SKILL.md +3 -2
- package/src/lib/rules-builder.cjs +1 -1
- package/src/output-styles/common.md +22 -5
- package/src/output-styles/detailed.md +7 -5
- package/src/output-styles/minimal.md +7 -3
- package/src/output-styles/simple.md +2 -3
- package/src/rules/agent/AGENT.md +3 -1
- package/src/rules/lead/LEAD.md +10 -7
- package/src/rules/shared/10-tool-workflow.md +2 -1
- package/src/rules/shared/60-verification.md +4 -5
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -1
- package/src/runtime/agent/orchestrator/config.mjs +6 -0
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +10 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +53 -60
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +2 -3
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +40 -19
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/catalog-deadline.mjs +163 -0
- package/src/runtime/agent/orchestrator/providers/catalog-failures.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +38 -14
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +100 -26
- package/src/runtime/agent/orchestrator/providers/lib/send-callbacks.mjs +24 -0
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +49 -4
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +0 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +45 -16
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-connect.mjs +196 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +74 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs +194 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs +353 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +293 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +33 -100
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +23 -14
- package/src/runtime/agent/orchestrator/providers/registry.mjs +41 -27
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +2 -3
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +3 -8
- package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +74 -13
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -2
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +39 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +9 -19
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +10 -7
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +62 -22
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +81 -34
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/request-fork.mjs +47 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/side-question.mjs +5 -43
- package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +73 -6
- package/src/runtime/agent/orchestrator/session/store/load-session.mjs +40 -0
- package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-header.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +255 -32
- package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +306 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +70 -21
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +46 -30
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +144 -45
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +32 -13
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +364 -38
- package/src/runtime/agent/orchestrator/session/store.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +43 -12
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +23 -6
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -6
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +13 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +40 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +37 -34
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +0 -4
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +29 -27
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +28 -11
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +12 -3
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +203 -101
- package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +90 -41
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-delivery.mjs +199 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +165 -39
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +25 -13
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +7 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +19 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-line-collector.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +25 -7
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-render.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +12 -5
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +61 -22
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +32 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +193 -19
- package/src/runtime/agent/orchestrator/tools/builtin/shell-direct-exe.mjs +57 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +119 -32
- package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +26 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +13 -28
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +90 -11
- package/src/runtime/agent/orchestrator/tools/builtin/wait-budgets.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +54 -10
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +22 -28
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +11 -7
- package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +126 -9
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +15 -6
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +40 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promotion-results.mjs +13 -3
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +35 -4
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +39 -23
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/attachments/office-extract.mjs +11 -9
- package/src/runtime/memory/lib/embedding-tokenizer.mjs +32 -18
- package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -1
- package/src/runtime/memory/lib/tool-call-handler.mjs +7 -5
- package/src/runtime/office/authoring/pptx-script-normalize.mjs +11 -8
- package/src/runtime/office/core/tabular.mjs +1 -1
- package/src/runtime/office/design/library/design-template-inspect.mjs +7 -4
- package/src/runtime/office/portable/docx-formatting.mjs +1 -1
- package/src/runtime/office/portable/docx-runs.mjs +1 -1
- package/src/runtime/office/portable/docx-tracked-edits.mjs +2 -2
- package/src/runtime/office/portable/portable-docx-edits.mjs +11 -10
- package/src/runtime/office/portable/portable-docx-operations.mjs +21 -17
- package/src/runtime/office/portable/portable-docx-parts.mjs +15 -9
- package/src/runtime/office/portable/portable-docx-styles.mjs +4 -1
- package/src/runtime/office/portable/portable-docx-xml.mjs +9 -9
- package/src/runtime/office/portable/portable-opc.mjs +27 -13
- package/src/runtime/office/portable/portable-pivot.mjs +3 -3
- package/src/runtime/office/portable/portable-pptx-chart.mjs +3 -3
- package/src/runtime/office/portable/portable-pptx-charts.mjs +24 -18
- package/src/runtime/office/portable/portable-pptx-core.mjs +5 -5
- package/src/runtime/office/portable/portable-pptx-deck.mjs +8 -4
- package/src/runtime/office/portable/portable-pptx-package.mjs +7 -4
- package/src/runtime/office/portable/portable-pptx-shapes.mjs +18 -9
- package/src/runtime/office/portable/portable-sheet-parts.mjs +9 -3
- package/src/runtime/office/portable/portable-sheet-xml.mjs +5 -5
- package/src/runtime/office/portable/portable-xlsx-charts.mjs +4 -1
- package/src/runtime/office/portable/portable-xlsx-drawings.mjs +10 -6
- package/src/runtime/office/portable/portable-xlsx-operations.mjs +8 -5
- package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +1 -1
- package/src/runtime/office/portable/portable-xlsx-reference-shift.mjs +11 -7
- package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +6 -3
- package/src/runtime/office/portable/portable-xml.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +22 -13
- package/src/runtime/shared/background-tasks.mjs +60 -20
- package/src/runtime/shared/child-spawn-gate.mjs +26 -16
- package/src/runtime/shared/config.mjs +0 -10
- package/src/runtime/shared/file-lock.mjs +7 -1
- package/src/runtime/shared/file-permissions-worker.mjs +38 -0
- package/src/runtime/shared/file-permissions.d.mts +19 -4
- package/src/runtime/shared/file-permissions.mjs +310 -4
- package/src/runtime/shared/git-runner-worker.mjs +6 -78
- package/src/runtime/shared/llm/http-agent.mjs +7 -3
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +10 -0
- package/src/runtime/shared/llm/usage-accounting.mjs +2 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +32 -3
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +12 -5
- package/src/runtime/shared/llm/usage-ledger-worker.mjs +1 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +20 -5
- package/src/runtime/shared/memory-snapshot.mjs +5 -1
- package/src/runtime/shared/notify-trace.mjs +8 -1
- package/src/runtime/shared/pristine-execution-contract.json +5 -1
- package/src/runtime/shared/process-runner-worker.mjs +7 -0
- package/src/runtime/shared/process-runner.mjs +38 -0
- package/src/runtime/shared/provider-accounts.mjs +2 -1
- package/src/runtime/shared/provider-api-key.mjs +0 -1
- package/src/runtime/shared/read-row-numbers.mjs +1 -1
- package/src/runtime/shared/resource-admission.mjs +60 -18
- package/src/runtime/shared/run-child.mjs +84 -0
- package/src/runtime/shared/session-activity-signal.mjs +23 -0
- package/src/runtime/shared/shell-output-summary.mjs +23 -0
- package/src/runtime/shared/singleton-owner.mjs +126 -42
- package/src/runtime/shared/tool-output-limit.mjs +4 -2
- package/src/runtime/shared/tool-output-marker.mjs +58 -0
- package/src/runtime/shared/user-data-guard.mjs +28 -9
- package/src/runtime/shared/worktrees/activity-gate.mjs +33 -8
- package/src/runtime/shared/worktrees/git-head.mjs +20 -2
- package/src/runtime/shared/worktrees/lock.mjs +3 -2
- package/src/runtime/terminal-bridge/tool-defs.mjs +1 -1
- package/src/runtime/tidy/runners/biome.mjs +3 -1
- package/src/runtime/tidy/shadow.mjs +2 -1
- package/src/runtime/web-search/index.mjs +5 -7
- package/src/runtime/web-search/lib/formatter.mjs +42 -6
- package/src/runtime/web-search/tool-defs.mjs +6 -1
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +4 -3
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -4
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +3 -3
- package/src/session-runtime/lifecycle/shared.mjs +19 -2
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +2 -0
- package/src/session-runtime/lifecycle/teardown.mjs +5 -2
- package/src/session-runtime/lifecycle-api.mjs +3 -1
- package/src/session-runtime/provider-auth/login.mjs +10 -6
- package/src/session-runtime/provider-auth/setup.mjs +19 -2
- package/src/session-runtime/provider-catalog-cache.mjs +101 -25
- package/src/session-runtime/provider-models/catalog-load.mjs +14 -3
- package/src/session-runtime/provider-models/row-hydration.mjs +4 -0
- package/src/session-runtime/provider-models.mjs +76 -10
- package/src/session-runtime/provider-readiness.mjs +4 -0
- package/src/session-runtime/services/agent-tool/close-flow.mjs +2 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +10 -2
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +13 -4
- package/src/session-runtime/services/agent-tool/notify.mjs +15 -4
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +134 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/deferred-spawn.mjs +2 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -1
- package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +8 -3
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +4 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +44 -10
- package/src/session-runtime/services/agent-tool.mjs +20 -0
- package/src/session-runtime/services/provider-admin.mjs +92 -20
- package/src/session-runtime/services/usage-dashboard-model.mjs +2 -2
- package/src/session-runtime/services/usage-dashboard.mjs +27 -69
- package/src/session-runtime/turn/session-ops.mjs +2 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/usage-stats-api.mjs +6 -1
- package/src/standalone/channel-transport/http-routes.mjs +10 -9
- package/src/standalone/daemon-desktop-runtime.mjs +20 -7
- package/src/standalone/daemon-owner.mjs +117 -25
- package/src/standalone/daemon.mjs +43 -13
- package/src/standalone/desktop-service-registry.mjs +32 -5
- package/src/standalone/session-call-cache.mjs +6 -0
- package/src/standalone/session-protocol.mjs +1 -1
- package/src/standalone/session-service/retention/busy.mjs +7 -2
- package/src/standalone/session-transport/routes.mjs +7 -6
- package/src/tui/app/model-options.mjs +39 -2
- package/src/tui/app/model-picker.mjs +16 -2
- package/src/tui/app/provider-setup-picker.mjs +1 -2
- package/src/tui/dist/index.mjs +436 -123
- package/src/tui/session/notice-text.mjs +0 -1
- package/src/tui/session/session-api/intake/submission.mjs +7 -13
- package/src/tui/session/session-api/integrations.mjs +1 -5
- package/src/tui/session/session-flow/drain.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +46 -0
- package/src/tui/session/session-flow.mjs +2 -0
- package/src/tui/session/turn-stream.mjs +33 -0
- package/src/tui/session/turn.mjs +30 -3
- package/src/ui/statusline.mjs +1 -6
- package/src/ui/streaming-markdown-heal.mjs +94 -25
- package/src/vendor/statusline/bin/statusline-route.mjs +0 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +0 -1
- package/src/workflows/default/WORKFLOW.md +9 -13
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// One marker vocabulary for every non-shell tool output that is not the whole
|
|
2
|
+
// answer. Two shapes, never mixed:
|
|
3
|
+
// window `[lines a-b of N; offset:N for more]` the agent asked for this slice
|
|
4
|
+
// cap `[output capped at X KB; …; offset:N for more]` the tool cut it
|
|
5
|
+
// A cap marker always carries the continuation (offset / cursor, else narrowing
|
|
6
|
+
// advice) and, when the complete output was saved, `full output: <path>`.
|
|
7
|
+
|
|
8
|
+
export function formatCapSize(bytes) {
|
|
9
|
+
const n = Number(bytes);
|
|
10
|
+
if (n < 1024) return `${Math.max(0, Math.trunc(n))} bytes`;
|
|
11
|
+
const kb = n / 1024;
|
|
12
|
+
return `${Number.isInteger(kb) ? kb : kb.toFixed(1)} KB`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Requested window: `[lines 5-27 of 90; offset:28 for more]` (no continuation
|
|
17
|
+
* at the end). `capBytes` marks a window the byte cap shortened:
|
|
18
|
+
* `[lines 5-27 of 90; output capped at 50 KB; offset:28 for more]`.
|
|
19
|
+
*/
|
|
20
|
+
export function windowMarker({
|
|
21
|
+
unit = 'lines',
|
|
22
|
+
from,
|
|
23
|
+
to,
|
|
24
|
+
total = null,
|
|
25
|
+
nextOffset = null,
|
|
26
|
+
capBytes = null,
|
|
27
|
+
offsetKey = 'offset',
|
|
28
|
+
}) {
|
|
29
|
+
const of = total == null ? '' : ` of ${total}`;
|
|
30
|
+
const cap = capBytes == null ? '' : `; output capped at ${formatCapSize(capBytes)}`;
|
|
31
|
+
const more = nextOffset == null ? '' : `; ${offsetKey}:${nextOffset} for more`;
|
|
32
|
+
return `[${unit} ${from}-${to}${of}${cap}${more}]`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Cap hit. `limit` is bytes (number) or a ready label ("500 rows"). `shown`
|
|
37
|
+
* says what survived. Continuation is `nextOffset`, else `cursor`, else
|
|
38
|
+
* `narrow` advice (a default is supplied so it is never missing).
|
|
39
|
+
*/
|
|
40
|
+
export function capMarker({
|
|
41
|
+
limit,
|
|
42
|
+
shown = '',
|
|
43
|
+
nextOffset = null,
|
|
44
|
+
offsetKey = 'offset',
|
|
45
|
+
cursor = null,
|
|
46
|
+
narrow = '',
|
|
47
|
+
fullPath = '',
|
|
48
|
+
}) {
|
|
49
|
+
const parts = [`output capped at ${typeof limit === 'number' ? formatCapSize(limit) : limit}`];
|
|
50
|
+
if (shown) parts.push(shown);
|
|
51
|
+
if (nextOffset != null) parts.push(`${offsetKey}:${nextOffset} for more`);
|
|
52
|
+
else if (cursor != null) parts.push(`cursor:${cursor} for more`);
|
|
53
|
+
if (narrow || (nextOffset == null && cursor == null)) {
|
|
54
|
+
parts.push(`narrow: ${narrow || 'restrict the path, pattern or scope'}`);
|
|
55
|
+
}
|
|
56
|
+
if (fullPath) parts.push(`full output: ${fullPath}`);
|
|
57
|
+
return `[${parts.join('; ')}]`;
|
|
58
|
+
}
|
|
@@ -58,8 +58,26 @@ function scopeDirFor(dataDir) {
|
|
|
58
58
|
return join(getBackupRoot(), `${SCOPE_PREFIX}${id}`);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
let lastBackupMs = 0;
|
|
62
|
+
let backupSeq = 0;
|
|
63
|
+
|
|
64
|
+
// Claims `<iso-ms>-<10-digit seq>-<reason>` with an exclusive mkdir, so names
|
|
65
|
+
// sort by timestamp, then sequence, and an existing backup is never reused
|
|
66
|
+
// (EEXIST retries with the next sequence). The clamp keeps a backwards clock
|
|
67
|
+
// from reordering this process's backups.
|
|
68
|
+
function reserveBackupDir(scopeDir, reason) {
|
|
69
|
+
mkdirSync(scopeDir, { recursive: true });
|
|
70
|
+
for (;;) {
|
|
71
|
+
lastBackupMs = Math.max(Date.now(), lastBackupMs);
|
|
72
|
+
const time = new Date(lastBackupMs).toISOString().replace(/[:.]/g, '-');
|
|
73
|
+
const backupDir = join(scopeDir, `${time}-${String(backupSeq++).padStart(10, '0')}-${safeReason(reason)}`);
|
|
74
|
+
try {
|
|
75
|
+
mkdirSync(backupDir);
|
|
76
|
+
return backupDir;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
63
81
|
}
|
|
64
82
|
|
|
65
83
|
function safeReason(reason) {
|
|
@@ -329,11 +347,6 @@ function skipBackups() {
|
|
|
329
347
|
return process.env.MIXDOG_SKIP_USER_DATA_BACKUP === '1' || process.env.MIXDOG_SKIP_USER_DATA_BACKUP === 'true';
|
|
330
348
|
}
|
|
331
349
|
|
|
332
|
-
function backupTarget(dataDir, reason) {
|
|
333
|
-
const scopeDir = scopeDirFor(dataDir);
|
|
334
|
-
return { scopeDir, backupDir: join(scopeDir, `${stamp()}-${safeReason(reason)}`) };
|
|
335
|
-
}
|
|
336
|
-
|
|
337
350
|
// Mark first so the manifest carries this incarnation's instanceId; the
|
|
338
351
|
// manifest goes last, so an interrupted copy is never restorable.
|
|
339
352
|
function finishBackup(dataDir, backupDir, reason) {
|
|
@@ -354,7 +367,8 @@ function finishBackup(dataDir, backupDir, reason) {
|
|
|
354
367
|
export function backupUserData(dataDir, reason = 'snapshot') {
|
|
355
368
|
if (skipBackups()) return { dir: null, copied: [] };
|
|
356
369
|
if (!dataDir || !existsSync(dataDir)) return { dir: null, copied: [] };
|
|
357
|
-
const
|
|
370
|
+
const scopeDir = scopeDirFor(dataDir);
|
|
371
|
+
const backupDir = reserveBackupDir(scopeDir, reason);
|
|
358
372
|
const copied = [];
|
|
359
373
|
for (const rel of USER_DATA_FILES) {
|
|
360
374
|
const src = join(dataDir, rel);
|
|
@@ -370,6 +384,8 @@ export function backupUserData(dataDir, reason = 'snapshot') {
|
|
|
370
384
|
if (process.env.MIXDOG_SETUP_QUIET !== '1') {
|
|
371
385
|
process.stderr.write(`[user-data-backup] ${reason}: copied ${copied.length} file(s) to ${backupDir}\n`);
|
|
372
386
|
}
|
|
387
|
+
} else {
|
|
388
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
373
389
|
}
|
|
374
390
|
return { dir: copied.length > 0 ? backupDir : null, copied };
|
|
375
391
|
}
|
|
@@ -413,7 +429,8 @@ async function pruneBackupsAsync(scopeDir, keep = 40) {
|
|
|
413
429
|
export async function backupUserDataAsync(dataDir, reason = 'snapshot') {
|
|
414
430
|
if (skipBackups()) return { dir: null, copied: [] };
|
|
415
431
|
if (!dataDir || !existsSync(dataDir)) return { dir: null, copied: [] };
|
|
416
|
-
const
|
|
432
|
+
const scopeDir = scopeDirFor(dataDir);
|
|
433
|
+
const backupDir = reserveBackupDir(scopeDir, reason);
|
|
417
434
|
const copied = [];
|
|
418
435
|
for (const rel of USER_DATA_FILES) {
|
|
419
436
|
const src = join(dataDir, rel);
|
|
@@ -429,6 +446,8 @@ export async function backupUserDataAsync(dataDir, reason = 'snapshot') {
|
|
|
429
446
|
if (process.env.MIXDOG_SETUP_QUIET !== '1') {
|
|
430
447
|
process.stderr.write(`[user-data-backup] ${reason}: copied ${copied.length} file(s) to ${backupDir}\n`);
|
|
431
448
|
}
|
|
449
|
+
} else {
|
|
450
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
432
451
|
}
|
|
433
452
|
return { dir: copied.length > 0 ? backupDir : null, copied };
|
|
434
453
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { readFileSync, renameSync } from 'node:fs';
|
|
4
4
|
import { isAbsolute, join } from 'node:path';
|
|
5
5
|
import { withFileLockSync, writeJsonAtomicSync } from '../atomic-file.mjs';
|
|
6
6
|
import { isPidAliveOrUnknown } from '../pid-liveness.mjs';
|
|
@@ -34,18 +34,43 @@ export function isExternalCheckout(folder) {
|
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Reads the ledger under its lock. A missing ledger is empty. A ledger that is
|
|
39
|
+
* not JSON is a torn write (a crash or power loss after the rename committed
|
|
40
|
+
* the file's size but not its bytes leaves it NUL-filled on NTFS): nothing in
|
|
41
|
+
* it can be recovered and every lease it held belongs to a process that died
|
|
42
|
+
* with it, so refusing forever would only lock the project out. It is moved
|
|
43
|
+
* aside as `<FILE>.corrupt-<time>` for inspection and admission starts over
|
|
44
|
+
* empty. A ledger that cannot be read at all (permissions, I/O, a directory in
|
|
45
|
+
* its place) is not an empty one and still refuses, as does one that moves
|
|
46
|
+
* aside unsuccessfully: an unreadable file is never overwritten.
|
|
47
|
+
*/
|
|
48
|
+
function readLedger(path) {
|
|
49
|
+
let text;
|
|
50
|
+
try {
|
|
51
|
+
text = readFileSync(path, 'utf8');
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error?.code === 'ENOENT') return { version: 1, leases: [] };
|
|
54
|
+
throw worktreeRefusal('workspace-admission-unreadable');
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(text);
|
|
58
|
+
} catch {
|
|
59
|
+
try {
|
|
60
|
+
renameSync(path, `${path}.corrupt-${Date.now()}`);
|
|
61
|
+
} catch {
|
|
62
|
+
throw worktreeRefusal('workspace-admission-unreadable');
|
|
63
|
+
}
|
|
64
|
+
return { version: 1, leases: [] };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
37
68
|
function mutate(commonDir, update) {
|
|
38
69
|
const path = join(commonDir, FILE);
|
|
39
70
|
return withFileLockSync(
|
|
40
71
|
`${path}.lock`,
|
|
41
72
|
() => {
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
state = JSON.parse(readFileSync(path, 'utf8'));
|
|
45
|
-
} catch (error) {
|
|
46
|
-
if (error?.code === 'ENOENT') state = { version: 1, leases: [] };
|
|
47
|
-
else throw worktreeRefusal('workspace-admission-unreadable');
|
|
48
|
-
}
|
|
73
|
+
const state = readLedger(path);
|
|
49
74
|
if (
|
|
50
75
|
state?.version !== 1 ||
|
|
51
76
|
!Array.isArray(state.leases) ||
|
|
@@ -46,6 +46,21 @@ function isFile(path, strict = false) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
const GIT_LAYOUT_ENTRIES = ['objects', 'refs', 'config', 'commondir', 'packed-refs', 'index'];
|
|
50
|
+
|
|
51
|
+
/** True when a `.git` directory holds any entry git itself would create. Permission errors propagate. */
|
|
52
|
+
function hasGitLayout(dotGit) {
|
|
53
|
+
return GIT_LAYOUT_ENTRIES.some((name) => {
|
|
54
|
+
try {
|
|
55
|
+
statSync(resolve(dotGit, name));
|
|
56
|
+
return true;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (!['ENOENT', 'ENOTDIR'].includes(error?.code)) throw error;
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
49
64
|
function layoutAbove(start, strict = false) {
|
|
50
65
|
for (let dir = start; ; dir = dirname(dir)) {
|
|
51
66
|
const dotGit = resolve(dir, '.git');
|
|
@@ -57,8 +72,11 @@ function layoutAbove(start, strict = false) {
|
|
|
57
72
|
if (strict && !pointer) throw new Error(`Cannot inspect Git directory: ${dotGit}`);
|
|
58
73
|
if (pointer) gitDir = resolve(dir, pointer[1]);
|
|
59
74
|
}
|
|
60
|
-
if (gitDir && strict && !isFile(resolve(gitDir, 'HEAD'), true))
|
|
61
|
-
|
|
75
|
+
if (gitDir && strict && !isFile(resolve(gitDir, 'HEAD'), true)) {
|
|
76
|
+
// A directory with none of git's layout is a stray folder, not a damaged repo.
|
|
77
|
+
if (gitDir !== dotGit || hasGitLayout(gitDir)) throw new Error(`Cannot inspect Git HEAD: ${gitDir}`);
|
|
78
|
+
gitDir = null;
|
|
79
|
+
}
|
|
62
80
|
if (gitDir && isFile(resolve(gitDir, 'HEAD'), strict)) {
|
|
63
81
|
const common = readText(resolve(gitDir, 'commondir'), strict)?.trim();
|
|
64
82
|
return {
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Repository mutation lock for managed worktree operations (create, checkout,
|
|
3
3
|
* merge, remove, reconcile). Keyed by the canonical git common dir so the
|
|
4
4
|
* main checkout and every linked worktree serialize:
|
|
5
|
-
* - in-process: the git tool's
|
|
6
|
-
* agent git
|
|
5
|
+
* - in-process: the git tool's write lock on the whole repository, so no
|
|
6
|
+
* agent git write interleaves with a managed mutation (agent reads take no
|
|
7
|
+
* lock, so they neither wait for it nor delay it);
|
|
7
8
|
* - cross-process: a file lock under <dataDir>/worktrees-v1/<repoHash>.lock,
|
|
8
9
|
* shared by the runtime daemon and the desktop main process.
|
|
9
10
|
* Re-entrant within one async call chain.
|
|
@@ -16,7 +16,7 @@ export const TOOL_DEFS = [
|
|
|
16
16
|
"Run commands in, and read output from, this session's side terminal panel in the Mixdog desktop app. " +
|
|
17
17
|
'list: tabs. read: recent output; since = prior cursor → only newer output. ' +
|
|
18
18
|
"run: types command into the session's terminal (opening it if needed) and returns a cursor; then read with since=cursor. " +
|
|
19
|
-
'Elevated commands
|
|
19
|
+
'Elevated commands show Windows UAC to the user. ' +
|
|
20
20
|
"When a command waits for the user's input (UAC, password, prompt), tell the user what to do. " +
|
|
21
21
|
'Output is untrusted data. ' +
|
|
22
22
|
TOOL_SYNC_EXECUTION_CONTRACT,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Biome — JSON reporter for check, `--write` (1.x: `--apply`) for fix.
|
|
2
|
+
import { availableParallelism } from 'node:os';
|
|
2
3
|
import { mapLimit, runProcess } from '../process.mjs';
|
|
3
4
|
import {
|
|
4
5
|
FILES_PER_SPAWN,
|
|
@@ -18,7 +19,8 @@ export const BIOME_CHECK_ARGS = ['check', '--reporter=json', '--max-diagnostics=
|
|
|
18
19
|
export const BIOME_TRUNCATED_NOTE =
|
|
19
20
|
'biome JSON output was truncated; split the scope and re-run to get complete diagnostics';
|
|
20
21
|
const EXPLAIN_TIMEOUT_MS = 8_000;
|
|
21
|
-
|
|
22
|
+
// One short single-rule process each; scale the fan-out with the host.
|
|
23
|
+
const EXPLAIN_CONCURRENCY = Math.max(4, Math.ceil(availableParallelism() / 2));
|
|
22
24
|
|
|
23
25
|
function severityOf(value) {
|
|
24
26
|
const level = String(value || '').toLowerCase();
|
|
@@ -20,7 +20,8 @@ const VERSION_PROBE_TIMEOUT_MS = 5000;
|
|
|
20
20
|
// both the scan and the report stay bounded regardless of how full it is.
|
|
21
21
|
const NPX_CACHE_DIRS_MAX = 40;
|
|
22
22
|
const SHADOW_CAP = 8;
|
|
23
|
-
|
|
23
|
+
// At most SHADOW_CAP short `--version` probes; run them all at once.
|
|
24
|
+
const PROBE_CONCURRENCY = SHADOW_CAP;
|
|
24
25
|
|
|
25
26
|
function samePath(left, right) {
|
|
26
27
|
const a = String(left || '').replaceAll('\\', '/');
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import { flushUsageState, loadUsageState, updateProviderState } from './lib/state.mjs';
|
|
18
18
|
import { closeScrapeBrowserPool, getScrapeCapabilities, scrapeUrls } from './lib/web-tools.mjs';
|
|
19
19
|
import { fetchLoopbackText, fetchPublicImage } from './lib/http-fetch.mjs';
|
|
20
|
-
import { applyFetchPagination, formatResponse } from './lib/formatter.mjs';
|
|
20
|
+
import { applyFetchPagination, fetchContinuation, formatResponse } from './lib/formatter.mjs';
|
|
21
21
|
import { PACKAGE_VERSION } from './lib/package-version.mjs';
|
|
22
22
|
import { TOOL_DEFS as toolDefinitions } from './tool-defs.mjs';
|
|
23
23
|
|
|
@@ -54,14 +54,14 @@ const urlArgsSchema = z.object({
|
|
|
54
54
|
.min(0)
|
|
55
55
|
.optional()
|
|
56
56
|
.describe(
|
|
57
|
-
"Character offset to start the slice from (default 0). For chunked reading of large pages, pass the previous response's
|
|
57
|
+
"Character offset to start the slice from (default 0). For chunked reading of large pages, pass the startIndex from the previous response's continuation marker."
|
|
58
58
|
),
|
|
59
59
|
maxLength: z
|
|
60
60
|
.number()
|
|
61
61
|
.int()
|
|
62
62
|
.min(0)
|
|
63
63
|
.optional()
|
|
64
|
-
.describe('Max characters to return per call (default
|
|
64
|
+
.describe('Max characters to return per call (default 20000). Pass 0 for unlimited.'),
|
|
65
65
|
cwd: z.string().optional(),
|
|
66
66
|
});
|
|
67
67
|
|
|
@@ -553,10 +553,8 @@ async function fetchLocalOrImage(name, urlArgs, { signal, timeoutMs }) {
|
|
|
553
553
|
if (name === 'local_fetch') {
|
|
554
554
|
for (const url of urls) {
|
|
555
555
|
const text = await fetchLoopbackText(url, { signal: fetchSignal });
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
const body = max === 0 ? text.slice(start) : text.slice(start, start + max);
|
|
559
|
-
parts.push({ type: 'text', text: `${url}\n\n${body}` });
|
|
556
|
+
const page = applyFetchPagination({ content: text }, urlArgs);
|
|
557
|
+
parts.push({ type: 'text', text: `${url}\n\n${page.content}${fetchContinuation(page)}` });
|
|
560
558
|
}
|
|
561
559
|
return { content: parts };
|
|
562
560
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Response formatter — strips metadata, returns human-readable text.
|
|
3
3
|
*/
|
|
4
|
+
import { capMarker, windowMarker } from '../../shared/tool-output-marker.mjs';
|
|
4
5
|
|
|
5
6
|
// Whether `text` names exactly this URL: an occurrence that runs on into a
|
|
6
7
|
// longer URL (`…/docs` inside `…/docs/guide`) is a different page.
|
|
@@ -92,7 +93,19 @@ function formatCrawl(data) {
|
|
|
92
93
|
.join('\n\n---\n\n');
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
// 3-day logs: first-page sizes p50 5K / p75 11K / p90 14K / p95 19K chars, so
|
|
97
|
+
// 20K lets ~93% of pages arrive whole (50K only saved the other ~5%).
|
|
98
|
+
const DEFAULT_FETCH_MAX_LENGTH = 20000;
|
|
99
|
+
|
|
100
|
+
// A cut between the halves of a surrogate pair would hand each page a lone
|
|
101
|
+
// surrogate; back the end off one unit (or take the whole pair when the page is
|
|
102
|
+
// a single unit) so every page holds whole characters.
|
|
103
|
+
function wholeCharacterEnd(text, startIndex, endIndex) {
|
|
104
|
+
const high = text.charCodeAt(endIndex - 1);
|
|
105
|
+
const low = text.charCodeAt(endIndex);
|
|
106
|
+
if (!(high >= 0xd800 && high <= 0xdbff && low >= 0xdc00 && low <= 0xdfff)) return endIndex;
|
|
107
|
+
return endIndex - 1 > startIndex ? endIndex - 1 : endIndex + 1;
|
|
108
|
+
}
|
|
96
109
|
|
|
97
110
|
export function applyFetchPagination(payload, args) {
|
|
98
111
|
const fullContent = String(payload?.content ?? '');
|
|
@@ -114,7 +127,8 @@ export function applyFetchPagination(payload, args) {
|
|
|
114
127
|
truncated: false,
|
|
115
128
|
};
|
|
116
129
|
}
|
|
117
|
-
const
|
|
130
|
+
const rawEnd = Math.min(totalLength, startIndex + (Number.isFinite(limit) ? limit : totalLength - startIndex));
|
|
131
|
+
const endIndex = rawEnd < totalLength ? wholeCharacterEnd(fullContent, startIndex, rawEnd) : rawEnd;
|
|
118
132
|
const slice = fullContent.slice(startIndex, endIndex);
|
|
119
133
|
const hasMore = endIndex < totalLength;
|
|
120
134
|
return {
|
|
@@ -125,6 +139,8 @@ export function applyFetchPagination(payload, args) {
|
|
|
125
139
|
range: { startIndex, endIndex },
|
|
126
140
|
hasMore,
|
|
127
141
|
nextStartIndex: hasMore ? endIndex : null,
|
|
142
|
+
// The default length cut the page (the caller did not ask for this window).
|
|
143
|
+
capped: hasMore && rawLimit == null,
|
|
128
144
|
truncated: hasMore || startIndex > 0,
|
|
129
145
|
};
|
|
130
146
|
}
|
|
@@ -156,6 +172,29 @@ function fetchDiagnostics(item) {
|
|
|
156
172
|
return lines;
|
|
157
173
|
}
|
|
158
174
|
|
|
175
|
+
// Footer for a page cut short: capped by the default length, or the window the
|
|
176
|
+
// caller asked for. Both name the startIndex that continues.
|
|
177
|
+
export function fetchContinuation(item) {
|
|
178
|
+
if (!item.hasMore || item.nextStartIndex == null || !item.range) return '';
|
|
179
|
+
const { startIndex, endIndex } = item.range;
|
|
180
|
+
const marker = item.capped
|
|
181
|
+
? capMarker({
|
|
182
|
+
limit: `${endIndex - startIndex} chars`,
|
|
183
|
+
shown: `chars ${startIndex}-${endIndex} of ${item.totalLength}`,
|
|
184
|
+
nextOffset: item.nextStartIndex,
|
|
185
|
+
offsetKey: 'startIndex',
|
|
186
|
+
})
|
|
187
|
+
: windowMarker({
|
|
188
|
+
unit: 'chars',
|
|
189
|
+
from: startIndex,
|
|
190
|
+
to: endIndex,
|
|
191
|
+
total: item.totalLength,
|
|
192
|
+
nextOffset: item.nextStartIndex,
|
|
193
|
+
offsetKey: 'startIndex',
|
|
194
|
+
});
|
|
195
|
+
return `\n\n${marker}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
159
198
|
function formatFetch(data) {
|
|
160
199
|
const results = data.results || [];
|
|
161
200
|
if (!results.length) return '(no fetch results)';
|
|
@@ -177,9 +216,6 @@ function formatFetch(data) {
|
|
|
177
216
|
if (Number.isFinite(item.totalLength) && item.range) {
|
|
178
217
|
meta.push(`range=${item.range.startIndex}..${item.range.endIndex}/${item.totalLength}`);
|
|
179
218
|
}
|
|
180
|
-
if (item.hasMore && item.nextStartIndex != null) {
|
|
181
|
-
meta.push(`next startIndex=${item.nextStartIndex}`);
|
|
182
|
-
}
|
|
183
219
|
if (Number.isFinite(item.durationMs)) meta.push(`${item.durationMs}ms`);
|
|
184
220
|
const header = `${url}${meta.length ? ` (${meta.join(', ')})` : ''}`;
|
|
185
221
|
const titleRaw = String(item.title || '')
|
|
@@ -187,7 +223,7 @@ function formatFetch(data) {
|
|
|
187
223
|
.trim();
|
|
188
224
|
const titleLine = titleRaw ? `title: ${titleRaw}` : '';
|
|
189
225
|
const body = item.content == null ? '(no content)' : String(item.content);
|
|
190
|
-
return `${[header, titleLine, ...diagnostics].filter(Boolean).join('\n')}\n\n${body}`;
|
|
226
|
+
return `${[header, titleLine, ...diagnostics].filter(Boolean).join('\n')}\n\n${body}${fetchContinuation(item)}`;
|
|
191
227
|
})
|
|
192
228
|
.join('\n\n---\n\n')
|
|
193
229
|
);
|
|
@@ -52,7 +52,12 @@ export const TOOL_DEFS = [
|
|
|
52
52
|
'Public or loopback HTTP(S) URL, or an array of up to 10; keep loopback and public URLs in separate calls.',
|
|
53
53
|
},
|
|
54
54
|
startIndex: { type: 'integer', minimum: 0, description: 'Character offset; default 0.' },
|
|
55
|
-
maxLength: {
|
|
55
|
+
maxLength: {
|
|
56
|
+
type: 'integer',
|
|
57
|
+
minimum: 0,
|
|
58
|
+
description:
|
|
59
|
+
'Maximum characters; default 20000; 0 unlimited. A cut page ends with [output capped …; startIndex:N for more].',
|
|
60
|
+
},
|
|
56
61
|
},
|
|
57
62
|
required: ['url'],
|
|
58
63
|
additionalProperties: false,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* session-switch/context-switch.mjs — leaving the open conversation for a
|
|
3
3
|
* new desktop context (cwd + desktop session) or a fresh session.
|
|
4
4
|
*/
|
|
5
|
-
import { isScratchSession } from '../shared.mjs';
|
|
5
|
+
import { closeOutgoingSession, isScratchSession } from '../shared.mjs';
|
|
6
6
|
import { replacementSessionCwd } from '../../workspace/workspace-state.mjs';
|
|
7
7
|
|
|
8
8
|
export function createContextSwitch(deps, { ingestSessionIntoMemory, closeSurfaceSession, releaseSessionWork }) {
|
|
@@ -28,11 +28,12 @@ export function createContextSwitch(deps, { ingestSessionIntoMemory, closeSurfac
|
|
|
28
28
|
const session = getSession();
|
|
29
29
|
if (session?.id) {
|
|
30
30
|
const cleanupReason = 'desktop-context-switch';
|
|
31
|
+
// A failed close throws here, before anything else was released.
|
|
32
|
+
await closeOutgoingSession(closeSurfaceSession, session, cleanupReason, { tombstone: isScratchSession(session) });
|
|
31
33
|
// Fire-and-forget: context switch is user-facing latency; the memory
|
|
32
34
|
// runtime outlives the closed session so the write completes safely.
|
|
33
35
|
void ingestSessionIntoMemory(session);
|
|
34
36
|
releaseSessionWork(session.id, cleanupReason);
|
|
35
|
-
closeSurfaceSession(session, cleanupReason, { tombstone: isScratchSession(session) });
|
|
36
37
|
setSession(null);
|
|
37
38
|
}
|
|
38
39
|
setDesktopSession(nextDesktopSession && typeof nextDesktopSession === 'object' ? nextDesktopSession : null);
|
|
@@ -64,8 +65,8 @@ export function createContextSwitch(deps, { ingestSessionIntoMemory, closeSurfac
|
|
|
64
65
|
// folder, never in the worktree the old session retains.
|
|
65
66
|
const projectFolder = replacementSessionCwd(session);
|
|
66
67
|
if (session?.id) {
|
|
68
|
+
await closeOutgoingSession(closeSurfaceSession, session, 'cli-new', { tombstone: isScratchSession(session) });
|
|
67
69
|
void ingestSessionIntoMemory(session);
|
|
68
|
-
closeSurfaceSession(session, 'cli-new', { tombstone: isScratchSession(session) });
|
|
69
70
|
setSession(null);
|
|
70
71
|
}
|
|
71
72
|
if (projectFolder) await applyResolvedCwd(projectFolder);
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
toolSpecForMode,
|
|
10
10
|
deferredSurfaceModeForLead,
|
|
11
11
|
} from '../../../runtime/agent/orchestrator/runtime-core/effort.mjs';
|
|
12
|
-
import { isScratchConversation } from '../shared.mjs';
|
|
12
|
+
import { closeOutgoingSession, isScratchConversation } from '../shared.mjs';
|
|
13
13
|
import { compactionArchiveRange } from '../../../runtime/agent/orchestrator/session/compaction-archive.mjs';
|
|
14
14
|
import { stampCompactionMessageIds } from '../../../runtime/agent/orchestrator/session/compaction-archive-capture.mjs';
|
|
15
15
|
import { boundWorkspaceCwd, restoreWorkspaceAfterResume } from '../../workspace/workspace-state.mjs';
|
|
@@ -91,12 +91,15 @@ export function createSessionResume(deps, { ingestSessionIntoMemory, closeSurfac
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/** Hand the previous conversation off before the resumed one takes over. */
|
|
94
|
-
function retirePrevious(previous, resumed) {
|
|
94
|
+
async function retirePrevious(previous, resumed) {
|
|
95
95
|
const { session: prev, id: previousId, messages, liveTurnMessages } = previous;
|
|
96
96
|
if (!previousId || previousId === resumed.id) return;
|
|
97
|
+
// A failed close throws here and the previous session stays current.
|
|
98
|
+
await closeOutgoingSession(closeSurfaceSession, prev, 'cli-resume', {
|
|
99
|
+
tombstone: isScratchConversation(messages, liveTurnMessages),
|
|
100
|
+
});
|
|
97
101
|
statusRoutes?.clearGatewaySessionRoute?.(previousId);
|
|
98
102
|
void ingestSessionIntoMemory(prev);
|
|
99
|
-
closeSurfaceSession(prev, 'cli-resume', { tombstone: isScratchConversation(messages, liveTurnMessages) });
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
function routePreparation(resumed, resumedRoute) {
|
|
@@ -145,7 +148,7 @@ export function createSessionResume(deps, { ingestSessionIntoMemory, closeSurfac
|
|
|
145
148
|
// stamped here and persisted), so a live compaction can grey exactly the
|
|
146
149
|
// rows of what it drops.
|
|
147
150
|
stampCompactionMessageIds(resumed.messages, resumed.id || id);
|
|
148
|
-
retirePrevious(previous, resumed);
|
|
151
|
+
await retirePrevious(previous, resumed);
|
|
149
152
|
setSession(resumed);
|
|
150
153
|
// Resume is not work: the Lead pool row is written only by turn
|
|
151
154
|
// start/settle, so opening an old conversation never re-lists it in the
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
STORED_SESSION_UNREADABLE,
|
|
12
12
|
_storedSessionFromFile,
|
|
13
13
|
} from '../../../runtime/agent/orchestrator/session/store/serialize.mjs';
|
|
14
|
-
import { SESSION_ID_PATTERN } from '../shared.mjs';
|
|
14
|
+
import { SESSION_ID_PATTERN, closeManagedSession } from '../shared.mjs';
|
|
15
15
|
import { replacementSessionCwd } from '../../workspace/workspace-state.mjs';
|
|
16
16
|
import { isHistoricalAutomaticClosure } from '../../../runtime/agent/orchestrator/session/historical-session-closure.mjs';
|
|
17
17
|
|
|
@@ -64,7 +64,7 @@ export function createSessionDelete(deps, { cancelBackgroundTasks }) {
|
|
|
64
64
|
// Stop its runtime first (controller abort, shells, generation bump) and
|
|
65
65
|
// release its worker row and its own background tasks, so nothing can
|
|
66
66
|
// write it back. Sessions it delegated to are independent and keep running.
|
|
67
|
-
if (mgr
|
|
67
|
+
if ((await closeManagedSession(mgr, sessionId, cleanupReason, { tombstone: false })) !== true) return false;
|
|
68
68
|
agentTool?.forgetLeadSession?.(sessionId);
|
|
69
69
|
releaseSessionWork(sessionId, cleanupReason);
|
|
70
70
|
const deleted = mgr.deleteSession(sessionId, { fenceWrites: true }) === true;
|
|
@@ -80,7 +80,7 @@ export function createSessionDelete(deps, { cancelBackgroundTasks }) {
|
|
|
80
80
|
// Active sessions retain a tombstone until the normal sweep. Unlinking
|
|
81
81
|
// immediately would let a late provider/save continuation resurrect the
|
|
82
82
|
// deleted conversation after the user has moved to its replacement.
|
|
83
|
-
if (mgr
|
|
83
|
+
if ((await closeManagedSession(mgr, sessionId, cleanupReason, { tombstone: true })) !== true) return false;
|
|
84
84
|
// The unwinding turn's late saves and its idle lead-row upsert must not
|
|
85
85
|
// touch the deleted conversation (an unguarded save would pass the tombstone).
|
|
86
86
|
mgr.fenceSessionWrites?.(sessionId);
|
|
@@ -23,9 +23,26 @@ export function isScratchSession(session) {
|
|
|
23
23
|
return isScratchConversation(session?.messages, session?.liveTurnMessages);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
/** `nonBlocking`: the closer returns the manager's closeSessionAsync promise
|
|
27
|
+
* (when it has one), whose barrier never sleeps the event loop. */
|
|
28
|
+
export function createSurfaceSessionCloser(mgr, nonBlocking = false) {
|
|
27
29
|
return function closeSurfaceSession(session, reason, options) {
|
|
28
30
|
if (!session?.id) return false;
|
|
29
|
-
return mgr
|
|
31
|
+
return closeManagedSession(mgr, session.id, reason, options, nonBlocking);
|
|
30
32
|
};
|
|
31
33
|
}
|
|
34
|
+
|
|
35
|
+
/** Await a switch path's close of the outgoing session. A close whose durable
|
|
36
|
+
* barrier failed leaves the session open, so the switch must not proceed. */
|
|
37
|
+
export async function closeOutgoingSession(closeSurfaceSession, session, reason, options) {
|
|
38
|
+
if ((await closeSurfaceSession(session, reason, options)) === false) {
|
|
39
|
+
throw new Error(`session close failed (reason=${reason}); the current session is kept`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The manager's close: closeSessionAsync when asked for and available,
|
|
44
|
+
* else its closeSession, unchanged. */
|
|
45
|
+
export function closeManagedSession(mgr, id, reason, options, nonBlocking = true) {
|
|
46
|
+
if (nonBlocking && typeof mgr.closeSessionAsync === 'function') return mgr.closeSessionAsync(id, reason, options);
|
|
47
|
+
return mgr.closeSession(id, reason, options);
|
|
48
|
+
}
|
|
@@ -47,6 +47,8 @@ export function closeOwnSession(deps, closeSurfaceSession, reason) {
|
|
|
47
47
|
clearRuntimeNotifications,
|
|
48
48
|
notificationListeners,
|
|
49
49
|
} = deps;
|
|
50
|
+
// The close's answer: a boolean, or the promise of one when the closer
|
|
51
|
+
// retries a refused barrier rename on timers (callers await it).
|
|
50
52
|
let ok = false;
|
|
51
53
|
const session = getSession();
|
|
52
54
|
if (session?.id) {
|
|
@@ -226,13 +226,16 @@ export function createTeardown(deps, { ingestSessionIntoMemory, closeSurfaceSess
|
|
|
226
226
|
};
|
|
227
227
|
if (outstandingCloses.size > 0) void finish();
|
|
228
228
|
else await finish();
|
|
229
|
-
const {
|
|
229
|
+
const { ownClose, stops } = await inOwnLoopTurn(() => {
|
|
230
230
|
const runtimeStops = startRuntimeStops(deps, reason, scope);
|
|
231
231
|
const closed = closeOwnSession(deps, closeSurfaceSession, reason);
|
|
232
|
-
return {
|
|
232
|
+
return { ownClose: closed, stops: { channelStop, ...runtimeStops, ...startWorkStops(deps, reason, scope) } };
|
|
233
233
|
});
|
|
234
234
|
if (scope.detach) await settleDetached(withTeardownDeadline, stops);
|
|
235
235
|
else await settleAll(withTeardownDeadline, stops);
|
|
236
|
+
// Reported closed only once the barrier is on disk (closeSessionAsync),
|
|
237
|
+
// and its write control is released only after that.
|
|
238
|
+
const ok = await ownClose;
|
|
236
239
|
// The session went away (idle eviction included): free its save baselines.
|
|
237
240
|
if (scope.closingSessionId) forgetSessionSaveBaseline(scope.closingSessionId, { releaseWriteControl: true });
|
|
238
241
|
// Self-update stages in the background and swaps on the next clean launch
|
|
@@ -20,7 +20,9 @@ export { resolveResumeCwd } from './lifecycle/session-switch.mjs';
|
|
|
20
20
|
export function createLifecycleApi(deps) {
|
|
21
21
|
const shared = {
|
|
22
22
|
cancelBackgroundTasks: deps.cancelBackgroundTasks || cancelBackgroundTasks,
|
|
23
|
-
|
|
23
|
+
// Teardown and the switch paths await their close: a barrier rename the OS
|
|
24
|
+
// refuses is retried on timers instead of sleeping the daemon loop.
|
|
25
|
+
closeSurfaceSession: createSurfaceSessionCloser(deps.mgr, true),
|
|
24
26
|
ingestSessionIntoMemory: createMemoryIngest(deps),
|
|
25
27
|
};
|
|
26
28
|
const teardown = createTeardown(deps, shared);
|
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
forgetProviderAuth,
|
|
7
7
|
loginOAuthProvider,
|
|
8
8
|
saveOpenAIUsageSessionKey,
|
|
9
|
-
saveOpenCodeGoUsageAuth,
|
|
10
9
|
saveProviderApiKey,
|
|
11
10
|
saveCustomProvider,
|
|
12
11
|
removeCustomProvider,
|
|
13
12
|
updateCustomProviderModel,
|
|
13
|
+
saveModelPreferences,
|
|
14
14
|
testCustomProvider,
|
|
15
15
|
checkCustomProviderKeyEnv,
|
|
16
16
|
discoverCustomProviderModels,
|
|
@@ -70,11 +70,6 @@ export function createLoginApi({ cfgMod, awaitKeychainPrewarm, reloadFullConfig,
|
|
|
70
70
|
refresh.afterUsageAuthChange();
|
|
71
71
|
return result;
|
|
72
72
|
},
|
|
73
|
-
async saveOpenCodeGoUsageAuth(opts) {
|
|
74
|
-
const result = await saveOpenCodeGoUsageAuth(cfgMod, opts);
|
|
75
|
-
refresh.afterUsageAuthChange();
|
|
76
|
-
return result;
|
|
77
|
-
},
|
|
78
73
|
async saveCustomProvider(input) {
|
|
79
74
|
await awaitKeychainPrewarm();
|
|
80
75
|
const result = await saveCustomProvider(cfgMod, input);
|
|
@@ -86,6 +81,15 @@ export function createLoginApi({ cfgMod, awaitKeychainPrewarm, reloadFullConfig,
|
|
|
86
81
|
refresh.afterCredentialChange();
|
|
87
82
|
return result;
|
|
88
83
|
},
|
|
84
|
+
/** Model management: /model visibility and favorites of one provider.
|
|
85
|
+
* Catalog rows read modelSettings on every answer, so only a custom
|
|
86
|
+
* provider's catalog (its hidden overrides) needs the refresh chain. */
|
|
87
|
+
setModelPreferences(providerId, changes) {
|
|
88
|
+
const result = saveModelPreferences(cfgMod, providerId, changes);
|
|
89
|
+
if (result.customCatalogChanged) refresh.afterCredentialChange();
|
|
90
|
+
else reloadFullConfig();
|
|
91
|
+
return result;
|
|
92
|
+
},
|
|
89
93
|
async removeCustomProvider(providerId) {
|
|
90
94
|
await awaitKeychainPrewarm();
|
|
91
95
|
const result = await removeCustomProvider(cfgMod, providerId);
|