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
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
// prompts at every continuation boundary, plus — at the TERMINAL boundary only
|
|
4
4
|
// — the manager's pending-message entries, grouped into one steering item per
|
|
5
5
|
// mode and adopted by this turn (so their delivery/ack/release rides it).
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
_groupPendingMessageEntries,
|
|
8
|
+
drainPendingMessages,
|
|
9
|
+
pendingQueueHoldsOnlyNotifications,
|
|
10
|
+
releasePendingMessages,
|
|
11
|
+
} from './pending-messages.mjs';
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* @param {object} input
|
|
@@ -26,9 +31,12 @@ export function createAskSteeringDrain({ sessionId, turn, askOpts }) {
|
|
|
26
31
|
/* best-effort steering drain */
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
|
-
// Manager/pending-messages
|
|
30
|
-
//
|
|
31
|
-
//
|
|
34
|
+
// Manager/pending-messages prompts carry no priority/slash metadata, so
|
|
35
|
+
// they stay OUT of the mid-chain (post-tool-batch) drain — that would
|
|
36
|
+
// bypass the queued-command filters. A queue holding only task
|
|
37
|
+
// notifications (agent/shell completions, which also end a passive wait)
|
|
38
|
+
// is taken at every boundary, so a completion reaches the very next model
|
|
39
|
+
// request instead of waiting for the model to stop. At the TERMINAL boundary
|
|
32
40
|
// they are exactly pending input: an `agent type=send` queued while the
|
|
33
41
|
// terminal sample was in flight must be folded into THIS turn before any
|
|
34
42
|
// stop hook runs, instead of losing its slot to a synthetic continuation
|
|
@@ -36,7 +44,7 @@ export function createAskSteeringDrain({ sessionId, turn, askOpts }) {
|
|
|
36
44
|
// nothing. Entries consumed here join the turn's pending entries: their
|
|
37
45
|
// delivery/ack (and release on failure) rides this turn, and the
|
|
38
46
|
// post-loop drain can no longer see them.
|
|
39
|
-
if (drainOptions?.stage === 'terminal') {
|
|
47
|
+
if (drainOptions?.stage === 'terminal' || pendingQueueHoldsOnlyNotifications(sessionId)) {
|
|
40
48
|
const pendingNow = drainPendingMessages(sessionId);
|
|
41
49
|
if (pendingNow.length > 0) {
|
|
42
50
|
// One steering entry per mode group: the merged prompt (if any) and
|
|
@@ -103,12 +103,15 @@ function completionEntryFields(entry) {
|
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
/** True when this completion entry already reached the model (no log). */
|
|
107
|
+
export function completionIsDelivered(entry) {
|
|
107
108
|
if (!isCompletionNotificationEntry(entry)) return false;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
return isDeliveredCompletion({ executionId: completionExecutionId(entry), text: pendingMessageText(entry) });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function completionWasDelivered(entry, site) {
|
|
113
|
+
if (!completionIsDelivered(entry)) return false;
|
|
114
|
+
logDuplicateSkip(site, { executionId: completionExecutionId(entry), text: pendingMessageText(entry) });
|
|
112
115
|
return true;
|
|
113
116
|
}
|
|
114
117
|
|
|
@@ -166,7 +169,7 @@ function explicitModeField(entry) {
|
|
|
166
169
|
return entry?.mode === PENDING_MODE_TASK_NOTIFICATION ? { mode: PENDING_MODE_TASK_NOTIFICATION } : {};
|
|
167
170
|
}
|
|
168
171
|
|
|
169
|
-
|
|
172
|
+
function normalizePendingMessageEntry(entry) {
|
|
170
173
|
if (typeof entry === 'string') {
|
|
171
174
|
const text = entry.trim();
|
|
172
175
|
return text ? { content: text, text } : null;
|
|
@@ -211,7 +214,7 @@ export function normalizePendingMessageEntry(entry) {
|
|
|
211
214
|
return carryLifecycleToken(marker ? { ...out, ...identity, ...marker } : { ...out, ...identity }, entry);
|
|
212
215
|
}
|
|
213
216
|
|
|
214
|
-
|
|
217
|
+
function pendingMessageText(entry) {
|
|
215
218
|
const normalized = normalizePendingMessageEntry(entry);
|
|
216
219
|
return normalized ? String(normalized.text || promptContentText(normalized.content) || '').trim() : '';
|
|
217
220
|
}
|
|
@@ -29,6 +29,12 @@ import {
|
|
|
29
29
|
pruneEmptyPendingIdSet,
|
|
30
30
|
} from './pending-claim-ledger.mjs';
|
|
31
31
|
import { legacyPendingMessagesPath } from './pending-spool-path.mjs';
|
|
32
|
+
import {
|
|
33
|
+
TASK_WAIT_WAKE_NOTIFICATION,
|
|
34
|
+
TASK_WAIT_WAKE_USER,
|
|
35
|
+
interruptTaskWaitForSession,
|
|
36
|
+
registerTaskWaitSteeringCheck,
|
|
37
|
+
} from '../task-wait-control.mjs';
|
|
32
38
|
import { pruneCleanupConfirmedLedger } from './pending-delivered-ledger.mjs';
|
|
33
39
|
// The durable delivered-id ledger on the session record lives in
|
|
34
40
|
// pending-delivered-ledger.mjs; re-exported so prior importers stay unchanged.
|
|
@@ -51,6 +57,8 @@ import {
|
|
|
51
57
|
PENDING_MODE_TASK_NOTIFICATION,
|
|
52
58
|
_groupPendingMessageEntries,
|
|
53
59
|
_mergePendingMessageEntries,
|
|
60
|
+
completionExecutionId,
|
|
61
|
+
completionIsDelivered,
|
|
54
62
|
completionWasDelivered,
|
|
55
63
|
entryLifecycleToken,
|
|
56
64
|
isCompletionNotificationEntry,
|
|
@@ -447,9 +455,52 @@ export function enqueuePendingMessage(sessionId, message) {
|
|
|
447
455
|
}
|
|
448
456
|
q.push(entry);
|
|
449
457
|
const bufferedDepth = schedulePendingMessagePersist(sessionId, entry);
|
|
458
|
+
const wake = pendingEntryWake(entry);
|
|
459
|
+
if (wake) {
|
|
460
|
+
interruptTaskWaitForSession(
|
|
461
|
+
sessionId,
|
|
462
|
+
wake,
|
|
463
|
+
wake === TASK_WAIT_WAKE_NOTIFICATION ? completionExecutionId(entry) : null
|
|
464
|
+
);
|
|
465
|
+
}
|
|
450
466
|
return Math.max(q.length, bufferedDepth || 0);
|
|
451
467
|
}
|
|
452
468
|
|
|
469
|
+
// What one queued entry wakes a passive task wait or shell hold for, so it
|
|
470
|
+
// reaches the loop now: steering (a Lead message to its agent) as user input,
|
|
471
|
+
// and an undelivered task completion (agent/shell, any status). A plain
|
|
472
|
+
// channel notice (scheduled or event-rule inject) waits for the turn, as it
|
|
473
|
+
// does in the Lead's queue.
|
|
474
|
+
function pendingEntryWake(entry) {
|
|
475
|
+
if (pendingEntryMode(entry) === PENDING_MODE_PROMPT) return TASK_WAIT_WAKE_USER;
|
|
476
|
+
if (isCompletionNotificationEntry(entry) && !completionIsDelivered(entry)) return TASK_WAIT_WAKE_NOTIFICATION;
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Anything already queued when a wait or hold begins ends it at once, exactly
|
|
481
|
+
// like what arrives during it. A completion the agent already received is no
|
|
482
|
+
// longer deliverable and never wakes.
|
|
483
|
+
registerTaskWaitSteeringCheck((sessionId) => {
|
|
484
|
+
let wake = false;
|
|
485
|
+
for (const entry of _sessionPendingMessages.get(sessionId) || []) {
|
|
486
|
+
const found = pendingEntryWake(entry);
|
|
487
|
+
if (found === TASK_WAIT_WAKE_USER) return found;
|
|
488
|
+
wake = wake || found;
|
|
489
|
+
}
|
|
490
|
+
return wake;
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// True when this session's queue holds task notifications and nothing else:
|
|
494
|
+
// the mid-chain boundary may then take the whole queue without bypassing the
|
|
495
|
+
// prompt filters that keep queued prompts for the terminal boundary.
|
|
496
|
+
export function pendingQueueHoldsOnlyNotifications(sessionId) {
|
|
497
|
+
const queued = [
|
|
498
|
+
...(_sessionPendingMessages.get(sessionId) || []),
|
|
499
|
+
...(_hydratedPendingMessages.get(sessionId) || []),
|
|
500
|
+
];
|
|
501
|
+
return queued.length > 0 && queued.every((entry) => pendingEntryMode(entry) === PENDING_MODE_TASK_NOTIFICATION);
|
|
502
|
+
}
|
|
503
|
+
|
|
453
504
|
// Drain order: enqueue time, then source (hydrated < buffered < memory), then
|
|
454
505
|
// position within the source.
|
|
455
506
|
function byEnqueueOrder(a, b) {
|
|
@@ -468,19 +519,13 @@ export function drainPendingMessages(sessionId) {
|
|
|
468
519
|
// This path is deliberately memory-only: no file lock, stat, parse, or
|
|
469
520
|
// atomic rename can run in the agent-completion tick.
|
|
470
521
|
const buffered = takeBufferedPendingMessages(sessionId);
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
//
|
|
475
|
-
//
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
// them here dropped the notification entirely. On a genuine resume the
|
|
479
|
-
// in-memory queue is empty, so keeping it only ever delivers live entries.
|
|
480
|
-
// Genuine user/steering messages carry no marker and are kept in order in
|
|
481
|
-
// both paths.
|
|
482
|
-
// Drain-time belt: drop MARKED in-memory completion entries whose text hash
|
|
483
|
-
// was already delivered+ACKed (TUI execution-ui) this process — those would
|
|
522
|
+
// A completion that reached the spool and was never acknowledged was never
|
|
523
|
+
// delivered to the model (delivery acknowledges and removes it), so it
|
|
524
|
+
// survives a restart: the hydrated disk entries drain like live in-memory
|
|
525
|
+
// ones. Dropping them on resume lost a finished (or failed) task's only
|
|
526
|
+
// notification.
|
|
527
|
+
// Drain-time belt: drop MARKED completion entries whose text hash was
|
|
528
|
+
// already delivered+ACKed (TUI execution-ui) this process — those would
|
|
484
529
|
// double-inject next turn. Only marked completion entries are eligible;
|
|
485
530
|
// genuine user/steering entries carry no marker and are always kept.
|
|
486
531
|
const deliveredIds = new Set();
|
|
@@ -494,10 +539,11 @@ export function drainPendingMessages(sessionId) {
|
|
|
494
539
|
deliveredEntries.push(entry);
|
|
495
540
|
return false;
|
|
496
541
|
};
|
|
542
|
+
const hydratedKept = hydrated.filter(keepUndelivered);
|
|
497
543
|
const memoryKept = memory.filter(keepUndelivered);
|
|
498
544
|
const bufferedKept = buffered.filter(keepUndelivered);
|
|
499
545
|
const tagged = [
|
|
500
|
-
...
|
|
546
|
+
...hydratedKept.map((entry, index) => ({ entry, source: 0, index })),
|
|
501
547
|
...bufferedKept.map((entry, index) => ({ entry, source: 1, index })),
|
|
502
548
|
...memoryKept.map((entry, index) => ({ entry, source: 2, index })),
|
|
503
549
|
];
|
|
@@ -512,14 +558,8 @@ export function drainPendingMessages(sessionId) {
|
|
|
512
558
|
if (!prior || item.source > prior.source) byId.set(normalized.id, { ...item, entry: normalized });
|
|
513
559
|
}
|
|
514
560
|
const ordered = [...byId.values()].sort(byEnqueueOrder);
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
.map(({ entry }) => entry);
|
|
518
|
-
const acknowledged = [...deliveredEntries, ...dropped];
|
|
519
|
-
if (acknowledged.length > 0) acknowledgePendingMessages(sessionId, acknowledged);
|
|
520
|
-
const visible = modelVisiblePendingMessages(
|
|
521
|
-
ordered.filter(({ entry }) => !dropped.includes(entry)).map(({ entry }) => entry)
|
|
522
|
-
);
|
|
561
|
+
if (deliveredEntries.length > 0) acknowledgePendingMessages(sessionId, deliveredEntries);
|
|
562
|
+
const visible = modelVisiblePendingMessages(ordered.map(({ entry }) => entry));
|
|
523
563
|
// Epoch gate. Normalization above rebuilds every entry, but each copy
|
|
524
564
|
// inherits the IMMUTABLE token the entry was accepted under. An entry from
|
|
525
565
|
// an older epoch (a cross-process close/detach moved the generation while
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
// }
|
|
24
24
|
import { createAbortController } from '../../../../shared/abort-controller.mjs';
|
|
25
25
|
import { emitSessionApprovalWaitChange } from '../../../../shared/session-approval-wait.mjs';
|
|
26
|
+
import { emitSessionActivityChange } from '../../../../shared/session-activity-signal.mjs';
|
|
26
27
|
import { DEFAULT_ACTIVITY_HEARTBEAT_MS } from '../../stall-policy.mjs';
|
|
27
28
|
import { MODEL_THINKING_STAGE, awaitingModelResponse, lastProgressAt } from '../../../../shared/quiet-progress.mjs';
|
|
28
29
|
import { finishBrowserTurn } from '../../../../browser-bridge/client.mjs';
|
|
@@ -177,12 +178,14 @@ export function markSessionAskStart(id) {
|
|
|
177
178
|
entry.modelRequestStartedAt = null;
|
|
178
179
|
entry.lastProgressAt = now;
|
|
179
180
|
entry.updatedAt = now;
|
|
181
|
+
entry.taskWaits = 0;
|
|
182
|
+
emitSessionActivityChange(id);
|
|
180
183
|
}
|
|
181
184
|
/** The provider put the request on the wire; its response headers are
|
|
182
185
|
* pending. The turn stays `requesting` (every stage set and the watchdog
|
|
183
186
|
* clock are unchanged); the send time lets surfaces show "model thinking"
|
|
184
187
|
* until transport arrives. Not progress: lastProgressAt is untouched. */
|
|
185
|
-
|
|
188
|
+
function markSessionRequestSent(id) {
|
|
186
189
|
if (!id) return;
|
|
187
190
|
const entry = _runtimeState.get(id);
|
|
188
191
|
if (!entry || entry.closed || entry.controller?.signal?.aborted || entry.stage !== 'requesting') return;
|
|
@@ -321,6 +324,23 @@ export function beginSessionApprovalWait(id) {
|
|
|
321
324
|
emitSessionApprovalWaitChange(id, null, meta);
|
|
322
325
|
};
|
|
323
326
|
}
|
|
327
|
+
// A turn parked on a background task (`task wait`): published as
|
|
328
|
+
// `awaitingTask` so status surfaces show the session waiting, not working.
|
|
329
|
+
// Counted like approval waits; never creates an entry. Returns the idempotent
|
|
330
|
+
// release.
|
|
331
|
+
export function beginSessionTaskWait(id) {
|
|
332
|
+
const entry = id ? _runtimeState.get(id) : null;
|
|
333
|
+
if (!entry) return () => {};
|
|
334
|
+
entry.taskWaits = (entry.taskWaits || 0) + 1;
|
|
335
|
+
if (entry.taskWaits === 1) emitSessionActivityChange(id);
|
|
336
|
+
let released = false;
|
|
337
|
+
return () => {
|
|
338
|
+
if (released) return;
|
|
339
|
+
released = true;
|
|
340
|
+
entry.taskWaits = Math.max(0, (entry.taskWaits || 0) - 1);
|
|
341
|
+
if (entry.taskWaits === 0) emitSessionActivityChange(id);
|
|
342
|
+
};
|
|
343
|
+
}
|
|
324
344
|
// Parent AbortSignal listeners are dropped on askSession unwind (finally /
|
|
325
345
|
// terminal return) and on error/cancel/close — not in markSessionDone, which
|
|
326
346
|
// also runs between queued follow-up turns within one ask.
|
|
@@ -335,6 +355,8 @@ function _settleTerminalEntry(entry, stage, lastError) {
|
|
|
335
355
|
entry.doneAt = settledAt;
|
|
336
356
|
entry.lastProgressAt = settledAt;
|
|
337
357
|
entry.updatedAt = settledAt;
|
|
358
|
+
entry.taskWaits = 0;
|
|
359
|
+
emitSessionActivityChange(entry.session?.id || '');
|
|
338
360
|
}
|
|
339
361
|
export function markSessionDone(id, { empty = false } = {}) {
|
|
340
362
|
if (!id) return;
|
|
@@ -3,7 +3,16 @@
|
|
|
3
3
|
// when tombstone=false), aborts the in-flight controller, tears down
|
|
4
4
|
// runtime/bash/dedup/offload/pending state, and defers the runtime-map clear.
|
|
5
5
|
// abortSessionTurn cancels the current turn without tombstoning.
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
markSessionClosed,
|
|
8
|
+
markSessionClosedAsync,
|
|
9
|
+
markSessionClosedAttempt,
|
|
10
|
+
bumpSessionGeneration,
|
|
11
|
+
bumpSessionGenerationAttempt,
|
|
12
|
+
isLifecycleRetry,
|
|
13
|
+
getSessionLifecycleCommitError,
|
|
14
|
+
} from '../store.mjs';
|
|
15
|
+
import { sleep } from '../../../../shared/sleep.mjs';
|
|
7
16
|
import { clearReadDedupSession } from '../read-dedup.mjs';
|
|
8
17
|
import { SessionClosedError } from './session-errors.mjs';
|
|
9
18
|
import { _dropPendingMessageState } from './pending-messages.mjs';
|
|
@@ -36,44 +45,79 @@ import { forgetSideQuestion } from '../side-question-store.mjs';
|
|
|
36
45
|
* TOMBSTONE_MAX_AGE_MS (1h — vastly longer than the microsecond in-flight race).
|
|
37
46
|
*/
|
|
38
47
|
export function closeSession(id, reason = 'manual', opts = {}) {
|
|
39
|
-
// tombstone=false: detach runtime resources (heartbeat, bash shells,
|
|
40
|
-
// controller abort, runtime-map clear) WITHOUT planting the disk
|
|
41
|
-
// tombstone. Used for non-empty sessions on /resume-away, /new, and
|
|
42
|
-
// TUI exit: a tombstone would drop the session from the Resume list
|
|
43
|
-
// immediately and let sweepTombstones() hard-delete it even though it
|
|
44
|
-
// has real conversation content worth resuming. Only truly-empty
|
|
45
|
-
// scratch sessions should still tombstone.
|
|
46
|
-
const tombstone = opts.tombstone !== false;
|
|
47
48
|
if (!id) return false;
|
|
49
|
+
const tombstone = opts.tombstone !== false;
|
|
48
50
|
const entry = _getRuntimeEntry(id);
|
|
49
|
-
|
|
50
|
-
// iterations, the current partial response, and observed tool results into
|
|
51
|
-
// one canonical transcript. Stage that in-memory snapshot synchronously
|
|
52
|
-
// BEFORE the lifecycle generation changes; once generation is bumped, the
|
|
53
|
-
// normal cancellation cleanup save is intentionally rejected as stale. It
|
|
54
|
-
// is only staged: a refused barrier rolls it back so the turn keeps running
|
|
55
|
-
// and checkpointing as if no close had been attempted.
|
|
56
|
-
let closeSnapshot = null;
|
|
57
|
-
try {
|
|
58
|
-
closeSnapshot = entry?.prepareCloseSnapshot?.tentative?.(reason) ?? null;
|
|
59
|
-
} catch {
|
|
60
|
-
/* best-effort */
|
|
61
|
-
}
|
|
62
|
-
// 1. Tombstone first — this wins the race against saveSession().
|
|
63
|
-
// Skipped when tombstone=false: no closed:true marker is planted, so
|
|
64
|
-
// the session file stays intact and resumeSession() will accept it.
|
|
65
|
-
// We still bump the on-disk generation via bumpSessionGeneration() —
|
|
66
|
-
// that alone is what protects the session from a late save race: any
|
|
67
|
-
// saveSession() still in flight from this detached turn (e.g. the
|
|
68
|
-
// cancel-cleanup save below) carries the OLD generation as its
|
|
69
|
-
// expectedGeneration, so _shouldDrop()'s ownership-counter rule drops
|
|
70
|
-
// it once disk generation moves past that. Without this bump the late
|
|
71
|
-
// write could silently overwrite the session after the user resumes
|
|
72
|
-
// it back (BL: burned-session late-save clobber).
|
|
51
|
+
const closeSnapshot = stageCloseSnapshot(entry, reason);
|
|
73
52
|
const outcome = { absent: false };
|
|
74
53
|
const newGen = tombstone
|
|
75
54
|
? markSessionClosed(id, reason, { outcome })
|
|
76
55
|
: bumpSessionGeneration(id, reason, { outcome });
|
|
56
|
+
return finishClose({ id, reason, tombstone, entry, closeSnapshot, newGen, outcome });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* closeSession that never blocks the event loop. Each barrier attempt runs
|
|
61
|
+
* synchronously exactly as closeSession's barrier does (the first one before
|
|
62
|
+
* this function first yields), but a rename the OS refuses with retries left
|
|
63
|
+
* is waited out with a timer instead of sleepSync. Between attempts the staged
|
|
64
|
+
* close snapshot is rolled back, so the turn keeps running untouched, and it
|
|
65
|
+
* is re-staged from the then-current turn right before the next attempt: the
|
|
66
|
+
* barrier that lands always carries the latest transcript. The session is
|
|
67
|
+
* torn down — and reported closed — only after its barrier is on disk.
|
|
68
|
+
*/
|
|
69
|
+
export async function closeSessionAsync(id, reason = 'manual', opts = {}) {
|
|
70
|
+
if (!id) return false;
|
|
71
|
+
const tombstone = opts.tombstone !== false;
|
|
72
|
+
const entry = _getRuntimeEntry(id);
|
|
73
|
+
for (let attempt = 0; ; attempt++) {
|
|
74
|
+
const closeSnapshot = stageCloseSnapshot(entry, reason);
|
|
75
|
+
const outcome = { absent: false };
|
|
76
|
+
const newGen = tombstone
|
|
77
|
+
? markSessionClosedAttempt(id, reason, { outcome }, attempt)
|
|
78
|
+
: bumpSessionGenerationAttempt(id, reason, { outcome }, attempt);
|
|
79
|
+
if (!isLifecycleRetry(newGen)) {
|
|
80
|
+
return finishClose({ id, reason, tombstone, entry, closeSnapshot, newGen, outcome });
|
|
81
|
+
}
|
|
82
|
+
closeSnapshot?.rollback();
|
|
83
|
+
await sleep(newGen.retryInMs);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// askSession owns the interruption tracker that can merge committed
|
|
88
|
+
// iterations, the current partial response, and observed tool results into
|
|
89
|
+
// one canonical transcript. Stage that in-memory snapshot synchronously
|
|
90
|
+
// BEFORE the lifecycle generation changes; once generation is bumped, the
|
|
91
|
+
// normal cancellation cleanup save is intentionally rejected as stale. It
|
|
92
|
+
// is only staged: a refused barrier rolls it back so the turn keeps running
|
|
93
|
+
// and checkpointing as if no close had been attempted.
|
|
94
|
+
function stageCloseSnapshot(entry, reason) {
|
|
95
|
+
try {
|
|
96
|
+
return entry?.prepareCloseSnapshot?.tentative?.(reason) ?? null;
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// tombstone=false: detach runtime resources (heartbeat, bash shells,
|
|
103
|
+
// controller abort, runtime-map clear) WITHOUT planting the disk
|
|
104
|
+
// tombstone. Used for non-empty sessions on /resume-away, /new, and
|
|
105
|
+
// TUI exit: a tombstone would drop the session from the Resume list
|
|
106
|
+
// immediately and let sweepTombstones() hard-delete it even though it
|
|
107
|
+
// has real conversation content worth resuming. Only truly-empty
|
|
108
|
+
// scratch sessions should still tombstone.
|
|
109
|
+
//
|
|
110
|
+
// The barrier (`newGen`) came first — it wins the race against saveSession().
|
|
111
|
+
// tombstone=false plants no closed:true marker, so the session file stays
|
|
112
|
+
// intact and resumeSession() will accept it, but the on-disk generation is
|
|
113
|
+
// still bumped (bumpSessionGeneration): that alone protects the session from
|
|
114
|
+
// a late save race. Any saveSession() still in flight from this detached turn
|
|
115
|
+
// (e.g. the cancel-cleanup save below) carries the OLD generation as its
|
|
116
|
+
// expectedGeneration, so _shouldDrop()'s ownership-counter rule drops it once
|
|
117
|
+
// disk generation moves past that. Without this bump the late write could
|
|
118
|
+
// silently overwrite the session after the user resumes it back (BL:
|
|
119
|
+
// burned-session late-save clobber).
|
|
120
|
+
function finishClose({ id, reason, tombstone, entry, closeSnapshot, newGen, outcome }) {
|
|
77
121
|
// Durable lifecycle barrier check. ANY nonnumeric result means the barrier
|
|
78
122
|
// is not on disk — a write failure, a liveness/ownership veto, a contended
|
|
79
123
|
// commit lock, or a missing record. Only a failed write publishes a
|
|
@@ -154,7 +198,10 @@ function reportCloseBarrierFailure({ id, reason, tombstone, entry }) {
|
|
|
154
198
|
*/
|
|
155
199
|
function cancelOwnedShellProcesses({ id, reason, tombstone }) {
|
|
156
200
|
const record = (result) => {
|
|
157
|
-
|
|
201
|
+
// Off the event loop's critical path: a refused rename retries on timers.
|
|
202
|
+
if (tombstone && result?.confirmed === false) {
|
|
203
|
+
markSessionClosedAsync(id, reason, { cancelStatus: 'cancel-unconfirmed' }).catch(() => {});
|
|
204
|
+
}
|
|
158
205
|
};
|
|
159
206
|
// A cancel that threw proved nothing either.
|
|
160
207
|
const unconfirmed = () => record({ confirmed: false });
|
|
@@ -122,6 +122,6 @@ export {
|
|
|
122
122
|
export { deleteSession, listOwnedAgentSessionIds, fenceSessionWrites } from './store.mjs';
|
|
123
123
|
// Read-only parsed-session access (desktop pane peek): no resume, no
|
|
124
124
|
// ownership, no liveness side effects.
|
|
125
|
-
export { loadSession } from './store.mjs';
|
|
126
|
-
export { closeSession, abortSessionTurn, unloadSessionRuntime } from './manager/session-close.mjs';
|
|
125
|
+
export { loadSession, loadSessionStatus } from './store.mjs';
|
|
126
|
+
export { closeSession, closeSessionAsync, abortSessionTurn, unloadSessionRuntime } from './manager/session-close.mjs';
|
|
127
127
|
export { sweepTombstones, startIdleCleanup, stopIdleCleanup } from './manager/idle-cleanup.mjs';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// request-fork.mjs — a cache-sharing fork of a provider request the main loop
|
|
2
|
+
// already sent (side-question-store.mjs): its messages unchanged, then the
|
|
3
|
+
// reply that request produced and one appended user turn. Reusing the saved
|
|
4
|
+
// send options keeps the model, system prompt, tools and cache-relevant
|
|
5
|
+
// options identical, so the provider's prompt cache serves the prefix.
|
|
6
|
+
import { collectAssistantToolCallIds } from './context-tool-pairs.mjs';
|
|
7
|
+
import { buildIntermediateAssistantMessage, buildToolCallAssistantMessage } from './loop/assistant-commit.mjs';
|
|
8
|
+
|
|
9
|
+
// The assistant turn the saved request produced, built the way the loop
|
|
10
|
+
// commits it (without the turn's one-shot transcript metadata), plus a stub
|
|
11
|
+
// result per tool call: the fork only needs each call answered so the
|
|
12
|
+
// request stays well-formed.
|
|
13
|
+
function replyMessages(response, toolStub) {
|
|
14
|
+
if (!response) return [];
|
|
15
|
+
const reply = response.toolCalls?.length
|
|
16
|
+
? buildToolCallAssistantMessage(response, { calls: response.toolCalls, opts: {} })
|
|
17
|
+
: buildIntermediateAssistantMessage(response, {});
|
|
18
|
+
if (!reply) return [];
|
|
19
|
+
const stubs = collectAssistantToolCallIds(reply).map((id) => ({ role: 'tool', toolCallId: id, content: toolStub }));
|
|
20
|
+
return [reply, ...stubs];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The fork of a saved request with `prompt` as its final user turn. The
|
|
25
|
+
* provider continuation state follows the reply when it is appended, exactly
|
|
26
|
+
* as the main loop's next request would carry it. `sharedCachePrefixCount`
|
|
27
|
+
* (the shared prefix's message count, which survives model-proxy
|
|
28
|
+
* serialization) keeps an Anthropic message breakpoint on the shared prefix,
|
|
29
|
+
* so the fork reads the saved request's cache entry and writes none for its
|
|
30
|
+
* suffix.
|
|
31
|
+
*/
|
|
32
|
+
export function buildForkedRequest(request, prompt, toolStub) {
|
|
33
|
+
const reply = replyMessages(request.response, toolStub);
|
|
34
|
+
const opts = { ...request.opts };
|
|
35
|
+
if (reply.length && Object.hasOwn(request.response, 'providerState')) {
|
|
36
|
+
opts.providerState = request.response.providerState;
|
|
37
|
+
}
|
|
38
|
+
if (request.messages.length) opts.sharedCachePrefixCount = request.messages.length;
|
|
39
|
+
const messages = [
|
|
40
|
+
...request.messages,
|
|
41
|
+
...reply,
|
|
42
|
+
// A user interjection keeps its own turn (never folded into a preceding
|
|
43
|
+
// tool result), so the prefix's last block reaches the provider unchanged.
|
|
44
|
+
{ role: 'user', content: prompt, meta: { source: 'steering' } },
|
|
45
|
+
];
|
|
46
|
+
return { messages, opts };
|
|
47
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// side-question-store.mjs — the in-memory state behind /btw side questions:
|
|
2
2
|
// per session, the latest provider request the main loop sent (the request a
|
|
3
|
-
// side question forks) and the side question in flight. It holds live
|
|
3
|
+
// side question or a compaction summary forks) and the side question in flight. It holds live
|
|
4
4
|
// provider/tool objects, so it never reaches the session record or disk.
|
|
5
5
|
import { isAgentOwner } from '../agent-owner.mjs';
|
|
6
6
|
|
|
@@ -65,6 +65,21 @@ export function sideQuestionRequest(sessionId) {
|
|
|
65
65
|
return (sessionId && requests.get(sessionId)) || null;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/** The saved request a compaction summary may fork: one whose reply settled
|
|
69
|
+
* and whose transcript no compaction has rewritten since it was sent. */
|
|
70
|
+
export function compactionForkRequest(sessionId) {
|
|
71
|
+
const request = sideQuestionRequest(sessionId);
|
|
72
|
+
return request?.response && !request.compacted ? request : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** A compaction rewrote the transcript: the saved request no longer holds the
|
|
76
|
+
* history a later compaction must summarize. A side question still forks it
|
|
77
|
+
* until the next request replaces it. */
|
|
78
|
+
export function retireCompactionFork(sessionId) {
|
|
79
|
+
const request = sideQuestionRequest(sessionId);
|
|
80
|
+
if (request) request.compacted = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
68
83
|
/** Start the session's side question, aborting the one still in flight. */
|
|
69
84
|
export function beginSideQuestion(sessionId) {
|
|
70
85
|
cancelSideQuestion(sessionId);
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
// the provider's prompt cache serves the prefix. Only the reply that request
|
|
6
6
|
// produced and one question are appended. One provider call, no tool loop;
|
|
7
7
|
// the answer is returned to the caller and never enters the transcript.
|
|
8
|
-
import {
|
|
9
|
-
import { buildIntermediateAssistantMessage, buildToolCallAssistantMessage } from './loop/assistant-commit.mjs';
|
|
8
|
+
import { buildForkedRequest } from './request-fork.mjs';
|
|
10
9
|
import { logSessionLlmUsage } from './manager/ask-turn-commit.mjs';
|
|
11
10
|
import { disposeTransportScope, forkTransportScope } from '../providers/transport-scope.mjs';
|
|
12
11
|
import { isAgentOwner } from '../agent-owner.mjs';
|
|
@@ -48,47 +47,10 @@ function sideQuestionPrompt(question) {
|
|
|
48
47
|
].join('\n');
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (!response) return [];
|
|
56
|
-
const reply = response.toolCalls?.length
|
|
57
|
-
? buildToolCallAssistantMessage(response, { calls: response.toolCalls, opts: {} })
|
|
58
|
-
: buildIntermediateAssistantMessage(response, {});
|
|
59
|
-
if (!reply) return [];
|
|
60
|
-
const stubs = collectAssistantToolCallIds(reply).map((id) => ({
|
|
61
|
-
role: 'tool',
|
|
62
|
-
toolCallId: id,
|
|
63
|
-
content: SIDE_QUESTION_TOOL_STUB,
|
|
64
|
-
}));
|
|
65
|
-
return [reply, ...stubs];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* The fork of a saved request: its messages unchanged, then the reply and
|
|
70
|
-
* the question. The send options are the saved ones; the provider
|
|
71
|
-
* continuation state follows the reply when it is appended, exactly as the
|
|
72
|
-
* main loop's next request would carry it. `sharedCachePrefixCount` (the
|
|
73
|
-
* shared prefix's message count, which survives model-proxy serialization)
|
|
74
|
-
* keeps an Anthropic message breakpoint on the shared prefix, so the fork reads the
|
|
75
|
-
* saved request's cache entry and writes none for its suffix.
|
|
76
|
-
*/
|
|
77
|
-
export function buildSideQuestionRequest(request, question) {
|
|
78
|
-
const reply = replyMessages(request.response);
|
|
79
|
-
const opts = { ...request.opts };
|
|
80
|
-
if (reply.length && Object.hasOwn(request.response, 'providerState')) {
|
|
81
|
-
opts.providerState = request.response.providerState;
|
|
82
|
-
}
|
|
83
|
-
if (request.messages.length) opts.sharedCachePrefixCount = request.messages.length;
|
|
84
|
-
const messages = [
|
|
85
|
-
...request.messages,
|
|
86
|
-
...reply,
|
|
87
|
-
// A user interjection keeps its own turn (never folded into a preceding
|
|
88
|
-
// tool result), so the prefix's last block reaches the provider unchanged.
|
|
89
|
-
{ role: 'user', content: sideQuestionPrompt(question), meta: { source: 'steering' } },
|
|
90
|
-
];
|
|
91
|
-
return { messages, opts };
|
|
50
|
+
/** The fork of a saved request: its messages unchanged, then the reply and
|
|
51
|
+
* the question (request-fork.mjs). */
|
|
52
|
+
function buildSideQuestionRequest(request, question) {
|
|
53
|
+
return buildForkedRequest(request, sideQuestionPrompt(question), SIDE_QUESTION_TOOL_STUB);
|
|
92
54
|
}
|
|
93
55
|
|
|
94
56
|
/**
|
|
@@ -63,3 +63,13 @@ export function readTextFile(path) {
|
|
|
63
63
|
return { state, text: '', code };
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
/** `readTextFile` with the raw bytes (`bytes`, a Buffer) instead of text. */
|
|
68
|
+
export function readBytesFile(path) {
|
|
69
|
+
try {
|
|
70
|
+
return { state: PROBE_PRESENT, bytes: readFileSync(path), code: null };
|
|
71
|
+
} catch (err) {
|
|
72
|
+
const { state, code } = _failureVerdict(err);
|
|
73
|
+
return { state, bytes: Buffer.alloc(0), code };
|
|
74
|
+
}
|
|
75
|
+
}
|