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
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// whole tree, but only these paths may be restored from it.
|
|
15
15
|
import { createHash } from 'node:crypto';
|
|
16
16
|
import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
17
|
-
import { join } from 'node:path';
|
|
17
|
+
import { join, resolve } from 'node:path';
|
|
18
18
|
import { cleanString as clean } from './clean.mjs';
|
|
19
19
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
20
20
|
|
|
@@ -131,20 +131,19 @@ async function readRecordFile(path) {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
/** Oldest-first sweep so a long-lived install cannot
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
sweepDone = true;
|
|
134
|
+
/** Oldest-first sweep of one record directory so a long-lived install cannot
|
|
135
|
+
* accumulate records. */
|
|
136
|
+
async function sweepDirectory(directory, ttlMs, maxFiles) {
|
|
138
137
|
try {
|
|
139
|
-
const names = await readdir(
|
|
138
|
+
const names = await readdir(directory);
|
|
140
139
|
const now = Date.now();
|
|
141
140
|
const kept = [];
|
|
142
141
|
for (const name of names) {
|
|
143
142
|
if (!name.endsWith('.json')) continue;
|
|
144
|
-
const path = join(
|
|
143
|
+
const path = join(directory, name);
|
|
145
144
|
try {
|
|
146
145
|
const info = await stat(path);
|
|
147
|
-
if (now - info.mtimeMs >
|
|
146
|
+
if (now - info.mtimeMs > ttlMs) {
|
|
148
147
|
await rm(path, { force: true });
|
|
149
148
|
continue;
|
|
150
149
|
}
|
|
@@ -154,7 +153,7 @@ async function sweepRecords() {
|
|
|
154
153
|
}
|
|
155
154
|
}
|
|
156
155
|
kept.sort((left, right) => left.mtimeMs - right.mtimeMs);
|
|
157
|
-
for (const entry of kept.slice(0, Math.max(0, kept.length -
|
|
156
|
+
for (const entry of kept.slice(0, Math.max(0, kept.length - maxFiles))) {
|
|
158
157
|
await rm(entry.path, { force: true });
|
|
159
158
|
}
|
|
160
159
|
} catch {
|
|
@@ -162,6 +161,18 @@ async function sweepRecords() {
|
|
|
162
161
|
}
|
|
163
162
|
}
|
|
164
163
|
|
|
164
|
+
async function sweepRecords() {
|
|
165
|
+
if (sweepDone) return;
|
|
166
|
+
sweepDone = true;
|
|
167
|
+
await sweepDirectory(recordRoot(), RECORD_TTL_MS, MAX_RECORD_FILES);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function writeJsonAtomically(path, payload) {
|
|
171
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
172
|
+
await writeFile(temporary, JSON.stringify(payload), 'utf8');
|
|
173
|
+
await rename(temporary, path);
|
|
174
|
+
}
|
|
175
|
+
|
|
165
176
|
/**
|
|
166
177
|
* Fold one turn into a session's record. `generation` identifies the turn, so
|
|
167
178
|
* re-recording the same turn updates it in place rather than appending.
|
|
@@ -219,9 +230,7 @@ export async function saveTurnSnapshotRecord(sessionId, turn) {
|
|
|
219
230
|
.slice(-MAX_SESSION_SCOPES)
|
|
220
231
|
.map(serializeScope),
|
|
221
232
|
};
|
|
222
|
-
|
|
223
|
-
await writeFile(temporary, JSON.stringify(payload), 'utf8');
|
|
224
|
-
await rename(temporary, path);
|
|
233
|
+
await writeJsonAtomically(path, payload);
|
|
225
234
|
return true;
|
|
226
235
|
} catch {
|
|
227
236
|
return false;
|
|
@@ -259,8 +268,333 @@ export async function loadSessionSnapshotRecords(sessionId) {
|
|
|
259
268
|
return sessionScopes(record).map(serializeScope);
|
|
260
269
|
}
|
|
261
270
|
|
|
271
|
+
// Absolute, slash-normalized and (on Windows) case-folded: equal for every
|
|
272
|
+
// spelling of one folder, so a stored root and a caller's root compare equal.
|
|
273
|
+
function matchKey(value) {
|
|
274
|
+
const text = clean(value);
|
|
275
|
+
if (!text) return '';
|
|
276
|
+
const key = resolve(text).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
277
|
+
return process.platform === 'win32' ? key.toLowerCase() : key;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Remove one worktree root from every session record: its turns and its
|
|
282
|
+
* session scope. Other roots' scopes, turns and the rest of the session history
|
|
283
|
+
* stay; a record left with nothing is deleted. Serialized with saves through
|
|
284
|
+
* the per-session chain. The per-turn change records rooted there are marked
|
|
285
|
+
* unavailable (see forgetChangeRecordsRoot). Returns `{ removed, kept }`:
|
|
286
|
+
* removed are `<recordFile>#<root>` scopes (or the record file itself once
|
|
287
|
+
* emptied) and `<changesFile>#<checkpointId>` change records; kept are files
|
|
288
|
+
* that matched but could not be rewritten.
|
|
289
|
+
*/
|
|
290
|
+
export async function forgetTurnSnapshotRoot(root) {
|
|
291
|
+
const removed = [];
|
|
292
|
+
const kept = [];
|
|
293
|
+
const target = matchKey(root);
|
|
294
|
+
if (!target) return { removed, kept };
|
|
295
|
+
let names = [];
|
|
296
|
+
try {
|
|
297
|
+
names = await readdir(recordRoot());
|
|
298
|
+
} catch {
|
|
299
|
+
return { removed, kept };
|
|
300
|
+
}
|
|
301
|
+
for (const name of names) {
|
|
302
|
+
if (!name.endsWith('.json')) continue;
|
|
303
|
+
const path = join(recordRoot(), name);
|
|
304
|
+
const sessionId = clean((await readRecordFile(path))?.sessionId);
|
|
305
|
+
if (!sessionId) continue;
|
|
306
|
+
await chain(sessionId, async () => {
|
|
307
|
+
const record = await readRecordFile(path);
|
|
308
|
+
if (!record) return;
|
|
309
|
+
const turns = record.turns.filter((turn) => matchKey(turn?.root) !== target);
|
|
310
|
+
const scopes = sessionScopes(record);
|
|
311
|
+
const remaining = scopes.filter((scope) => matchKey(scope.root) !== target);
|
|
312
|
+
if (turns.length === record.turns.length && remaining.length === scopes.length) return;
|
|
313
|
+
try {
|
|
314
|
+
if (turns.length === 0 && remaining.length === 0) {
|
|
315
|
+
await rm(path, { force: true });
|
|
316
|
+
removed.push(path);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
await writeJsonAtomically(path, { ...record, turns, sessionScopes: remaining.map(serializeScope) });
|
|
320
|
+
removed.push(`${path}#${target}`);
|
|
321
|
+
} catch {
|
|
322
|
+
kept.push(path);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
await forgetChangeRecordsRoot(target, removed, kept);
|
|
327
|
+
return { removed, kept };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Why a change-record file of a removed worktree can no longer be undone or
|
|
331
|
+
* reapplied. */
|
|
332
|
+
export const REMOVED_WORKTREE_CHANGE_REASON = 'its worktree was removed';
|
|
333
|
+
|
|
334
|
+
const unavailableChangeFile = (file) => {
|
|
335
|
+
const { patch, patchEncoding, patchOmitted, ...rest } = file;
|
|
336
|
+
return { ...rest, revertable: false, reason: REMOVED_WORKTREE_CHANGE_REASON, patch: null };
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const changeFileUnavailable = (file) =>
|
|
340
|
+
file.revertable === false && file.reason === REMOVED_WORKTREE_CHANGE_REASON && file.patch === null;
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Mark every per-turn change record rooted at `target` (a matchKey) as
|
|
344
|
+
* unavailable: the record and its file list stay, so the turn's history card
|
|
345
|
+
* still says what changed, but each file drops its patch (the removed
|
|
346
|
+
* worktree's contents) and becomes non-revertable, so no Undo or Reapply is
|
|
347
|
+
* offered. Records of other roots are untouched. Serialized with every other
|
|
348
|
+
* change-record write of the session.
|
|
349
|
+
*/
|
|
350
|
+
async function forgetChangeRecordsRoot(target, removed, kept) {
|
|
351
|
+
let names = [];
|
|
352
|
+
try {
|
|
353
|
+
names = await readdir(changesRoot());
|
|
354
|
+
} catch {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
for (const name of names) {
|
|
358
|
+
if (!name.endsWith('.json')) continue;
|
|
359
|
+
const path = join(changesRoot(), name);
|
|
360
|
+
let sessionId = '';
|
|
361
|
+
try {
|
|
362
|
+
sessionId = clean(JSON.parse(await readFile(path, 'utf8'))?.sessionId);
|
|
363
|
+
} catch {
|
|
364
|
+
// Unreadable or corrupt file: no session id, skipped below.
|
|
365
|
+
}
|
|
366
|
+
if (!sessionId || changesPath(sessionId) !== path) continue;
|
|
367
|
+
await chain(`changes:${sessionId}`, async () => {
|
|
368
|
+
const records = (await readChangesFile(sessionId))?.records;
|
|
369
|
+
if (!records) return;
|
|
370
|
+
const marked = [];
|
|
371
|
+
const next = records.map((record) => {
|
|
372
|
+
const files = Array.isArray(record?.files) ? record.files : [];
|
|
373
|
+
if (matchKey(record?.root) !== target || files.every(changeFileUnavailable)) return record;
|
|
374
|
+
marked.push(record.checkpointId);
|
|
375
|
+
return {
|
|
376
|
+
...record,
|
|
377
|
+
files: files.map(unavailableChangeFile),
|
|
378
|
+
revision: (Number(record.revision) || 0) + 1,
|
|
379
|
+
updatedAt: Date.now(),
|
|
380
|
+
};
|
|
381
|
+
});
|
|
382
|
+
if (marked.length === 0) return;
|
|
383
|
+
try {
|
|
384
|
+
await writeChangeRecords(sessionId, next);
|
|
385
|
+
removed.push(...marked.map((checkpointId) => `${path}#${checkpointId}`));
|
|
386
|
+
} catch {
|
|
387
|
+
kept.push(path);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ── Per-turn change records ─────────────────────────────────────────────────
|
|
394
|
+
//
|
|
395
|
+
// One file per session holding every retained turn's frozen net diff, keyed by
|
|
396
|
+
// checkpointId (the outer prompt id). Unlike the baseline index above these
|
|
397
|
+
// carry their own complete patches, so they need no shadow object to survive.
|
|
398
|
+
|
|
399
|
+
const CHANGES_VERSION = 1;
|
|
400
|
+
// Byte budget of one session's change records; the oldest turns go first.
|
|
401
|
+
const MAX_CHANGE_BYTES_PER_SESSION = 32 * 1024 * 1024;
|
|
402
|
+
const MAX_CHANGE_RECORD_FILES = 512;
|
|
403
|
+
const CHANGE_RECORD_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
404
|
+
let changesSweepDone = false;
|
|
405
|
+
|
|
406
|
+
function changesRoot() {
|
|
407
|
+
return join(recordRoot(), 'changes');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function changesPath(sessionId) {
|
|
411
|
+
const hash = createHash('sha256').update(sessionId).digest('hex').slice(0, 24);
|
|
412
|
+
return join(changesRoot(), `${hash}.json`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function readChangesFile(sessionId) {
|
|
416
|
+
try {
|
|
417
|
+
const parsed = JSON.parse(await readFile(changesPath(sessionId), 'utf8'));
|
|
418
|
+
if (parsed?.version !== CHANGES_VERSION || parsed.sessionId !== sessionId) return null;
|
|
419
|
+
return Array.isArray(parsed.records) ? parsed : null;
|
|
420
|
+
} catch {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** Evict the oldest turns until the session fits its byte budget; a turn that
|
|
426
|
+
* alone exceeds it drops its largest patches (those files stop being
|
|
427
|
+
* revertable) instead of the whole record. */
|
|
428
|
+
function boundChangeRecords(records, keepCheckpointId) {
|
|
429
|
+
const sizes = records.map((record) => Buffer.byteLength(JSON.stringify(record), 'utf8'));
|
|
430
|
+
let total = sizes.reduce((sum, size) => sum + size, 0);
|
|
431
|
+
for (let index = 0; total > MAX_CHANGE_BYTES_PER_SESSION && index < records.length; ) {
|
|
432
|
+
if (records[index].checkpointId === keepCheckpointId) {
|
|
433
|
+
index += 1;
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
total -= sizes[index];
|
|
437
|
+
records.splice(index, 1);
|
|
438
|
+
sizes.splice(index, 1);
|
|
439
|
+
}
|
|
440
|
+
if (total <= MAX_CHANGE_BYTES_PER_SESSION) return;
|
|
441
|
+
const kept = records.find((record) => record.checkpointId === keepCheckpointId);
|
|
442
|
+
// Replay patches and display patches share one budget, measured in UTF-8 bytes.
|
|
443
|
+
const weight = (file) =>
|
|
444
|
+
['patch', 'viewPatch'].reduce(
|
|
445
|
+
(sum, key) => sum + (typeof file[key] === 'string' ? Buffer.byteLength(file[key], 'utf8') : 0),
|
|
446
|
+
0
|
|
447
|
+
);
|
|
448
|
+
const largest = [...(kept?.files || []), ...(kept?.outside || [])]
|
|
449
|
+
.filter((file) => weight(file) > 0)
|
|
450
|
+
.sort((left, right) => weight(right) - weight(left));
|
|
451
|
+
for (const file of largest) {
|
|
452
|
+
if (total <= MAX_CHANGE_BYTES_PER_SESSION) break;
|
|
453
|
+
total -= weight(file);
|
|
454
|
+
file.patch = null;
|
|
455
|
+
delete file.patchEncoding;
|
|
456
|
+
delete file.viewPatch;
|
|
457
|
+
file.revertable = false;
|
|
458
|
+
file.reason = 'change is too large to keep for undo';
|
|
459
|
+
file.patchOmitted = true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Test seam: runs before every change-record write; a throw fails the write.
|
|
464
|
+
let changeWriteHookForTest = null;
|
|
465
|
+
|
|
466
|
+
async function writeChangeRecords(sessionId, records) {
|
|
467
|
+
await changeWriteHookForTest?.(sessionId, records);
|
|
468
|
+
changeRecordsCache.delete(sessionId);
|
|
469
|
+
await mkdir(changesRoot(), { recursive: true });
|
|
470
|
+
if (!changesSweepDone) {
|
|
471
|
+
changesSweepDone = true;
|
|
472
|
+
void sweepDirectory(changesRoot(), CHANGE_RECORD_TTL_MS, MAX_CHANGE_RECORD_FILES);
|
|
473
|
+
}
|
|
474
|
+
await writeJsonAtomically(changesPath(sessionId), { version: CHANGES_VERSION, sessionId, records });
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const changeContent = (record) =>
|
|
478
|
+
JSON.stringify([record.kind, record.root, record.agents || [], record.files || [], record.outside || []]);
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Upsert one turn's frozen changes. Every file of the new freeze is `applied`;
|
|
482
|
+
* a file the user had already undone and that the new net diff no longer
|
|
483
|
+
* contains keeps its `reverted` entry, so Reapply survives a re-seal. The
|
|
484
|
+
* revision bumps only when the record actually changed. Resolves to the
|
|
485
|
+
* stored record.
|
|
486
|
+
*/
|
|
487
|
+
export async function upsertTurnChangeRecord(sessionId, next) {
|
|
488
|
+
const id = clean(sessionId);
|
|
489
|
+
const checkpointId = clean(next?.checkpointId);
|
|
490
|
+
if (!id || !checkpointId) return null;
|
|
491
|
+
return await chain(`changes:${id}`, async () => {
|
|
492
|
+
const records = (await readChangesFile(id))?.records || [];
|
|
493
|
+
const index = records.findIndex((record) => record?.checkpointId === checkpointId);
|
|
494
|
+
const prior = index >= 0 ? records[index] : null;
|
|
495
|
+
const files = (Array.isArray(next.files) ? next.files : []).map((file) => ({ ...file, state: 'applied' }));
|
|
496
|
+
const present = new Set(files.map((file) => rootKey(file.path)));
|
|
497
|
+
for (const file of prior?.files || []) {
|
|
498
|
+
if (file.state === 'reverted' && !present.has(rootKey(file.path))) files.push(file);
|
|
499
|
+
}
|
|
500
|
+
files.sort((left, right) => left.path.localeCompare(right.path));
|
|
501
|
+
const candidate = {
|
|
502
|
+
checkpointId,
|
|
503
|
+
kind: clean(next.kind) || 'git',
|
|
504
|
+
root: clean(next.root),
|
|
505
|
+
agents: Array.isArray(next.agents) ? next.agents : [],
|
|
506
|
+
files,
|
|
507
|
+
// View-only edits outside the root of a Git turn: never undone or
|
|
508
|
+
// replayed, listed only by the session review.
|
|
509
|
+
...(Array.isArray(next.outside) && next.outside.length ? { outside: next.outside } : {}),
|
|
510
|
+
};
|
|
511
|
+
if (prior && changeContent(prior) === changeContent(candidate)) return prior;
|
|
512
|
+
const now = Date.now();
|
|
513
|
+
const record = {
|
|
514
|
+
...candidate,
|
|
515
|
+
revision: (Number(prior?.revision) || 0) + 1,
|
|
516
|
+
createdAt: Number(prior?.createdAt) || now,
|
|
517
|
+
updatedAt: now,
|
|
518
|
+
};
|
|
519
|
+
if (index >= 0) records[index] = record;
|
|
520
|
+
else records.push(record);
|
|
521
|
+
boundChangeRecords(records, checkpointId);
|
|
522
|
+
await writeChangeRecords(id, records);
|
|
523
|
+
return records.find((entry) => entry.checkpointId === checkpointId) || null;
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** Apply `mutate(record)` (returning the next files, or null for no change)
|
|
528
|
+
* and bump the revision. Serialized with every other write of the session. */
|
|
529
|
+
export async function updateTurnChangeRecord(sessionId, checkpointId, mutate) {
|
|
530
|
+
const id = clean(sessionId);
|
|
531
|
+
const key = clean(checkpointId);
|
|
532
|
+
if (!id || !key) return null;
|
|
533
|
+
return await chain(`changes:${id}`, async () => {
|
|
534
|
+
const records = (await readChangesFile(id))?.records || [];
|
|
535
|
+
const index = records.findIndex((record) => record?.checkpointId === key);
|
|
536
|
+
if (index < 0) return null;
|
|
537
|
+
const files = mutate(records[index]);
|
|
538
|
+
if (!files) return records[index];
|
|
539
|
+
records[index] = {
|
|
540
|
+
...records[index],
|
|
541
|
+
files,
|
|
542
|
+
revision: (Number(records[index].revision) || 0) + 1,
|
|
543
|
+
updatedAt: Date.now(),
|
|
544
|
+
};
|
|
545
|
+
await writeChangeRecords(id, records);
|
|
546
|
+
return records[index];
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** One turn's change record by its exact checkpoint id; never a fallback. */
|
|
551
|
+
export async function loadTurnChangeRecord(sessionId, checkpointId) {
|
|
552
|
+
const id = clean(sessionId);
|
|
553
|
+
const key = clean(checkpointId);
|
|
554
|
+
if (!id || !key) return null;
|
|
555
|
+
const records = (await readChangesFile(id))?.records || [];
|
|
556
|
+
return records.find((record) => record?.checkpointId === key) || null;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Parsed change records by session, valid while the file's mtime and size are
|
|
560
|
+
// unchanged: the session review refreshes every few seconds and must not
|
|
561
|
+
// re-read and re-parse a multi-megabyte file each time. Callers never mutate.
|
|
562
|
+
const changeRecordsCache = new Map();
|
|
563
|
+
const CHANGE_RECORDS_CACHE_SESSIONS = 8;
|
|
564
|
+
|
|
565
|
+
/** Every retained turn's change record of a session, oldest first. */
|
|
566
|
+
export async function loadTurnChangeRecords(sessionId) {
|
|
567
|
+
const id = clean(sessionId);
|
|
568
|
+
if (!id) return [];
|
|
569
|
+
let signature = '';
|
|
570
|
+
try {
|
|
571
|
+
const info = await stat(changesPath(id));
|
|
572
|
+
signature = `${info.mtimeMs}:${info.size}`;
|
|
573
|
+
} catch {
|
|
574
|
+
changeRecordsCache.delete(id);
|
|
575
|
+
return [];
|
|
576
|
+
}
|
|
577
|
+
const cached = changeRecordsCache.get(id);
|
|
578
|
+
if (cached?.signature === signature) return cached.records;
|
|
579
|
+
const records = ((await readChangesFile(id))?.records || [])
|
|
580
|
+
.filter(Boolean)
|
|
581
|
+
.sort((left, right) => (Number(left.createdAt) || 0) - (Number(right.createdAt) || 0));
|
|
582
|
+
changeRecordsCache.delete(id);
|
|
583
|
+
changeRecordsCache.set(id, { signature, records });
|
|
584
|
+
if (changeRecordsCache.size > CHANGE_RECORDS_CACHE_SESSIONS) {
|
|
585
|
+
changeRecordsCache.delete(changeRecordsCache.keys().next().value);
|
|
586
|
+
}
|
|
587
|
+
return records;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function _setTurnChangeRecordWriteHookForTest(hook) {
|
|
591
|
+
changeWriteHookForTest = typeof hook === 'function' ? hook : null;
|
|
592
|
+
}
|
|
593
|
+
|
|
262
594
|
export function _setTurnSnapshotStoreRootForTest(directory) {
|
|
263
595
|
recordRootOverride = clean(directory);
|
|
264
596
|
writeChains.clear();
|
|
597
|
+
changeRecordsCache.clear();
|
|
265
598
|
sweepDone = false;
|
|
599
|
+
changesSweepDone = false;
|
|
266
600
|
}
|