mixdog 1.1.2 → 1.2.0
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/README.md +1 -0
- package/package.json +2 -1
- package/scripts/lib/run-node-tests.mjs +9 -0
- package/scripts/prune-desktop-runtime.mjs +3 -0
- package/scripts/test-direct.mjs +4 -2
- package/src/defaults/shipped-history.json +6 -0
- package/src/defaults/skills/setup/references/actions.md +5 -2
- package/src/defaults/skills/visualize/SKILL.md +157 -0
- package/src/defaults/skills/visualize/references/craft.md +88 -0
- package/src/headless-command.mjs +56 -3
- package/src/headless-exec.mjs +17 -1
- package/src/help.mjs +2 -0
- package/src/rules/shared/70-delivery.md +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
- package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
- package/src/runtime/agent/orchestrator/config.mjs +9 -1
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +47 -7
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +51 -8
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +25 -5
- package/src/runtime/agent/orchestrator/mcp/client.mjs +348 -122
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +12 -4
- package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +67 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +521 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +15 -1
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +12 -11
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +11 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +18 -11
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +17 -7
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
- package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +29 -19
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +23 -4
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +28 -8
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +140 -0
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
- package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +31 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -66
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
- package/src/runtime/attachments/store.mjs +38 -0
- package/src/runtime/shared/atomic-file.mjs +8 -2
- package/src/runtime/shared/best-effort-stderr.mjs +8 -0
- package/src/runtime/shared/code-graph-state.mjs +12 -0
- package/src/runtime/shared/defaults-separation.mjs +2 -0
- package/src/runtime/shared/git-runner-worker.mjs +3 -2
- package/src/runtime/shared/git-runner.mjs +4 -3
- package/src/runtime/shared/llm/default-context-window.mjs +6 -0
- package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
- package/src/runtime/shared/llm/model-catalog.mjs +240 -118
- package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +156 -0
- package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
- package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
- package/src/runtime/shared/model-proxy-mode.mjs +19 -0
- package/src/runtime/shared/provider-accounts.mjs +2 -2
- package/src/runtime/shared/provider-api-key.mjs +21 -1
- package/src/runtime/shared/schedule-model-ref.mjs +11 -2
- package/src/runtime/shared/session-usage-totals.mjs +19 -0
- package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
- package/src/runtime/shared/singleton-owner.mjs +36 -6
- package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
- package/src/runtime/shared/tool-surface.mjs +7 -0
- package/src/runtime/shared/tool-work-units.mjs +14 -0
- package/src/runtime/shared/transcript-metadata.mjs +17 -1
- package/src/runtime/shared/turn-changes.mjs +791 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
- package/src/runtime/shared/turn-snapshot.mjs +690 -81
- package/src/runtime/shared/turn-worktree-snapshot.mjs +161 -11
- package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
- package/src/runtime/shared/worktrees/contract.mjs +551 -0
- package/src/runtime/shared/worktrees/disk.mjs +57 -0
- package/src/runtime/shared/worktrees/git.mjs +247 -0
- package/src/runtime/shared/worktrees/include.mjs +480 -0
- package/src/runtime/shared/worktrees/lock.mjs +46 -0
- package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
- package/src/runtime/shared/worktrees/manager.mjs +1215 -0
- package/src/runtime/shared/worktrees/merge.mjs +182 -0
- package/src/runtime/shared/worktrees/owner.mjs +46 -0
- package/src/runtime/shared/worktrees/paths.mjs +62 -0
- package/src/runtime/shared/worktrees/registry.mjs +110 -0
- package/src/runtime/shared/worktrees/settings.mjs +84 -0
- package/src/runtime/shared/worktrees/slug.mjs +62 -0
- package/src/runtime/shared/worktrees/test-support.mjs +96 -0
- package/src/runtime/visualize-bridge/client.mjs +181 -0
- package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
- package/src/session-runtime/boot/apis.mjs +35 -0
- package/src/session-runtime/boot/config.mjs +2 -0
- package/src/session-runtime/boot/facade.mjs +20 -0
- package/src/session-runtime/boot/infrastructure.mjs +1 -0
- package/src/session-runtime/boot/session.mjs +1 -0
- package/src/session-runtime/boot/tools.mjs +4 -0
- package/src/session-runtime/config-lifecycle.mjs +10 -1
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +11 -1
- package/src/session-runtime/global-extensions.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
- package/src/session-runtime/internal-tool-executor.mjs +9 -1
- package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
- package/src/session-runtime/mcp-glue/connect.mjs +11 -6
- package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -3
- package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
- package/src/session-runtime/model-route/route-persist.mjs +3 -0
- package/src/session-runtime/model-route/set-route.mjs +15 -2
- package/src/session-runtime/notification-bus.mjs +9 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
- package/src/session-runtime/resource-extension-sync.mjs +30 -7
- package/src/session-runtime/resource-mcp-api.mjs +20 -7
- package/src/session-runtime/route-resolution.mjs +10 -1
- package/src/session-runtime/route-state.mjs +16 -2
- package/src/session-runtime/runtime-core.mjs +5 -0
- package/src/session-runtime/runtime-facade.mjs +8 -0
- package/src/session-runtime/runtime-review-api.mjs +9 -0
- package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
- package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
- package/src/session-runtime/services/agent-tool.mjs +1 -0
- package/src/session-runtime/services/daemon-candidate.mjs +3 -2
- package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
- package/src/session-runtime/session-create/session-options.mjs +10 -1
- package/src/session-runtime/session-turn-api.mjs +1 -0
- package/src/session-runtime/settings-api.mjs +4 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
- package/src/session-runtime/settings-worktree-api.mjs +17 -0
- package/src/session-runtime/setup-tool/executor.mjs +11 -4
- package/src/session-runtime/setup-tool/settings-contract.mjs +3 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +31 -3
- package/src/session-runtime/turn/agent-ops.mjs +12 -1
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +13 -3
- package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
- package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
- package/src/session-runtime/turn/turn-run.mjs +6 -7
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
- package/src/session-runtime/workspace/admission-gate.mjs +197 -0
- package/src/session-runtime/workspace/project-branches.mjs +136 -0
- package/src/session-runtime/workspace/transition-steps.mjs +364 -0
- package/src/session-runtime/workspace/transitions.mjs +651 -0
- package/src/session-runtime/workspace/workspace-state.mjs +112 -0
- package/src/standalone/channel-transport/http-routes.mjs +17 -0
- package/src/standalone/channel-transport.mjs +13 -1
- package/src/standalone/daemon-desktop-runtime.mjs +1 -0
- package/src/standalone/daemon-owner.mjs +372 -0
- package/src/standalone/daemon-stored-session-views.mjs +4 -1
- package/src/standalone/daemon.mjs +139 -5
- package/src/standalone/desktop-service-registry.mjs +20 -0
- package/src/standalone/local-session-runtime.mjs +11 -0
- package/src/standalone/session-client/attachment-calls.mjs +3 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
- package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
- package/src/standalone/session-client.mjs +104 -13
- package/src/standalone/session-protocol.mjs +16 -0
- package/src/standalone/session-proxy.mjs +3 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
- package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
- package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
- package/src/standalone/session-service/projection/revisions.mjs +2 -0
- package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
- package/src/standalone/session-service/projection/workspace.mjs +14 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +11 -3
- package/src/standalone/session-transport/call-route.mjs +13 -0
- package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
- package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
- package/src/standalone/session-transport/client-registry.mjs +20 -3
- package/src/standalone/session-transport/routes.mjs +79 -2
- package/src/standalone/session-transport/stack.mjs +2 -0
- package/src/standalone/session-transport.mjs +24 -1
- package/src/tui/app/message-selector.mjs +2 -1
- package/src/tui/app/model-options.mjs +35 -5
- package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
- package/src/tui/app/model-picker.mjs +4 -2
- package/src/tui/dist/index.mjs +332 -36
- package/src/tui/index.jsx +10 -2
- package/src/tui/session/compaction-view.mjs +147 -0
- package/src/tui/session/context-state.mjs +6 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/quota-resume.mjs +2 -1
- package/src/tui/session/session-api/commands.mjs +10 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/intake/rewind.mjs +2 -1
- package/src/tui/session/session-api/integrations.mjs +17 -0
- package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
- package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
- package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
- package/src/tui/session/session-api/routes.mjs +10 -1
- package/src/tui/session/session-api/settings.mjs +2 -0
- package/src/tui/session/session-api/worktree.mjs +60 -0
- package/src/tui/session/session-api.mjs +2 -0
- package/src/tui/session/session-flow/auto-clear.mjs +31 -12
- package/src/tui/session/session-flow/drain.mjs +15 -1
- package/src/tui/session/session-flow/session-reset.mjs +4 -3
- package/src/tui/session/session-flow.mjs +2 -0
- package/src/tui/session/transcript-intake.mjs +14 -5
- package/src/tui/session/turn-stream.mjs +32 -4
- package/src/tui/session/turn-tool-cards.mjs +10 -2
- package/src/tui/session/turn.mjs +39 -10
|
@@ -92,7 +92,18 @@ export function sessionOriginFields(opts, { profile, presetObj, providerCacheOpt
|
|
|
92
92
|
|
|
93
93
|
// The persisted session record.
|
|
94
94
|
export function buildSessionRecord({ opts, route, presetObj, surface, prompt, messages, contextMeta, origin }) {
|
|
95
|
-
const {
|
|
95
|
+
const {
|
|
96
|
+
providerName,
|
|
97
|
+
modelName,
|
|
98
|
+
toolPreset,
|
|
99
|
+
effort,
|
|
100
|
+
fast,
|
|
101
|
+
autoEffort,
|
|
102
|
+
modelParameters,
|
|
103
|
+
contextPercent,
|
|
104
|
+
contextTokens,
|
|
105
|
+
id,
|
|
106
|
+
} = route;
|
|
96
107
|
return {
|
|
97
108
|
id,
|
|
98
109
|
codexWireSessionId: providerName === 'openai-oauth' ? mintUuidV7() : null,
|
|
@@ -115,8 +126,11 @@ export function buildSessionRecord({ opts, route, presetObj, surface, prompt, me
|
|
|
115
126
|
presetName: presetObj?.name || null,
|
|
116
127
|
effort,
|
|
117
128
|
fast,
|
|
129
|
+
// Absent means Auto effort is on; only the off state is persisted.
|
|
130
|
+
...(autoEffort === false ? { autoEffort: false } : {}),
|
|
118
131
|
modelParameters,
|
|
119
132
|
contextPercent,
|
|
133
|
+
...(contextTokens ? { contextTokens } : {}),
|
|
120
134
|
selectedContextWindow: opts.selectedContextWindow || null,
|
|
121
135
|
agent: opts.agent,
|
|
122
136
|
owner: opts.owner || 'user',
|
|
@@ -144,6 +158,7 @@ export function buildSessionRecord({ opts, route, presetObj, surface, prompt, me
|
|
|
144
158
|
lastHeartbeatAt: null,
|
|
145
159
|
totalInputTokens: 0,
|
|
146
160
|
totalOutputTokens: 0,
|
|
161
|
+
totalToolCalls: 0,
|
|
147
162
|
// Refreshed on each completed ask() — surfaced by agent type=list for
|
|
148
163
|
// debugging + consumed by store.mjs's idle-sweep to reclaim stalled
|
|
149
164
|
// agent sessions past RUNNING_STALL_MS.
|
|
@@ -35,6 +35,7 @@ function standaloneStatusRouteInfo(session) {
|
|
|
35
35
|
modelDisplay: session.modelDisplay || session.displayName || session.model,
|
|
36
36
|
effort: session.effort || '',
|
|
37
37
|
fast: session.fast === true,
|
|
38
|
+
...(session.autoEffort === false ? { autoEffort: false } : {}),
|
|
38
39
|
modelParameters: session.modelParameters || {},
|
|
39
40
|
contextWindow: session.contextWindow || null,
|
|
40
41
|
rawContextWindow: session.rawContextWindow || session.contextWindow || null,
|
|
@@ -390,6 +390,7 @@ export function applyAskTerminalUsageTotals(session, result, options = {}) {
|
|
|
390
390
|
accumulateSessionUsage(session, {
|
|
391
391
|
deltaInput: result.usage.inputTokens || 0,
|
|
392
392
|
deltaOutput: result.usage.outputTokens || 0,
|
|
393
|
+
deltaToolCalls: result.toolCallsTotal || 0,
|
|
393
394
|
...reasoningUsage(result.usage),
|
|
394
395
|
deltaCachedRead: result.usage.cachedTokens || 0,
|
|
395
396
|
deltaCacheWrite: result.usage.cacheWriteTokens || 0,
|
|
@@ -465,6 +466,7 @@ function accumulateSessionUsage(session, delta) {
|
|
|
465
466
|
: uncachedInputTokensForProvider(session.provider, deltaInput, deltaCachedRead, deltaCacheWrite);
|
|
466
467
|
session.totalInputTokens = (session.totalInputTokens || 0) + (deltaInput || 0);
|
|
467
468
|
session.totalOutputTokens = (session.totalOutputTokens || 0) + (deltaOutput || 0);
|
|
469
|
+
session.totalToolCalls = (session.totalToolCalls || 0) + (delta.deltaToolCalls || 0);
|
|
468
470
|
session.tokensCumulative = (session.tokensCumulative || 0) + (deltaInput || 0) + (deltaOutput || 0);
|
|
469
471
|
session.totalCachedReadTokens = (session.totalCachedReadTokens || 0) + (deltaCachedRead || 0);
|
|
470
472
|
session.totalCacheWriteTokens = (session.totalCacheWriteTokens || 0) + (deltaCacheWrite || 0);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable per-message identity: `meta.transcript.messageId`.
|
|
3
|
+
*
|
|
4
|
+
* Prompts and replies get theirs at creation (shared/transcript-metadata.mjs).
|
|
5
|
+
* Every other message — tool calls and results, injected rows, legacy
|
|
6
|
+
* transcripts — is stamped before a compaction runs or a stored session is
|
|
7
|
+
* resumed (stampMessageIds), and every compaction rewrite carries the id of
|
|
8
|
+
* the message it rewrites to its copy (spread copies keep `meta`;
|
|
9
|
+
* constructed copies use carryMessageId). A compaction's dropped messages
|
|
10
|
+
* are then exactly the ids its result no longer holds.
|
|
11
|
+
*
|
|
12
|
+
* A message whose `meta` is a string marker ('skill', 'hook': injected
|
|
13
|
+
* model context that is never displayed) cannot carry an id without changing
|
|
14
|
+
* what that marker means; it stays unstamped and has no display identity.
|
|
15
|
+
*
|
|
16
|
+
* Leaf module: no store, no config.
|
|
17
|
+
*/
|
|
18
|
+
import { createHash } from 'node:crypto';
|
|
19
|
+
|
|
20
|
+
/** The message's stable id, or ''. */
|
|
21
|
+
export function messageIdOf(message) {
|
|
22
|
+
const meta = message?.meta;
|
|
23
|
+
if (!meta || typeof meta !== 'object') return '';
|
|
24
|
+
const id = meta.transcript?.messageId;
|
|
25
|
+
return typeof id === 'string' ? id : '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Whether `meta` can hold a transcript id (absent or a plain object). */
|
|
29
|
+
export function canCarryMessageId(message) {
|
|
30
|
+
if (!message || typeof message !== 'object') return false;
|
|
31
|
+
const meta = message.meta;
|
|
32
|
+
return meta === undefined || meta === null || (typeof meta === 'object' && !Array.isArray(meta));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function withMessageId(meta, messageId) {
|
|
36
|
+
const base = meta && typeof meta === 'object' ? meta : {};
|
|
37
|
+
const transcript = base.transcript && typeof base.transcript === 'object' ? base.transcript : {};
|
|
38
|
+
return { ...base, transcript: { ...transcript, messageId } };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// A deterministic seed: the same unstamped transcript gets the same ids on
|
|
42
|
+
// every stamping (a legacy session reopened twice before its stamps are
|
|
43
|
+
// saved keeps them; two compactions of one transcript agree). The content
|
|
44
|
+
// part is bounded — its length and edges, media by kind and size — so
|
|
45
|
+
// stamping never serializes whole images or documents. The position keeps
|
|
46
|
+
// equal messages apart.
|
|
47
|
+
const SEED_EDGE_CHARS = 512;
|
|
48
|
+
function boundedText(value) {
|
|
49
|
+
const text = String(value);
|
|
50
|
+
if (text.length <= SEED_EDGE_CHARS * 2) return text;
|
|
51
|
+
return `${text.slice(0, SEED_EDGE_CHARS)}\u0000${text.length}\u0000${text.slice(-SEED_EDGE_CHARS)}`;
|
|
52
|
+
}
|
|
53
|
+
function boundedContent(content) {
|
|
54
|
+
if (typeof content === 'string') return boundedText(content);
|
|
55
|
+
if (!Array.isArray(content)) return content == null ? '' : typeof content;
|
|
56
|
+
return content
|
|
57
|
+
.map((part) => {
|
|
58
|
+
if (!part || typeof part !== 'object') return String(part);
|
|
59
|
+
if (typeof part.text === 'string') return `t:${boundedText(part.text)}`;
|
|
60
|
+
const data = part.data ?? part.source?.data ?? part.image_url?.url ?? part.url ?? part.ref ?? '';
|
|
61
|
+
return `${part.type || 'part'}:${typeof data === 'string' ? data.length : 0}`;
|
|
62
|
+
})
|
|
63
|
+
.join('\u0001');
|
|
64
|
+
}
|
|
65
|
+
function seededMessageId(sessionId, index, message) {
|
|
66
|
+
const calls = Array.isArray(message.toolCalls) ? message.toolCalls : message.tool_calls;
|
|
67
|
+
const callIds = Array.isArray(calls) ? calls.map((call) => call?.id || '').join(',') : '';
|
|
68
|
+
const seed = [
|
|
69
|
+
sessionId,
|
|
70
|
+
index,
|
|
71
|
+
message.role,
|
|
72
|
+
message.toolCallId || message.tool_call_id || '',
|
|
73
|
+
callIds,
|
|
74
|
+
boundedContent(message.content),
|
|
75
|
+
].join('\u0002');
|
|
76
|
+
return `msg_${createHash('sha1').update(seed).digest('hex').slice(0, 32)}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Stamp a stable id on every message that can carry one and has none, in
|
|
80
|
+
* place. Returns how many were stamped (the caller persists them). */
|
|
81
|
+
export function stampMessageIds(messages, { sessionId = '' } = {}) {
|
|
82
|
+
if (!Array.isArray(messages)) return 0;
|
|
83
|
+
let stamped = 0;
|
|
84
|
+
const seen = new Set();
|
|
85
|
+
messages.forEach((message, index) => {
|
|
86
|
+
const existing = messageIdOf(message);
|
|
87
|
+
if (existing) {
|
|
88
|
+
seen.add(existing);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!canCarryMessageId(message)) return;
|
|
92
|
+
let id = seededMessageId(String(sessionId || ''), index, message);
|
|
93
|
+
// Never two messages with one id within a transcript.
|
|
94
|
+
for (let salt = 1; seen.has(id); salt += 1) id = seededMessageId(`${sessionId}#${salt}`, index, message);
|
|
95
|
+
seen.add(id);
|
|
96
|
+
message.meta = withMessageId(message.meta, id);
|
|
97
|
+
stamped += 1;
|
|
98
|
+
});
|
|
99
|
+
return stamped;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** JSON.stringify replacer that leaves stable ids out: hashes and archives
|
|
103
|
+
* the compactor derives from message JSON describe the conversation, not
|
|
104
|
+
* its display bookkeeping (a meta that only held the id is left out too). */
|
|
105
|
+
export function withoutMessageIdsReplacer(key, value) {
|
|
106
|
+
if (key !== 'meta' || !value || typeof value !== 'object' || typeof value.transcript?.messageId !== 'string') {
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
const { messageId: _id, ...transcript } = value.transcript;
|
|
110
|
+
const meta = { ...value };
|
|
111
|
+
if (Object.keys(transcript).length) meta.transcript = transcript;
|
|
112
|
+
else delete meta.transcript;
|
|
113
|
+
return Object.keys(meta).length ? meta : undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** `copy` carrying the stable id of `original` (a compaction rewrite that
|
|
117
|
+
* builds a new message instead of spreading the original). Only the id
|
|
118
|
+
* travels; the copy's own meta is kept. */
|
|
119
|
+
export function carryMessageId(copy, original) {
|
|
120
|
+
const id = messageIdOf(original);
|
|
121
|
+
if (!id || !canCarryMessageId(copy) || messageIdOf(copy) === id) return copy;
|
|
122
|
+
return { ...copy, meta: withMessageId(copy.meta, id) };
|
|
123
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Tests that compare a transcript before and after a compaction: compaction
|
|
2
|
+
// stamps stable ids (message-ids.mjs) on messages that had none, which is
|
|
3
|
+
// bookkeeping, not a change of the conversation.
|
|
4
|
+
|
|
5
|
+
/** `messages` without the stable ids stamped on them (and without the meta a
|
|
6
|
+
* stamp alone created). */
|
|
7
|
+
export function withoutStampedIds(messages) {
|
|
8
|
+
return messages.map((message) => {
|
|
9
|
+
const transcript = message?.meta?.transcript;
|
|
10
|
+
if (!transcript?.messageId) return message;
|
|
11
|
+
const { messageId: _id, ...rest } = transcript;
|
|
12
|
+
const meta = { ...message.meta };
|
|
13
|
+
if (Object.keys(rest).length) meta.transcript = rest;
|
|
14
|
+
else delete meta.transcript;
|
|
15
|
+
const out = { ...message };
|
|
16
|
+
if (Object.keys(meta).length) out.meta = meta;
|
|
17
|
+
else delete out.meta;
|
|
18
|
+
return out;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Whether every message that can carry an id has one. */
|
|
23
|
+
export function everyMessageStamped(messages) {
|
|
24
|
+
return messages.every(
|
|
25
|
+
(message) =>
|
|
26
|
+
(message?.meta !== undefined && (typeof message.meta !== 'object' || message.meta === null)) ||
|
|
27
|
+
typeof message?.meta?.transcript?.messageId === 'string'
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -29,20 +29,14 @@ import { traceAgentCompact, messagePrefixHash } from '../agent-trace.mjs';
|
|
|
29
29
|
import { invalidateProviderRequestToolsScope } from '../runtime-core/provider-request-tools.mjs';
|
|
30
30
|
import { bumpUsageMetricsEpoch } from './manager/usage-metrics.mjs';
|
|
31
31
|
import { resetReadStateAfterCompaction } from './read-dedup.mjs';
|
|
32
|
+
import { scheduleCompactionArchive, stampCompactionMessageIds } from './compaction-archive-capture.mjs';
|
|
33
|
+
import { writeStderrBestEffort } from '../../../shared/best-effort-stderr.mjs';
|
|
32
34
|
import {
|
|
33
35
|
acknowledgePendingGoalReminder,
|
|
34
36
|
markPendingGoalReminder,
|
|
35
37
|
snapshotPendingGoalReminder,
|
|
36
38
|
} from '../runtime-core/goal-reminder.mjs';
|
|
37
39
|
|
|
38
|
-
function writeStderr(line) {
|
|
39
|
-
try {
|
|
40
|
-
process.stderr.write(line);
|
|
41
|
-
} catch {
|
|
42
|
-
/* best-effort */
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
40
|
async function setStage(opts, stage) {
|
|
47
41
|
try {
|
|
48
42
|
await opts.onStageChange?.(stage);
|
|
@@ -126,20 +120,23 @@ function preSendCompactDecision(state, compactPolicy) {
|
|
|
126
120
|
activeTurn: true,
|
|
127
121
|
}).reducesTokens;
|
|
128
122
|
}
|
|
123
|
+
let compactTrigger = 'auto';
|
|
124
|
+
if (reactivePending) compactTrigger = 'reactive';
|
|
125
|
+
else if (cacheExpired) compactTrigger = 'cache_expired';
|
|
129
126
|
return {
|
|
130
127
|
messageTokensEst,
|
|
131
128
|
pressureTokens,
|
|
132
129
|
shouldCompact,
|
|
133
130
|
requireReduction,
|
|
134
|
-
compactTrigger
|
|
131
|
+
compactTrigger,
|
|
135
132
|
compactBudgetTokens,
|
|
136
133
|
};
|
|
137
134
|
}
|
|
138
135
|
|
|
139
|
-
// Gauge sync
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
136
|
+
// Gauge sync. The host refreshes its context readout only on provider usage
|
|
137
|
+
// deltas and turn end, so a tool batch that pushed the transcript over the
|
|
138
|
+
// trigger would compact while the screen still showed headroom. Publish the
|
|
139
|
+
// decision's OWN numerator here —
|
|
143
140
|
// before compaction mutates the transcript — so the readout and the trigger
|
|
144
141
|
// describe one moment.
|
|
145
142
|
function publishContextPressure(opts, sessionId, compactPolicy, { pressureTokens, shouldCompact }) {
|
|
@@ -158,15 +155,14 @@ function publishContextPressure(opts, sessionId, compactPolicy, { pressureTokens
|
|
|
158
155
|
}
|
|
159
156
|
|
|
160
157
|
// Pre/PostCompact bridge to the standard hook bus: a session-property hook
|
|
161
|
-
// (manager/loop have no bus access)
|
|
162
|
-
// 'auto'
|
|
163
|
-
// loop.
|
|
158
|
+
// (manager/loop have no bus access); a pre-send pass is always trigger
|
|
159
|
+
// 'auto'. Best-effort — a hook must never break compaction or the loop.
|
|
164
160
|
async function runCompactHook(ctx, name) {
|
|
165
161
|
const { opts, sessionRef, sessionId, cwd } = ctx.state;
|
|
166
162
|
const hook = typeof opts[name] === 'function' ? opts[name] : sessionRef?.[name];
|
|
167
163
|
if (typeof hook !== 'function') return;
|
|
168
164
|
try {
|
|
169
|
-
await hook({ sessionId, cwd, trigger:
|
|
165
|
+
await hook({ sessionId, cwd, trigger: 'auto' });
|
|
170
166
|
} catch {
|
|
171
167
|
/* best-effort */
|
|
172
168
|
}
|
|
@@ -342,7 +338,7 @@ function throwCompactFailure(ctx, run, compactErr) {
|
|
|
342
338
|
const { sessionRef, sessionId, signal, model } = state;
|
|
343
339
|
acknowledgeGoalReminder(sessionRef, run);
|
|
344
340
|
if (isCompactAbort(signal, compactErr)) {
|
|
345
|
-
|
|
341
|
+
writeStderrBestEffort(
|
|
346
342
|
`[loop] pre-send compact cancelled (sess=${sessionId || 'unknown'}): ${compactErr?.message || compactErr}\n`
|
|
347
343
|
);
|
|
348
344
|
throw compactErr;
|
|
@@ -381,8 +377,16 @@ function throwCompactFailure(ctx, run, compactErr) {
|
|
|
381
377
|
function adoptCompactedTranscript(ctx, run) {
|
|
382
378
|
const { state } = ctx;
|
|
383
379
|
const { messages, sessionRef, sessionId, opts } = state;
|
|
380
|
+
const replaced = messages.slice();
|
|
384
381
|
messages.length = 0;
|
|
385
382
|
messages.push(...run.compacted);
|
|
383
|
+
// Display-only archive of the dropped conversation; the projection and the
|
|
384
|
+
// write run off this synchronous adoption.
|
|
385
|
+
const archived = sessionRef
|
|
386
|
+
? scheduleCompactionArchive({ session: sessionRef, sessionId, before: replaced, after: messages })
|
|
387
|
+
: null;
|
|
388
|
+
ctx.droppedMessages = archived?.dropped ?? 0;
|
|
389
|
+
ctx.droppedIdentities = archived?.identities ?? null;
|
|
386
390
|
acknowledgeGoalReminder(sessionRef, run);
|
|
387
391
|
resetReadStateAfterCompaction(sessionId);
|
|
388
392
|
// Cross-turn receipts reference tool results that compaction just removed
|
|
@@ -451,6 +455,8 @@ function reportCompactOutcome(ctx, run) {
|
|
|
451
455
|
status: changed ? 'compacted' : 'no_change',
|
|
452
456
|
afterTokens,
|
|
453
457
|
changed,
|
|
458
|
+
droppedMessages: ctx.droppedMessages || 0,
|
|
459
|
+
droppedIdentities: ctx.droppedIdentities || null,
|
|
454
460
|
freshContext: freshContextResult?.freshContext === true,
|
|
455
461
|
handoffSource: freshContextResult?.handoffSource || null,
|
|
456
462
|
durationMs,
|
|
@@ -460,6 +466,10 @@ function reportCompactOutcome(ctx, run) {
|
|
|
460
466
|
async function compactBeforeSend(ctx) {
|
|
461
467
|
const { state, decision } = ctx;
|
|
462
468
|
const { messages, sessionRef, sessionId, opts } = state;
|
|
469
|
+
// Every message enters the compaction with a stable id (legacy ones are
|
|
470
|
+
// stamped now; the turn's saves persist them): the result's rewrites carry
|
|
471
|
+
// it, so what was dropped is exactly the ids the result no longer holds.
|
|
472
|
+
stampCompactionMessageIds(messages, sessionId || sessionRef?.id || '');
|
|
463
473
|
// Snapshot BEFORE mutating the live array below, so compact_meta reports
|
|
464
474
|
// real savings and the true input prefix rather than the replacement
|
|
465
475
|
// transcript.
|
|
@@ -489,7 +499,7 @@ async function compactBeforeSend(ctx) {
|
|
|
489
499
|
await compactTranscript(ctx, run);
|
|
490
500
|
} catch (freshErr) {
|
|
491
501
|
run.freshContextError = freshErr;
|
|
492
|
-
|
|
502
|
+
writeStderrBestEffort(
|
|
493
503
|
`[loop] fresh-context compact failed (sess=${sessionId || 'unknown'}): ${freshErr?.message || freshErr}\n`
|
|
494
504
|
);
|
|
495
505
|
throw freshErr;
|
|
@@ -31,6 +31,7 @@ import { _uncacheSessionSummary, _queueSessionSummaryRemoval } from './summary-c
|
|
|
31
31
|
import { _savePending, _clearDebounce, _releasePendingSlot } from './pending-saves.mjs';
|
|
32
32
|
import { _runtimeLivenessVeto, _heartbeatLivenessVeto, _deleteHeartbeatUnlessNewer } from './liveness-veto.mjs';
|
|
33
33
|
import { _runSessionPurgeHooks } from './session-observers.mjs';
|
|
34
|
+
import { deleteCompactionArchive } from '../compaction-archive.mjs';
|
|
34
35
|
|
|
35
36
|
/** Strictly enumerate child-agent session files linked to one visible parent.
|
|
36
37
|
* Used only by explicit parent deletion; ordinary close/context switches keep
|
|
@@ -130,6 +131,8 @@ export function deleteSession(id, options = {}) {
|
|
|
130
131
|
// Preserve a sidecar published strictly after the sweep's scan snapshot.
|
|
131
132
|
_deleteHeartbeatUnlessNewer(id, options);
|
|
132
133
|
deleteSessionPresence(id);
|
|
134
|
+
// The display-only compaction archive has no meaning without its session.
|
|
135
|
+
deleteCompactionArchive(getStoreDir(), id);
|
|
133
136
|
_clearLiveSession(id);
|
|
134
137
|
// The file is gone: every marker of the retired incarnation goes with it
|
|
135
138
|
// (save error + failed-snapshot evidence, split-brain drop flag, lifecycle
|
|
@@ -6,6 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { probePath, PROBE_PRESENT, PROBE_ABSENT } from '../fs-probe.mjs';
|
|
7
7
|
import { _queueSummaryIndexPrune } from '../summary-cache.mjs';
|
|
8
8
|
import { deleteSession } from '../session-delete.mjs';
|
|
9
|
+
import { COMPACTION_ARCHIVE_SUFFIX } from '../../compaction-archive.mjs';
|
|
9
10
|
|
|
10
11
|
/** Retention cap: prune resumable open (non-tombstone) sessions newest-first
|
|
11
12
|
* — keep the most recent openMaxCount, prune anything older than openMaxAgeMs
|
|
@@ -47,18 +48,24 @@ export function pruneOpenCandidates({ plan, now, tally }) {
|
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
/** Orphan .hb/.own reap: a
|
|
51
|
+
/** Orphan .hb/.own/compaction-archive reap: a sidecar whose .json no longer
|
|
51
52
|
* exists is dead weight once it is also stale (older than maxAge) — the
|
|
52
53
|
* session JSON was swept/closed but the sidecar lingered (crashed owner or
|
|
53
54
|
* pre-fix orphan). The staleness gate avoids nuking the sidecar of a session
|
|
54
55
|
* mid-create whose .json write has not landed yet. Yields between sidecars
|
|
55
56
|
* like the row loop. */
|
|
56
57
|
export function* reapOrphanSidecars({ plan, dir, now, tally }) {
|
|
58
|
+
const recordOf = (sidecar) =>
|
|
59
|
+
sidecar.endsWith(COMPACTION_ARCHIVE_SUFFIX)
|
|
60
|
+
? `${sidecar.slice(0, -COMPACTION_ARCHIVE_SUFFIX.length)}.json`
|
|
61
|
+
: sidecar.replace(/\.(hb|own)$/, '.json');
|
|
57
62
|
try {
|
|
58
|
-
for (const h of readdirSync(dir).filter(
|
|
63
|
+
for (const h of readdirSync(dir).filter(
|
|
64
|
+
(f) => f.endsWith('.hb') || f.endsWith('.own') || f.endsWith(COMPACTION_ARCHIVE_SUFFIX)
|
|
65
|
+
)) {
|
|
59
66
|
yield undefined;
|
|
60
67
|
// Only a PROVEN-absent session file makes its sidecar an orphan.
|
|
61
|
-
if (probePath(join(dir, h
|
|
68
|
+
if (probePath(join(dir, recordOf(h))).state !== PROBE_ABSENT) continue;
|
|
62
69
|
const sidecarProbe = probePath(join(dir, h));
|
|
63
70
|
if (sidecarProbe.state !== PROBE_PRESENT) continue;
|
|
64
71
|
if (now - sidecarProbe.mtimeMs > plan.maxAge) {
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
storedLeadWorkerIndexPath,
|
|
28
28
|
} from './store-summary-locations.mjs';
|
|
29
29
|
import { cleanValue, isStoredSessionId, positiveNumber } from './store-summary-fields.mjs';
|
|
30
|
+
import { sessionUsageTotals } from '../../../shared/session-usage-totals.mjs';
|
|
30
31
|
|
|
31
32
|
const DEAD_AGENT_STATUS =
|
|
32
33
|
/^(?:done|complete|completed|success|closed|error|fail|failed|cancelled|canceled|killed|timeout)$/i;
|
|
@@ -223,6 +224,7 @@ const WORKER_HEADER_FIELDS = [
|
|
|
223
224
|
'model',
|
|
224
225
|
'effort',
|
|
225
226
|
'fast',
|
|
227
|
+
'autoEffort',
|
|
226
228
|
'createdAt',
|
|
227
229
|
'updatedAt',
|
|
228
230
|
'cwd',
|
|
@@ -234,6 +236,9 @@ const WORKER_HEADER_FIELDS = [
|
|
|
234
236
|
'startedAt',
|
|
235
237
|
'cancelledAt',
|
|
236
238
|
'finishedAt',
|
|
239
|
+
'totalInputTokens',
|
|
240
|
+
'totalOutputTokens',
|
|
241
|
+
'totalToolCalls',
|
|
237
242
|
...SESSION_CANCEL_FIELDS,
|
|
238
243
|
];
|
|
239
244
|
|
|
@@ -321,6 +326,7 @@ function projectChildWorkerRow(row, { now, heartbeatMtimes }) {
|
|
|
321
326
|
model: cleanValue(row.model || session?.model) || null,
|
|
322
327
|
effort: cleanValue(row.effort || session?.effort) || null,
|
|
323
328
|
fast: row.fast === true || session?.fast === true,
|
|
329
|
+
...(row.autoEffort === false || session?.autoEffort === false ? { autoEffort: false } : {}),
|
|
324
330
|
status: declaredWorking && !working ? 'unknown' : declaredStatus,
|
|
325
331
|
stage: declaredWorking && !working ? 'unknown' : declaredStage,
|
|
326
332
|
// A live turn blocked on the user's approval (persisted by the agent
|
|
@@ -335,6 +341,7 @@ function projectChildWorkerRow(row, { now, heartbeatMtimes }) {
|
|
|
335
341
|
cwd: cleanValue(row.cwd || session?.cwd) || null,
|
|
336
342
|
clientHostPid: positiveNumber(row.clientHostPid || session?.clientHostPid, 0) || null,
|
|
337
343
|
taskId: cleanValue(row.task_id || row.taskId) || null,
|
|
344
|
+
...sessionUsageTotals(session),
|
|
338
345
|
};
|
|
339
346
|
}
|
|
340
347
|
|
|
@@ -356,6 +363,7 @@ function sidecarWorkerRow(
|
|
|
356
363
|
model: cleanValue(session?.model) || current.model || null,
|
|
357
364
|
effort: cleanValue(session?.effort) || current.effort || null,
|
|
358
365
|
fast: session?.fast === true || current.fast === true,
|
|
366
|
+
...(session?.autoEffort === false || current.autoEffort === false ? { autoEffort: false } : {}),
|
|
359
367
|
status,
|
|
360
368
|
stage: status,
|
|
361
369
|
startedAt: session?.createdAt || current.startedAt || heartbeatAt,
|
|
@@ -365,6 +373,7 @@ function sidecarWorkerRow(
|
|
|
365
373
|
cwd: cleanValue(session?.cwd) || current.cwd || null,
|
|
366
374
|
clientHostPid: positiveNumber(session?.clientHostPid, 0) || current.clientHostPid || null,
|
|
367
375
|
taskId: cleanValue(session?.task_id || session?.taskId) || current.taskId || null,
|
|
376
|
+
...sessionUsageTotals(session),
|
|
368
377
|
};
|
|
369
378
|
}
|
|
370
379
|
|
|
@@ -458,6 +467,7 @@ function projectLeadWorkerRow(row, { now, heartbeatMtimes }) {
|
|
|
458
467
|
model: cleanValue(row.model) || null,
|
|
459
468
|
effort: cleanValue(row.effort) || null,
|
|
460
469
|
fast: row.fast === true,
|
|
470
|
+
...(row.autoEffort === false ? { autoEffort: false } : {}),
|
|
461
471
|
status: shownStatus,
|
|
462
472
|
stage: shownStatus,
|
|
463
473
|
startedAt: row.startedAt || row.createdAt || null,
|
|
@@ -468,6 +478,7 @@ function projectLeadWorkerRow(row, { now, heartbeatMtimes }) {
|
|
|
468
478
|
cwd: cleanValue(row.cwd) || null,
|
|
469
479
|
clientHostPid: positiveNumber(row.clientHostPid, 0) || null,
|
|
470
480
|
taskId: cleanValue(row.task_id || row.taskId) || null,
|
|
481
|
+
...sessionUsageTotals(readWorkerSessionHeader(sessionId)),
|
|
471
482
|
};
|
|
472
483
|
}
|
|
473
484
|
|
|
@@ -19,6 +19,35 @@ export function cleanValue(value) {
|
|
|
19
19
|
return String(value || '').trim();
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
const textOrNull = (value) => cleanValue(typeof value === 'string' ? value : '') || null;
|
|
23
|
+
const TRANSITION_STATES = new Set(['idle', 'provisioning', 'switching', 'removing', 'recovery']);
|
|
24
|
+
|
|
25
|
+
/** Catalog view of a session workspace record (the SessionWorkspace shape of
|
|
26
|
+
* runtime/shared/worktrees/contract.mjs): where it executes (`cwd`, `branch`,
|
|
27
|
+
* `worktreeId`) under which registered Project (`projectPath`), at which
|
|
28
|
+
* `revision`. Null when the record is absent or lacks either path, so
|
|
29
|
+
* consumers group by `projectPath` and never mistake a worktree `cwd` for
|
|
30
|
+
* the project. */
|
|
31
|
+
export function workspaceSummary(value) {
|
|
32
|
+
if (!value || typeof value !== 'object') return null;
|
|
33
|
+
const projectPath = cleanValue(typeof value.projectPath === 'string' ? value.projectPath : '');
|
|
34
|
+
const cwd = cleanValue(typeof value.cwd === 'string' ? value.cwd : '');
|
|
35
|
+
if (!projectPath || !cwd || projectPath.includes('\0') || cwd.includes('\0')) return null;
|
|
36
|
+
const state = TRANSITION_STATES.has(value.transition?.state) ? value.transition.state : 'idle';
|
|
37
|
+
const txId = state === 'idle' ? null : textOrNull(value.transition?.txId);
|
|
38
|
+
return {
|
|
39
|
+
projectPath,
|
|
40
|
+
repoCommonDir: textOrNull(value.repoCommonDir),
|
|
41
|
+
cwd,
|
|
42
|
+
worktreeId: textOrNull(value.worktreeId),
|
|
43
|
+
branch: textOrNull(value.branch),
|
|
44
|
+
baseCommit: textOrNull(value.baseCommit),
|
|
45
|
+
mergeTarget: textOrNull(value.mergeTarget),
|
|
46
|
+
revision: Number.isSafeInteger(value.revision) && value.revision >= 0 ? value.revision : 0,
|
|
47
|
+
transition: txId ? { state, txId } : { state: 'idle', txId: null },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
22
51
|
export function desktopSession(value, cwd = '') {
|
|
23
52
|
if (!value || typeof value !== 'object') return null;
|
|
24
53
|
if (value.classification === 'task') return { classification: 'task', projectPath: null };
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
sessionMessageText,
|
|
27
27
|
} from '../runtime-core/session-text.mjs';
|
|
28
28
|
import { sessionVisibility } from './store-summary-visibility.mjs';
|
|
29
|
-
import { isStoredSessionId, positiveNumber as _positiveNumber } from './store-summary-fields.mjs';
|
|
29
|
+
import { isStoredSessionId, positiveNumber as _positiveNumber, workspaceSummary } from './store-summary-fields.mjs';
|
|
30
30
|
|
|
31
31
|
export const SESSION_SUMMARY_INDEX_VERSION = 2;
|
|
32
32
|
|
|
@@ -182,11 +182,13 @@ export function _sessionSummary(session) {
|
|
|
182
182
|
clientHostPid: _positiveNumber(session.clientHostPid, 0) || null,
|
|
183
183
|
cwd: session.cwd || '',
|
|
184
184
|
desktopSession: _desktopSessionSummary(session.desktopSession, session.cwd),
|
|
185
|
+
...(workspaceSummary(session.workspace) ? { workspace: workspaceSummary(session.workspace) } : {}),
|
|
185
186
|
provider: session.provider || null,
|
|
186
187
|
model: session.model || null,
|
|
187
188
|
presetName: session.presetName || session.profileId || null,
|
|
188
189
|
effort: session.effort || null,
|
|
189
190
|
fast: session.fast === true,
|
|
191
|
+
...(session.autoEffort === false ? { autoEffort: false } : {}),
|
|
190
192
|
modelParameters:
|
|
191
193
|
session.modelParameters && typeof session.modelParameters === 'object' ? { ...session.modelParameters } : null,
|
|
192
194
|
agentTag: session.agentTag || null,
|
|
@@ -231,11 +233,13 @@ function _normalizeSummaryRow(row) {
|
|
|
231
233
|
clientHostPid: _positiveNumber(row.clientHostPid, 0) || null,
|
|
232
234
|
cwd: row.cwd || '',
|
|
233
235
|
desktopSession: _desktopSessionSummary(row.desktopSession, row.cwd),
|
|
236
|
+
...(workspaceSummary(row.workspace) ? { workspace: workspaceSummary(row.workspace) } : {}),
|
|
234
237
|
provider: row.provider || null,
|
|
235
238
|
model: row.model || null,
|
|
236
239
|
presetName: row.presetName || row.profileId || null,
|
|
237
240
|
effort: row.effort || null,
|
|
238
241
|
fast: row.fast === true,
|
|
242
|
+
...(row.autoEffort === false ? { autoEffort: false } : {}),
|
|
239
243
|
modelParameters: row.modelParameters && typeof row.modelParameters === 'object' ? { ...row.modelParameters } : null,
|
|
240
244
|
agentTag: row.agentTag || null,
|
|
241
245
|
task_id: row.task_id || null,
|
|
@@ -24,8 +24,9 @@ import {
|
|
|
24
24
|
} from './store-transcript-worker.mjs';
|
|
25
25
|
import { projectStoredTranscript } from './store-transcript-projection.mjs';
|
|
26
26
|
import { dataDir, sessionHeartbeatMtimes } from './store-summary-locations.mjs';
|
|
27
|
-
import { desktopSession, isStoredSessionId, positiveNumber } from './store-summary-fields.mjs';
|
|
27
|
+
import { desktopSession, isStoredSessionId, positiveNumber, workspaceSummary } from './store-summary-fields.mjs';
|
|
28
28
|
import { readArchivedAgentResult } from './store-summary-archived-agent.mjs';
|
|
29
|
+
import { compactionArchivePath, readCompactionArchivePage } from './compaction-archive.mjs';
|
|
29
30
|
import {
|
|
30
31
|
readCanonicalSessionRecord as readCanonicalLifecycle,
|
|
31
32
|
offerStrictSessionRecord,
|
|
@@ -132,6 +133,8 @@ function isLeadVisibleRow(row) {
|
|
|
132
133
|
const sourceType = String(row.sourceType || '')
|
|
133
134
|
.trim()
|
|
134
135
|
.toLowerCase();
|
|
136
|
+
// Mirror listLeadSessions: one-shot `mixdog exec` runs stay out of Recent.
|
|
137
|
+
if (sourceType === 'exec') return false;
|
|
135
138
|
const sourceName = String(row.sourceName || '')
|
|
136
139
|
.trim()
|
|
137
140
|
.toLowerCase();
|
|
@@ -200,6 +203,7 @@ function normalizedRow(row, heartbeatAt = 0) {
|
|
|
200
203
|
clientHostPid: positiveNumber(row.clientHostPid, 0) || null,
|
|
201
204
|
cwd: row.cwd || '',
|
|
202
205
|
desktopSession: desktopSession(row.desktopSession, row.cwd),
|
|
206
|
+
...(workspaceSummary(row.workspace) ? { workspace: workspaceSummary(row.workspace) } : {}),
|
|
203
207
|
provider: row.provider || null,
|
|
204
208
|
model: row.model || null,
|
|
205
209
|
agentTag: row.agentTag || null,
|
|
@@ -426,11 +430,13 @@ function storedSessionMetadata(sessionId, session) {
|
|
|
426
430
|
visibility: sessionVisibility(session),
|
|
427
431
|
agentTag: session.agentTag || null,
|
|
428
432
|
cwd: session.cwd || '',
|
|
433
|
+
...(workspaceSummary(session.workspace) ? { workspace: workspaceSummary(session.workspace) } : {}),
|
|
429
434
|
provider: session.provider || null,
|
|
430
435
|
model: session.model || null,
|
|
431
436
|
presetName: session.presetName || session.profileId || null,
|
|
432
437
|
effort: session.effort || null,
|
|
433
438
|
fast: session.fast === true,
|
|
439
|
+
...(session.autoEffort === false ? { autoEffort: false } : {}),
|
|
434
440
|
modelParameters: session.modelParameters || null,
|
|
435
441
|
taskType: session.taskType || null,
|
|
436
442
|
permission: session.permission || null,
|
|
@@ -452,6 +458,9 @@ function storedTranscriptInputPaths(sessionId) {
|
|
|
452
458
|
return {
|
|
453
459
|
recordPath: join(dataDir(), 'sessions', `${sessionId}.json`),
|
|
454
460
|
checkpointPath: join(dataDir(), 'turn-checkpoints', `${sessionId}.json`),
|
|
461
|
+
// Written after the compaction commit that names it, so its identity
|
|
462
|
+
// joins the projection's.
|
|
463
|
+
archivePath: compactionArchivePath(join(dataDir(), 'sessions'), sessionId),
|
|
455
464
|
};
|
|
456
465
|
}
|
|
457
466
|
|
|
@@ -464,7 +473,7 @@ const STAMP_ABSENT_CODES = new Set(['ENOENT', 'ENOTDIR']);
|
|
|
464
473
|
export function storedSessionTranscriptStamp(id) {
|
|
465
474
|
const sessionId = String(id || '').trim();
|
|
466
475
|
if (!isStoredSessionId(sessionId)) return null;
|
|
467
|
-
const { recordPath, checkpointPath } = storedTranscriptInputPaths(sessionId);
|
|
476
|
+
const { recordPath, checkpointPath, archivePath } = storedTranscriptInputPaths(sessionId);
|
|
468
477
|
const settled = (path, absent) => {
|
|
469
478
|
let stamp;
|
|
470
479
|
try {
|
|
@@ -477,7 +486,8 @@ export function storedSessionTranscriptStamp(id) {
|
|
|
477
486
|
};
|
|
478
487
|
const record = settled(recordPath, null);
|
|
479
488
|
const checkpoint = record && settled(checkpointPath, 'absent');
|
|
480
|
-
|
|
489
|
+
const archive = checkpoint && settled(archivePath, 'absent');
|
|
490
|
+
return archive ? `${record}|${checkpoint}|${archive}` : null;
|
|
481
491
|
}
|
|
482
492
|
|
|
483
493
|
/** Read exactly one persisted session for a visible desktop pane. This never
|
|
@@ -489,8 +499,13 @@ export async function readStoredSessionTranscript(id, options = {}) {
|
|
|
489
499
|
if (!isStoredSessionId(sessionId)) return null;
|
|
490
500
|
// Same strict authority as the store, and the same fail-closed rule: an
|
|
491
501
|
// absent, unreadable, ambiguous or foreign record yields no transcript.
|
|
492
|
-
const { recordPath, checkpointPath } = storedTranscriptInputPaths(sessionId);
|
|
502
|
+
const { recordPath, checkpointPath, archivePath } = storedTranscriptInputPaths(sessionId);
|
|
493
503
|
if (options.metadataOnly === true) return readStoredSessionMetadata(recordPath, sessionId);
|
|
504
|
+
// One page of the display-only compaction archive (the live head loader
|
|
505
|
+
// pages it once a runtime's in-memory rows are exhausted).
|
|
506
|
+
if (options.compactionArchive && typeof options.compactionArchive === 'object') {
|
|
507
|
+
return readCompactionArchivePage(join(dataDir(), 'sessions'), sessionId, options.compactionArchive);
|
|
508
|
+
}
|
|
494
509
|
// The checkpoint sidecar shapes the projection as much as the record does,
|
|
495
510
|
// so its identity joins the cache fingerprint. Only a PROVABLY absent
|
|
496
511
|
// checkpoint skips recovery: an unreadable probe must not silently
|
|
@@ -502,6 +517,7 @@ export async function readStoredSessionTranscript(id, options = {}) {
|
|
|
502
517
|
if (recordStat.state === PROBE_ABSENT) return readArchivedAgentResult(sessionId);
|
|
503
518
|
if (recordStat.state !== PROBE_PRESENT) return null;
|
|
504
519
|
const checkpoint = probePath(checkpointPath);
|
|
520
|
+
const archive = probePath(archivePath);
|
|
505
521
|
const requestedLimit = Number(options.transcriptItemLimit);
|
|
506
522
|
const itemLimit = Number.isFinite(requestedLimit) && requestedLimit > 0 ? requestedLimit : Number.POSITIVE_INFINITY;
|
|
507
523
|
let readState = PROBE_PRESENT;
|
|
@@ -525,6 +541,9 @@ export async function readStoredSessionTranscript(id, options = {}) {
|
|
|
525
541
|
checkpoint.size,
|
|
526
542
|
checkpoint.ino,
|
|
527
543
|
checkpoint.dev,
|
|
544
|
+
archive.state,
|
|
545
|
+
archive.mtimeMs,
|
|
546
|
+
archive.size,
|
|
528
547
|
].join(':'),
|
|
529
548
|
fileStat: recordStat,
|
|
530
549
|
loadText: async () => {
|