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,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict session-record read that decodes only what its caller needs.
|
|
3
|
+
*
|
|
4
|
+
* `readTopLevelLifecycleRecord` (lifecycle-scan.mjs) is the authority: a full
|
|
5
|
+
* `JSON.parse`, then a top-level duplicate-key walk. On a transcript that
|
|
6
|
+
* materializes every message object — about 2.5 ms per MB, most of it for
|
|
7
|
+
* tool output nobody reads. This reader reaches the SAME verdict and the same
|
|
8
|
+
* values while decoding only:
|
|
9
|
+
* - top-level values whose key is not in `skip`;
|
|
10
|
+
* - of `messages` (when it is an array): each element whose `role` is in
|
|
11
|
+
* `fullMessageRoles` in full; every other object element as `{ role }`
|
|
12
|
+
* (or `{}` without one); non-object elements in full.
|
|
13
|
+
* Everything else is validated, never decoded.
|
|
14
|
+
*
|
|
15
|
+
* The fast path accepts a document only when it has PROVEN it valid:
|
|
16
|
+
* - no raw byte below 0x20 anywhere (one native regex test; JSON forbids
|
|
17
|
+
* them inside strings, so a document with whitespace other than spaces,
|
|
18
|
+
* i.e. a pretty-printed one, simply takes the authoritative path);
|
|
19
|
+
* - no invalid escape anywhere (one native regex test; a backslash outside
|
|
20
|
+
* a string fails the grammar walk below);
|
|
21
|
+
* - the complete grammar, walked over every byte outside strings: values,
|
|
22
|
+
* separators, exact number and literal spellings, nesting, and nothing
|
|
23
|
+
* but whitespace after the root object;
|
|
24
|
+
* - a root object without any repeated top-level key.
|
|
25
|
+
* Anything it cannot prove — malformed, ambiguous, or merely unusual — is
|
|
26
|
+
* handed to `readTopLevelLifecycleRecord` itself, so every rejection is the
|
|
27
|
+
* authority's own. Decoded values are `JSON.parse` of their exact UTF-8
|
|
28
|
+
* slices, which equal the full parse's sub-values (slices start and end at
|
|
29
|
+
* ASCII delimiters, so UTF-8 decoding agrees too).
|
|
30
|
+
*/
|
|
31
|
+
import { lifecycleRecordOf, readTopLevelLifecycleRecord } from './lifecycle-scan.mjs';
|
|
32
|
+
|
|
33
|
+
const QUOTE = 0x22;
|
|
34
|
+
const BACKSLASH = 0x5c;
|
|
35
|
+
const SPACE = 0x20;
|
|
36
|
+
const COMMA = 0x2c;
|
|
37
|
+
const COLON = 0x3a;
|
|
38
|
+
const OPEN_OBJECT = 0x7b;
|
|
39
|
+
const CLOSE_OBJECT = 0x7d;
|
|
40
|
+
const OPEN_ARRAY = 0x5b;
|
|
41
|
+
const CLOSE_ARRAY = 0x5d;
|
|
42
|
+
const MINUS = 0x2d;
|
|
43
|
+
const PLUS = 0x2b;
|
|
44
|
+
const DOT = 0x2e;
|
|
45
|
+
const ZERO = 0x30;
|
|
46
|
+
const NINE = 0x39;
|
|
47
|
+
const EMPTY = new Set();
|
|
48
|
+
|
|
49
|
+
// String-content faults, one native pass each (an alternation of the two is
|
|
50
|
+
// slower than both). Any raw control byte: inside a string JSON.parse rejects
|
|
51
|
+
// it; outside one only \t \n \r are whitespace, and those documents take the
|
|
52
|
+
// full path.
|
|
53
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: control bytes are exactly what this detects.
|
|
54
|
+
const CONTROL_BYTE = /[\x00-\x1f]/;
|
|
55
|
+
// An escape that is not one of \" \\ \/ \b \f \n \r \t \uXXXX. The run before
|
|
56
|
+
// it starts after a non-backslash, so escapes pair from the run's start.
|
|
57
|
+
const INVALID_ESCAPE = /(?<!\\)(?:\\\\)*\\(?:[^"\\/bfnrtu]|u(?![0-9a-fA-F]{4}))/;
|
|
58
|
+
|
|
59
|
+
const isDigit = (code) => code >= ZERO && code <= NINE;
|
|
60
|
+
|
|
61
|
+
// Only spaces can remain: CONTROL_BYTE already sent every \t \n \r away.
|
|
62
|
+
function skipSpaces(text, at) {
|
|
63
|
+
let i = at;
|
|
64
|
+
while (text.charCodeAt(i) === SPACE) i++;
|
|
65
|
+
return i;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Index after the string opening at `at`, or -1. */
|
|
69
|
+
function stringEnd(text, at) {
|
|
70
|
+
let quote = at;
|
|
71
|
+
for (;;) {
|
|
72
|
+
quote = text.indexOf('"', quote + 1);
|
|
73
|
+
if (quote === -1) return -1;
|
|
74
|
+
let run = 0;
|
|
75
|
+
for (let k = quote - 1; text.charCodeAt(k) === BACKSLASH; k--) run++;
|
|
76
|
+
if ((run & 1) === 0) return quote + 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Index after the JSON number at `at`, or -1. */
|
|
81
|
+
function numberEnd(text, at) {
|
|
82
|
+
let i = at;
|
|
83
|
+
if (text.charCodeAt(i) === MINUS) i++;
|
|
84
|
+
if (text.charCodeAt(i) === ZERO) i++;
|
|
85
|
+
else if (isDigit(text.charCodeAt(i))) {
|
|
86
|
+
while (isDigit(text.charCodeAt(i))) i++;
|
|
87
|
+
} else return -1;
|
|
88
|
+
if (text.charCodeAt(i) === DOT) {
|
|
89
|
+
i++;
|
|
90
|
+
if (!isDigit(text.charCodeAt(i))) return -1;
|
|
91
|
+
while (isDigit(text.charCodeAt(i))) i++;
|
|
92
|
+
}
|
|
93
|
+
const exponent = text.charCodeAt(i);
|
|
94
|
+
if (exponent === 0x65 || exponent === 0x45) {
|
|
95
|
+
i++;
|
|
96
|
+
if (text.charCodeAt(i) === PLUS || text.charCodeAt(i) === MINUS) i++;
|
|
97
|
+
if (!isDigit(text.charCodeAt(i))) return -1;
|
|
98
|
+
while (isDigit(text.charCodeAt(i))) i++;
|
|
99
|
+
}
|
|
100
|
+
return i;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Index after `"key" :` starting at `at`, or -1. */
|
|
104
|
+
function memberValueStart(text, at) {
|
|
105
|
+
if (text.charCodeAt(at) !== QUOTE) return -1;
|
|
106
|
+
const end = stringEnd(text, at);
|
|
107
|
+
if (end === -1) return -1;
|
|
108
|
+
const colon = skipSpaces(text, end);
|
|
109
|
+
return text.charCodeAt(colon) === COLON ? colon + 1 : -1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Index after one valid JSON value starting at or after `at`, or -1.
|
|
113
|
+
* Iterative, so nesting depth costs no stack. */
|
|
114
|
+
function valueEnd(text, at) {
|
|
115
|
+
const closers = [];
|
|
116
|
+
let i = at;
|
|
117
|
+
for (;;) {
|
|
118
|
+
i = skipSpaces(text, i);
|
|
119
|
+
const code = text.charCodeAt(i);
|
|
120
|
+
if (code === QUOTE) i = stringEnd(text, i);
|
|
121
|
+
else if (code === OPEN_OBJECT || code === OPEN_ARRAY) {
|
|
122
|
+
const closer = code === OPEN_OBJECT ? CLOSE_OBJECT : CLOSE_ARRAY;
|
|
123
|
+
i = skipSpaces(text, i + 1);
|
|
124
|
+
if (text.charCodeAt(i) === closer) i++;
|
|
125
|
+
else {
|
|
126
|
+
closers.push(closer);
|
|
127
|
+
if (closer === CLOSE_OBJECT) {
|
|
128
|
+
i = memberValueStart(text, i);
|
|
129
|
+
if (i === -1) return -1;
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
} else if (code === 0x74) i = text.startsWith('true', i) ? i + 4 : -1;
|
|
134
|
+
else if (code === 0x66) i = text.startsWith('false', i) ? i + 5 : -1;
|
|
135
|
+
else if (code === 0x6e) i = text.startsWith('null', i) ? i + 4 : -1;
|
|
136
|
+
else i = numberEnd(text, i);
|
|
137
|
+
if (i === -1) return -1;
|
|
138
|
+
// After a value: close containers or move to the next member/element.
|
|
139
|
+
for (;;) {
|
|
140
|
+
if (closers.length === 0) return i;
|
|
141
|
+
i = skipSpaces(text, i);
|
|
142
|
+
const next = text.charCodeAt(i);
|
|
143
|
+
const closer = closers[closers.length - 1];
|
|
144
|
+
if (next === closer) {
|
|
145
|
+
closers.pop();
|
|
146
|
+
i++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (next !== COMMA) return -1;
|
|
150
|
+
i = skipSpaces(text, i + 1);
|
|
151
|
+
if (closer === CLOSE_OBJECT) {
|
|
152
|
+
i = memberValueStart(text, i);
|
|
153
|
+
if (i === -1) return -1;
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const decodeSlice = (bytes, start, end) => JSON.parse(bytes.toString('utf8', start, end));
|
|
161
|
+
|
|
162
|
+
/** The key of the string token [start, end): its own bytes unless escaped. */
|
|
163
|
+
function decodeKey(bytes, text, start, end) {
|
|
164
|
+
for (let k = start + 1; k < end - 1; k++) {
|
|
165
|
+
if (text.charCodeAt(k) === BACKSLASH) return decodeSlice(bytes, start, end);
|
|
166
|
+
}
|
|
167
|
+
return bytes.toString('utf8', start + 1, end - 1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function defineMember(target, key, value) {
|
|
171
|
+
// JSON.parse creates own data properties, `__proto__` included.
|
|
172
|
+
Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// "role" spelled with escapes is at most 6 escapes of 6 chars plus quotes.
|
|
176
|
+
const MAX_ESCAPED_ROLE_KEY = 26;
|
|
177
|
+
|
|
178
|
+
/** One `messages` element [start, ...): `{ end, item }`, or null. */
|
|
179
|
+
function messageElement(bytes, text, start, fullRoles) {
|
|
180
|
+
if (text.charCodeAt(start) !== OPEN_OBJECT) {
|
|
181
|
+
const end = valueEnd(text, start);
|
|
182
|
+
return end === -1 ? null : { end, item: decodeSlice(bytes, start, end) };
|
|
183
|
+
}
|
|
184
|
+
let i = skipSpaces(text, start + 1);
|
|
185
|
+
let role;
|
|
186
|
+
let hasRole = false;
|
|
187
|
+
if (text.charCodeAt(i) === CLOSE_OBJECT) return { end: i + 1, item: {} };
|
|
188
|
+
for (;;) {
|
|
189
|
+
if (text.charCodeAt(i) !== QUOTE) return null;
|
|
190
|
+
const keyEnd = stringEnd(text, i);
|
|
191
|
+
if (keyEnd === -1) return null;
|
|
192
|
+
const isRole =
|
|
193
|
+
(keyEnd - i === 6 && text.startsWith('"role"', i)) ||
|
|
194
|
+
(keyEnd - i <= MAX_ESCAPED_ROLE_KEY && keyEnd - i > 6 && decodeKey(bytes, text, i, keyEnd) === 'role');
|
|
195
|
+
const colon = skipSpaces(text, keyEnd);
|
|
196
|
+
if (text.charCodeAt(colon) !== COLON) return null;
|
|
197
|
+
const valueStart = skipSpaces(text, colon + 1);
|
|
198
|
+
const end = valueEnd(text, valueStart);
|
|
199
|
+
if (end === -1) return null;
|
|
200
|
+
if (isRole) {
|
|
201
|
+
// Duplicate members resolve last-wins, as in the full parse.
|
|
202
|
+
role = decodeSlice(bytes, valueStart, end);
|
|
203
|
+
hasRole = true;
|
|
204
|
+
}
|
|
205
|
+
i = skipSpaces(text, end);
|
|
206
|
+
if (text.charCodeAt(i) === COMMA) {
|
|
207
|
+
i = skipSpaces(text, i + 1);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (text.charCodeAt(i) !== CLOSE_OBJECT) return null;
|
|
211
|
+
const elementEnd = i + 1;
|
|
212
|
+
let item = {};
|
|
213
|
+
if (hasRole && fullRoles.has(role)) item = decodeSlice(bytes, start, elementEnd);
|
|
214
|
+
else if (hasRole) defineMember(item, 'role', role);
|
|
215
|
+
return { end: elementEnd, item };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** The `messages` array [start, ...): `{ end, messages }`, or null. */
|
|
220
|
+
function messagesValue(bytes, text, start, fullRoles) {
|
|
221
|
+
const messages = [];
|
|
222
|
+
let i = skipSpaces(text, start + 1);
|
|
223
|
+
if (text.charCodeAt(i) === CLOSE_ARRAY) return { end: i + 1, messages };
|
|
224
|
+
for (;;) {
|
|
225
|
+
const element = messageElement(bytes, text, i, fullRoles);
|
|
226
|
+
if (!element) return null;
|
|
227
|
+
messages.push(element.item);
|
|
228
|
+
i = skipSpaces(text, element.end);
|
|
229
|
+
if (text.charCodeAt(i) === COMMA) {
|
|
230
|
+
i = skipSpaces(text, i + 1);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
return text.charCodeAt(i) === CLOSE_ARRAY ? { end: i + 1, messages } : null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The proven-valid, duplicate-free root object of `bytes`, or null when
|
|
239
|
+
* validity could not be proven (or the document is not such an object). Of
|
|
240
|
+
* it only the members `decode(key)` accepts are decoded — `messages`, when an
|
|
241
|
+
* array, as described in the module comment with `fullMessageRoles`; every
|
|
242
|
+
* other member is validated and left out.
|
|
243
|
+
*/
|
|
244
|
+
export function provenTopLevelObject(bytes, decode, fullMessageRoles = EMPTY) {
|
|
245
|
+
try {
|
|
246
|
+
return provenDocument(bytes, decode, fullMessageRoles);
|
|
247
|
+
} catch {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function provenDocument(bytes, decode, fullRoles) {
|
|
253
|
+
const text = bytes.toString('latin1');
|
|
254
|
+
if (CONTROL_BYTE.test(text) || INVALID_ESCAPE.test(text)) return null;
|
|
255
|
+
let i = skipSpaces(text, 0);
|
|
256
|
+
if (text.charCodeAt(i) !== OPEN_OBJECT) return null;
|
|
257
|
+
const doc = {};
|
|
258
|
+
const seen = new Set();
|
|
259
|
+
i = skipSpaces(text, i + 1);
|
|
260
|
+
if (text.charCodeAt(i) === CLOSE_OBJECT) i++;
|
|
261
|
+
else {
|
|
262
|
+
for (;;) {
|
|
263
|
+
if (text.charCodeAt(i) !== QUOTE) return null;
|
|
264
|
+
const keyEnd = stringEnd(text, i);
|
|
265
|
+
if (keyEnd === -1) return null;
|
|
266
|
+
const key = decodeKey(bytes, text, i, keyEnd);
|
|
267
|
+
if (seen.has(key)) return null;
|
|
268
|
+
seen.add(key);
|
|
269
|
+
const colon = skipSpaces(text, keyEnd);
|
|
270
|
+
if (text.charCodeAt(colon) !== COLON) return null;
|
|
271
|
+
const start = skipSpaces(text, colon + 1);
|
|
272
|
+
const wanted = decode(key);
|
|
273
|
+
let end;
|
|
274
|
+
if (wanted && key === 'messages' && text.charCodeAt(start) === OPEN_ARRAY) {
|
|
275
|
+
const value = messagesValue(bytes, text, start, fullRoles);
|
|
276
|
+
if (!value) return null;
|
|
277
|
+
end = value.end;
|
|
278
|
+
defineMember(doc, key, value.messages);
|
|
279
|
+
} else {
|
|
280
|
+
end = valueEnd(text, start);
|
|
281
|
+
if (end === -1) return null;
|
|
282
|
+
if (wanted) defineMember(doc, key, decodeSlice(bytes, start, end));
|
|
283
|
+
}
|
|
284
|
+
i = skipSpaces(text, end);
|
|
285
|
+
if (text.charCodeAt(i) === COMMA) {
|
|
286
|
+
i = skipSpaces(text, i + 1);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (text.charCodeAt(i) !== CLOSE_OBJECT) return null;
|
|
290
|
+
i++;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return skipSpaces(text, i) === text.length ? doc : null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* `readTopLevelLifecycleRecord` of the UTF-8 document in `bytes`, with `doc`
|
|
299
|
+
* decoded as described above — only for a proven document; a record from the
|
|
300
|
+
* authoritative fallback carries the complete document. Consumers must read
|
|
301
|
+
* nothing but non-skipped fields and, of non-full messages, `role`.
|
|
302
|
+
*/
|
|
303
|
+
export function readStrictRecordFields(bytes, { skip = EMPTY, fullMessageRoles = EMPTY } = {}) {
|
|
304
|
+
const doc = provenTopLevelObject(bytes, (key) => !skip.has(key), fullMessageRoles);
|
|
305
|
+
return doc ? lifecycleRecordOf(doc) : readTopLevelLifecycleRecord(bytes.toString('utf8'));
|
|
306
|
+
}
|
|
@@ -15,8 +15,9 @@ import { join } from 'node:path';
|
|
|
15
15
|
// Leaf helpers only: the three-way present/absent/unreadable classification
|
|
16
16
|
// and the strict record parser the authoritative store uses, so the catalog
|
|
17
17
|
// cannot disagree with it.
|
|
18
|
-
import { probePath, readTextFile, PROBE_PRESENT, PROBE_ABSENT } from './store/fs-probe.mjs';
|
|
19
|
-
import {
|
|
18
|
+
import { probePath, readBytesFile, readTextFile, PROBE_PRESENT, PROBE_ABSENT } from './store/fs-probe.mjs';
|
|
19
|
+
import { isLifecycleUnreadable } from './lifecycle-scan.mjs';
|
|
20
|
+
import { readStrictRecordFields } from './store-record-strict-scan.mjs';
|
|
20
21
|
import { isAgentOnlySession, isRootLeadSession, sessionVisibility } from './store-summary-visibility.mjs';
|
|
21
22
|
import { applySummaryLogText, summaryLogPath } from './store-summary-log.mjs';
|
|
22
23
|
import { dataDir } from './store-summary-locations.mjs';
|
|
@@ -43,7 +44,7 @@ export const STAMP_ABSENT_CODES = new Set(['ENOENT', 'ENOTDIR']);
|
|
|
43
44
|
/** The one catalog order of every listing path (JSON and DB): newest
|
|
44
45
|
* conversation activity first (see normalizedRow's lastUsedAt note), ties by
|
|
45
46
|
* id, so a fallback and the DB never disagree on equal activity times. */
|
|
46
|
-
|
|
47
|
+
const byCatalogOrder = byActivityThenId;
|
|
47
48
|
|
|
48
49
|
// ── Stamp-keyed strict derivations ──────────────────────────────────────────
|
|
49
50
|
// Session metadata and catalog rows are small values derived from a strict
|
|
@@ -52,8 +53,20 @@ export const byCatalogOrder = byActivityThenId;
|
|
|
52
53
|
// proven to come from (stat → read → stat) and that stamp is past the racy
|
|
53
54
|
// window. Any other file — changed, replaced by another writer, unreadable —
|
|
54
55
|
// is read and strictly parsed again.
|
|
56
|
+
// Bounded by count and by serialized size: nothing tells this map a session
|
|
57
|
+
// file went away, so a churning host fills it with dead paths until LRU
|
|
58
|
+
// eviction (`delete` + `set` on a hit keeps Map order least recent first).
|
|
55
59
|
const STAMPED_DERIVATION_LIMIT = 2048;
|
|
56
|
-
const
|
|
60
|
+
const STAMPED_DERIVATION_MAX_BYTES = 8 * 1024 * 1024;
|
|
61
|
+
const stampedDerivations = new Map(); // `${kind}|${path}` → { stamp, value, bytes }
|
|
62
|
+
let stampedDerivationBytes = 0;
|
|
63
|
+
|
|
64
|
+
function dropStampedDerivation(key) {
|
|
65
|
+
const entry = stampedDerivations.get(key);
|
|
66
|
+
if (!entry) return;
|
|
67
|
+
stampedDerivations.delete(key);
|
|
68
|
+
stampedDerivationBytes -= entry.bytes;
|
|
69
|
+
}
|
|
57
70
|
|
|
58
71
|
export function observeStamp(path) {
|
|
59
72
|
try {
|
|
@@ -64,24 +77,35 @@ export function observeStamp(path) {
|
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
/** `{ state, value }`: `derive(text)` for the record at `path`, or its cached
|
|
67
|
-
* result while the file is provably unchanged.
|
|
68
|
-
|
|
80
|
+
* result while the file is provably unchanged. `bytes`: `derive` gets the
|
|
81
|
+
* raw Buffer instead of text. `stamp`: the caller's own stat of `path`,
|
|
82
|
+
* taken just before, stands for the pre-read stat (one stat fewer). */
|
|
83
|
+
export function stampedDerivation(kind, path, derive, { bytes = false, stamp } = {}) {
|
|
69
84
|
const key = `${kind}|${path}`;
|
|
70
|
-
const before = observeStamp(path);
|
|
85
|
+
const before = stamp === undefined ? observeStamp(path) : stamp;
|
|
71
86
|
const cached = stampedDerivations.get(key);
|
|
72
87
|
if (before && cached && sameSessionStamp(cached.stamp, before)) {
|
|
73
88
|
stampedDerivations.delete(key);
|
|
74
89
|
stampedDerivations.set(key, cached);
|
|
75
90
|
return { state: PROBE_PRESENT, value: cached.value };
|
|
76
91
|
}
|
|
77
|
-
|
|
78
|
-
const read = readTextFile(path);
|
|
92
|
+
dropStampedDerivation(key);
|
|
93
|
+
const read = bytes ? readBytesFile(path) : readTextFile(path);
|
|
79
94
|
if (read.state !== PROBE_PRESENT) return { state: read.state, value: null };
|
|
80
|
-
const value = derive(read.text);
|
|
95
|
+
const value = derive(bytes ? read.bytes : read.text);
|
|
81
96
|
if (isSettledSessionStamp(before) && sameSessionStamp(before, observeStamp(path))) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
// UTF-16 code units of the key and the serialized value approximate the
|
|
98
|
+
// retained bytes; the strict parse just above dwarfs this stringify.
|
|
99
|
+
const size = 2 * (key.length + (JSON.stringify(value)?.length ?? 0));
|
|
100
|
+
if (size <= STAMPED_DERIVATION_MAX_BYTES) {
|
|
101
|
+
stampedDerivations.set(key, { stamp: before, value, bytes: size });
|
|
102
|
+
stampedDerivationBytes += size;
|
|
103
|
+
while (
|
|
104
|
+
stampedDerivations.size > STAMPED_DERIVATION_LIMIT ||
|
|
105
|
+
stampedDerivationBytes > STAMPED_DERIVATION_MAX_BYTES
|
|
106
|
+
) {
|
|
107
|
+
dropStampedDerivation(stampedDerivations.keys().next().value);
|
|
108
|
+
}
|
|
85
109
|
}
|
|
86
110
|
}
|
|
87
111
|
return { state: PROBE_PRESENT, value };
|
|
@@ -199,6 +223,24 @@ function leadRows(rows) {
|
|
|
199
223
|
return rows.filter((row) => row && isLeadVisibleRow(row));
|
|
200
224
|
}
|
|
201
225
|
|
|
226
|
+
// What a row derivation decodes of a record (store-record-strict-scan.mjs):
|
|
227
|
+
// rowFromSession reads its user messages whole and only the role of the rest,
|
|
228
|
+
// and never these top-level fields — together ~10% of a record, mostly tool
|
|
229
|
+
// catalogs. Every other top-level field is decoded, so a field a row starts
|
|
230
|
+
// to read is there without touching this list.
|
|
231
|
+
const CATALOG_RECORD_READ = {
|
|
232
|
+
skip: new Set([
|
|
233
|
+
'tools',
|
|
234
|
+
'deferredToolCatalog',
|
|
235
|
+
'deferredLateToolCatalog',
|
|
236
|
+
'bp3CoreContext',
|
|
237
|
+
'bp3EnvironmentContext',
|
|
238
|
+
'compaction',
|
|
239
|
+
'pendingDeferredToolDelta',
|
|
240
|
+
]),
|
|
241
|
+
fullMessageRoles: new Set(['user']),
|
|
242
|
+
};
|
|
243
|
+
|
|
202
244
|
function rowFromSession(session) {
|
|
203
245
|
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
204
246
|
const preview =
|
|
@@ -259,14 +301,21 @@ function reconcileSessionFile(directory, storageId, retained, forceRead) {
|
|
|
259
301
|
return { kind: 'indexed', ...fingerprint };
|
|
260
302
|
}
|
|
261
303
|
// A file the index does not describe (never saved by the store, or
|
|
262
|
-
// saved after the index was written) is
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
304
|
+
// saved after the index was written) is read once per full stamp; the
|
|
305
|
+
// stat above is the read's pre-read stat.
|
|
306
|
+
const read = stampedDerivation(
|
|
307
|
+
'row',
|
|
308
|
+
path,
|
|
309
|
+
(bytes) => {
|
|
310
|
+
// Same strict authority as the store: a duplicate/ambiguous top-level
|
|
311
|
+
// record or a foreign identity is not this file's session. Only what a
|
|
312
|
+
// row is made of is decoded (see CATALOG_RECORD_READ).
|
|
313
|
+
const record = readStrictRecordFields(bytes, CATALOG_RECORD_READ);
|
|
314
|
+
if (isLifecycleUnreadable(record) || record.id !== storageId) return null;
|
|
315
|
+
return rowFromSession(record.doc);
|
|
316
|
+
},
|
|
317
|
+
{ bytes: true, stamp }
|
|
318
|
+
);
|
|
270
319
|
if (read.state === PROBE_ABSENT) return null;
|
|
271
320
|
// Exists and could not be read (EACCES/EIO): retain the last known row
|
|
272
321
|
// rather than let a transient IO error delete it from view.
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from './store-summary-catalog.mjs';
|
|
25
25
|
import { createStoredTranscriptCache, nextProjectionStamp } from './store-transcript-cache.mjs';
|
|
26
26
|
import {
|
|
27
|
+
forgetStoredTranscriptOffThread,
|
|
27
28
|
OFFLOAD_MIN_CHARS,
|
|
28
29
|
projectStoredTranscriptOffThread,
|
|
29
30
|
storedTranscriptWorkerRunning,
|
|
@@ -35,6 +36,7 @@ import { readArchivedAgentResult } from './store-summary-archived-agent.mjs';
|
|
|
35
36
|
import { compactionArchivePath, readCompactionArchivePage } from './compaction-archive.mjs';
|
|
36
37
|
import { currentSessionCatalogDb, onSessionCatalogDbChange, sessionCatalogDb } from './catalog-db/index.mjs';
|
|
37
38
|
import { decodeCatalogCursor, encodeCatalogCursor, isAfterCursor } from './catalog-db/schema.mjs';
|
|
39
|
+
import { onSessionActivityChange } from '../../../shared/session-activity-signal.mjs';
|
|
38
40
|
import {
|
|
39
41
|
readCanonicalSessionRecord as readCanonicalLifecycle,
|
|
40
42
|
offerStrictSessionRecord,
|
|
@@ -45,7 +47,11 @@ import {
|
|
|
45
47
|
// Worker-pool row projection (child + Lead indexes, live-work evidence) lives
|
|
46
48
|
// in store-agent-worker-rows.mjs; re-exported so prior importers of this
|
|
47
49
|
// module stay unchanged.
|
|
48
|
-
export {
|
|
50
|
+
export {
|
|
51
|
+
listStoredAgentWorkerLinks,
|
|
52
|
+
listStoredAgentWorkers,
|
|
53
|
+
listStoredAgentWorkersAsync,
|
|
54
|
+
} from './store-agent-worker-rows.mjs';
|
|
49
55
|
export { storedAgentWorkerIndexPath } from './store-summary-locations.mjs';
|
|
50
56
|
|
|
51
57
|
// One cache per process: the daemon serves every cold pane read from it, and
|
|
@@ -59,7 +65,9 @@ export function clearStoredTranscriptCache() {
|
|
|
59
65
|
|
|
60
66
|
/** Drop one session's cold projections: it went live, or its last view left. */
|
|
61
67
|
export function forgetStoredSessionTranscript(sessionId) {
|
|
62
|
-
|
|
68
|
+
const id = String(sessionId || '').trim();
|
|
69
|
+
storedTranscriptCache.forget(`${id}|`);
|
|
70
|
+
if (id) forgetStoredTranscriptOffThread(id);
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
/** Its last view left: keep the cold projections for a revisit, within the
|
|
@@ -82,18 +90,22 @@ export function storedTranscriptCacheStats() {
|
|
|
82
90
|
const ordinaryCatalog = createOrdinaryCatalog();
|
|
83
91
|
const catalogListeners = new Set();
|
|
84
92
|
|
|
85
|
-
function notifyCatalogListeners() {
|
|
93
|
+
function notifyCatalogListeners(kind = 'catalog') {
|
|
86
94
|
for (const listener of catalogListeners) {
|
|
87
95
|
try {
|
|
88
|
-
listener();
|
|
96
|
+
listener(kind);
|
|
89
97
|
} catch {
|
|
90
98
|
/* a listener's failure is its own */
|
|
91
99
|
}
|
|
92
100
|
}
|
|
93
101
|
}
|
|
94
102
|
|
|
95
|
-
ordinaryCatalog.subscribe(notifyCatalogListeners);
|
|
96
|
-
onSessionCatalogDbChange(notifyCatalogListeners);
|
|
103
|
+
ordinaryCatalog.subscribe(() => notifyCatalogListeners());
|
|
104
|
+
onSessionCatalogDbChange(() => notifyCatalogListeners());
|
|
105
|
+
// Runtime activity transitions (turn start/settle, task wait, background
|
|
106
|
+
// hand-off/finish) change only the Agents pool projection: `agent-activity`
|
|
107
|
+
// lets the reader refresh just that, at once.
|
|
108
|
+
onSessionActivityChange(() => notifyCatalogListeners('agent-activity'));
|
|
97
109
|
|
|
98
110
|
/** `listener()` runs when a background pass finds a session file that changed
|
|
99
111
|
* without a directory or index change, i.e. when an ordinary listing would
|
|
@@ -292,29 +304,32 @@ export function storedSessionTranscriptStamp(id) {
|
|
|
292
304
|
return archive ? `${record}|${checkpoint}|${archive}` : null;
|
|
293
305
|
}
|
|
294
306
|
|
|
307
|
+
/** A large record with no checkpoint sidecar is read, digested, parsed and
|
|
308
|
+
* projected on a worker thread: the same pipeline, off the daemon event
|
|
309
|
+
* loop. It hands no parse to the load cache (a later resume reads the file
|
|
310
|
+
* itself). The answer is the cache's `project` contract; a worker failure
|
|
311
|
+
* rejects and the cache runs the in-process pipeline instead. */
|
|
312
|
+
async function projectStoredTranscriptElsewhere(known, context) {
|
|
313
|
+
const { sessionId, recordPath, itemLimit, timing } = context;
|
|
314
|
+
timing.worker = storedTranscriptWorkerRunning(sessionId) ? 'warm' : 'cold';
|
|
315
|
+
const workerStartedAt = performance.now();
|
|
316
|
+
const answer = await projectStoredTranscriptOffThread({ sessionId, recordPath, itemLimit, known });
|
|
317
|
+
timing.workerMs = performance.now() - workerStartedAt;
|
|
318
|
+
Object.assign(timing, answer.timing);
|
|
319
|
+
context.readState = answer.state;
|
|
320
|
+
if (answer.state !== PROBE_PRESENT) return null;
|
|
321
|
+
if (answer.same) return answer;
|
|
322
|
+
if (answer.unreadable) return { ...answer, value: null };
|
|
323
|
+
if (answer.stamp) readCanonicalLifecycle.rememberStrictVerdict(recordPath, answer.stamp, answer.lifecycle);
|
|
324
|
+
// Stamps are process-unique per module realm; re-issue in this one.
|
|
325
|
+
answer.value.projectionStamp = nextProjectionStamp();
|
|
326
|
+
return answer;
|
|
327
|
+
}
|
|
328
|
+
|
|
295
329
|
/** Projects the strictly parsed record `text` (read at full stamp `stamp`, or
|
|
296
330
|
* null when not provably stable) into the cached transcript value. */
|
|
297
331
|
async function produceStoredTranscript(text, stamp, context) {
|
|
298
|
-
const { sessionId, recordPath, itemLimit, includeMessages, mode,
|
|
299
|
-
// A large record with no checkpoint sidecar parses and projects on a
|
|
300
|
-
// worker thread: the same pipeline, off the daemon event loop. It hands
|
|
301
|
-
// no parse to the load cache (a later resume reads the file itself).
|
|
302
|
-
if (offloadable && text.length >= OFFLOAD_MIN_CHARS) {
|
|
303
|
-
try {
|
|
304
|
-
timing.worker = storedTranscriptWorkerRunning() ? 'warm' : 'cold';
|
|
305
|
-
const workerStartedAt = performance.now();
|
|
306
|
-
const answer = await projectStoredTranscriptOffThread({ sessionId, text, itemLimit });
|
|
307
|
-
timing.workerMs = performance.now() - workerStartedAt;
|
|
308
|
-
Object.assign(timing, answer.timing);
|
|
309
|
-
if (answer.unreadable) return null;
|
|
310
|
-
if (stamp) readCanonicalLifecycle.rememberStrictVerdict(recordPath, stamp, answer.lifecycle);
|
|
311
|
-
// Stamps are process-unique per module realm; re-issue in this one.
|
|
312
|
-
answer.value.projectionStamp = nextProjectionStamp();
|
|
313
|
-
return answer.value;
|
|
314
|
-
} catch {
|
|
315
|
-
// Worker unavailable: fall through to the in-process pipeline.
|
|
316
|
-
}
|
|
317
|
-
}
|
|
332
|
+
const { sessionId, recordPath, itemLimit, includeMessages, mode, checkpointAbsent, timing } = context;
|
|
318
333
|
const parseStartedAt = performance.now();
|
|
319
334
|
const record = readTopLevelLifecycleRecord(text);
|
|
320
335
|
timing.parseMs = performance.now() - parseStartedAt;
|
|
@@ -388,7 +403,6 @@ export async function readStoredSessionTranscript(id, options = {}) {
|
|
|
388
403
|
const archive = probePath(archivePath);
|
|
389
404
|
const requestedLimit = Number(options.transcriptItemLimit);
|
|
390
405
|
const itemLimit = Number.isFinite(requestedLimit) && requestedLimit > 0 ? requestedLimit : Number.POSITIVE_INFINITY;
|
|
391
|
-
let readState = PROBE_PRESENT;
|
|
392
406
|
// Full stamp the loaded text provably came from (unchanged around the
|
|
393
407
|
// read), or null. Only such a parse may serve other readers.
|
|
394
408
|
let readStamp = null;
|
|
@@ -405,9 +419,10 @@ export async function readStoredSessionTranscript(id, options = {}) {
|
|
|
405
419
|
itemLimit,
|
|
406
420
|
includeMessages,
|
|
407
421
|
mode,
|
|
408
|
-
offloadable,
|
|
409
422
|
checkpointAbsent,
|
|
410
423
|
timing,
|
|
424
|
+
// How the record read went (the worker's read or loadText below).
|
|
425
|
+
readState: PROBE_PRESENT,
|
|
411
426
|
};
|
|
412
427
|
const { value, hit, read } = await storedTranscriptCache.read({
|
|
413
428
|
key: `${sessionId}|${itemLimit}|${mode}`,
|
|
@@ -425,6 +440,7 @@ export async function readStoredSessionTranscript(id, options = {}) {
|
|
|
425
440
|
archive.size,
|
|
426
441
|
].join(':'),
|
|
427
442
|
fileStat: recordStat,
|
|
443
|
+
project: offloadable ? (known) => projectStoredTranscriptElsewhere(known, produceContext) : null,
|
|
428
444
|
loadText: async () => {
|
|
429
445
|
const readStartedAt = performance.now();
|
|
430
446
|
const before = observeStamp(recordPath);
|
|
@@ -440,7 +456,7 @@ export async function readStoredSessionTranscript(id, options = {}) {
|
|
|
440
456
|
}
|
|
441
457
|
body ||= readTextFile(recordPath);
|
|
442
458
|
timing.readMs = performance.now() - readStartedAt;
|
|
443
|
-
readState = body.state;
|
|
459
|
+
produceContext.readState = body.state;
|
|
444
460
|
if (body.state !== PROBE_PRESENT) return null;
|
|
445
461
|
readStamp = before && sameSessionStamp(before, observeStamp(recordPath)) ? before : null;
|
|
446
462
|
return body.text;
|
|
@@ -448,7 +464,7 @@ export async function readStoredSessionTranscript(id, options = {}) {
|
|
|
448
464
|
produce: (text) => produceStoredTranscript(text, readStamp, produceContext),
|
|
449
465
|
});
|
|
450
466
|
// The record vanished between stat and read: same answer as an absent probe.
|
|
451
|
-
if (readState === PROBE_ABSENT) return readVanishedSessionTranscript(sessionId);
|
|
467
|
+
if (produceContext.readState === PROBE_ABSENT) return readVanishedSessionTranscript(sessionId);
|
|
452
468
|
if (typeof options.trace === 'function') {
|
|
453
469
|
options.trace({
|
|
454
470
|
sessionId,
|