agent-nuvira 3.3.7 → 3.3.9
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 +57 -50
- package/dist/agents/agents/planner.d.ts.map +1 -1
- package/dist/agents/agents/planner.js +41 -0
- package/dist/agents/agents/planner.js.map +1 -1
- package/dist/agents/agents/reasoner.d.ts.map +1 -1
- package/dist/agents/agents/reasoner.js +6 -0
- package/dist/agents/agents/reasoner.js.map +1 -1
- package/dist/agents/agents/runner.d.ts +6 -0
- package/dist/agents/agents/runner.d.ts.map +1 -1
- package/dist/agents/agents/runner.js +82 -5
- package/dist/agents/agents/runner.js.map +1 -1
- package/dist/agents/agents/writer-tool-calling.d.ts.map +1 -1
- package/dist/agents/agents/writer-tool-calling.js +5 -2
- package/dist/agents/agents/writer-tool-calling.js.map +1 -1
- package/dist/agents/agents/writer.d.ts.map +1 -1
- package/dist/agents/agents/writer.js +8 -4
- package/dist/agents/agents/writer.js.map +1 -1
- package/dist/agents/fanout-scheduler.d.ts +72 -0
- package/dist/agents/fanout-scheduler.d.ts.map +1 -0
- package/dist/agents/fanout-scheduler.js +100 -0
- package/dist/agents/fanout-scheduler.js.map +1 -0
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +30 -1
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/agents/plan-quality.d.ts +58 -0
- package/dist/agents/plan-quality.d.ts.map +1 -0
- package/dist/agents/plan-quality.js +152 -0
- package/dist/agents/plan-quality.js.map +1 -0
- package/dist/agents/prompt-assembly.d.ts +8 -0
- package/dist/agents/prompt-assembly.d.ts.map +1 -1
- package/dist/agents/prompt-assembly.js +18 -0
- package/dist/agents/prompt-assembly.js.map +1 -1
- package/dist/agents/step-handoff.d.ts +12 -2
- package/dist/agents/step-handoff.d.ts.map +1 -1
- package/dist/agents/step-handoff.js +65 -8
- package/dist/agents/step-handoff.js.map +1 -1
- package/dist/agents/tool-calling-agent.d.ts.map +1 -1
- package/dist/agents/tool-calling-agent.js +18 -0
- package/dist/agents/tool-calling-agent.js.map +1 -1
- package/dist/cli/cache.d.ts.map +1 -1
- package/dist/cli/cache.js +34 -4
- package/dist/cli/cache.js.map +1 -1
- package/dist/cli/chat.d.ts +6 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +79 -18
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/cli-program.d.ts.map +1 -1
- package/dist/cli/cli-program.js +6 -0
- package/dist/cli/cli-program.js.map +1 -1
- package/dist/cli/config.d.ts +10 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +179 -1
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/continuity.d.ts +61 -0
- package/dist/cli/continuity.d.ts.map +1 -0
- package/dist/cli/continuity.js +112 -0
- package/dist/cli/continuity.js.map +1 -0
- package/dist/cli/dashboard.d.ts +3 -1
- package/dist/cli/dashboard.d.ts.map +1 -1
- package/dist/cli/dashboard.js +5 -16
- package/dist/cli/dashboard.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +48 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/execute.d.ts +8 -0
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +34 -1
- package/dist/cli/execute.js.map +1 -1
- package/dist/cli/loop-executor.d.ts +24 -0
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +134 -13
- package/dist/cli/loop-executor.js.map +1 -1
- package/dist/cli/website.d.ts +25 -0
- package/dist/cli/website.d.ts.map +1 -0
- package/dist/cli/website.js +58 -0
- package/dist/cli/website.js.map +1 -0
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +35 -0
- package/dist/config/manager.js.map +1 -1
- package/dist/config/process-env.d.ts.map +1 -1
- package/dist/config/process-env.js +20 -0
- package/dist/config/process-env.js.map +1 -1
- package/dist/config/provider-env.d.ts +2 -1
- package/dist/config/provider-env.d.ts.map +1 -1
- package/dist/config/provider-env.js +19 -2
- package/dist/config/provider-env.js.map +1 -1
- package/dist/config/service-catalog.d.ts +81 -0
- package/dist/config/service-catalog.d.ts.map +1 -0
- package/dist/config/service-catalog.js +286 -0
- package/dist/config/service-catalog.js.map +1 -0
- package/dist/config/service-probe.d.ts +27 -0
- package/dist/config/service-probe.d.ts.map +1 -0
- package/dist/config/service-probe.js +104 -0
- package/dist/config/service-probe.js.map +1 -0
- package/dist/config/types.d.ts +63 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/cache.d.ts +46 -2
- package/dist/context/cache.d.ts.map +1 -1
- package/dist/context/cache.js +85 -7
- package/dist/context/cache.js.map +1 -1
- package/dist/findings/verdicts.d.ts +59 -0
- package/dist/findings/verdicts.d.ts.map +1 -1
- package/dist/findings/verdicts.js +117 -0
- package/dist/findings/verdicts.js.map +1 -1
- package/dist/forwarded.d.ts +2 -0
- package/dist/forwarded.d.ts.map +1 -0
- package/dist/forwarded.js +2 -0
- package/dist/forwarded.js.map +1 -0
- package/dist/gateway/registry.d.ts.map +1 -1
- package/dist/gateway/registry.js +9 -0
- package/dist/gateway/registry.js.map +1 -1
- package/dist/learning/auto-router.d.ts +30 -0
- package/dist/learning/auto-router.d.ts.map +1 -1
- package/dist/learning/auto-router.js +69 -12
- package/dist/learning/auto-router.js.map +1 -1
- package/dist/learning/context-assembly.d.ts +37 -0
- package/dist/learning/context-assembly.d.ts.map +1 -0
- package/dist/learning/context-assembly.js +63 -0
- package/dist/learning/context-assembly.js.map +1 -0
- package/dist/learning/reasoning-trace.d.ts +26 -5
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js +21 -1
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/learning/session-digest.d.ts +78 -0
- package/dist/learning/session-digest.d.ts.map +1 -0
- package/dist/learning/session-digest.js +185 -0
- package/dist/learning/session-digest.js.map +1 -0
- package/dist/learning/session-recall.d.ts +135 -0
- package/dist/learning/session-recall.d.ts.map +1 -0
- package/dist/learning/session-recall.js +305 -0
- package/dist/learning/session-recall.js.map +1 -0
- package/dist/learning/session-store.d.ts +172 -0
- package/dist/learning/session-store.d.ts.map +1 -0
- package/dist/learning/session-store.js +424 -0
- package/dist/learning/session-store.js.map +1 -0
- package/dist/learning/step-checkpoint.d.ts +28 -0
- package/dist/learning/step-checkpoint.d.ts.map +1 -1
- package/dist/learning/step-checkpoint.js +90 -6
- package/dist/learning/step-checkpoint.js.map +1 -1
- package/dist/learning/working-state.d.ts +19 -0
- package/dist/learning/working-state.d.ts.map +1 -1
- package/dist/learning/working-state.js +34 -1
- package/dist/learning/working-state.js.map +1 -1
- package/dist/parity/drivers.d.ts.map +1 -1
- package/dist/parity/drivers.js +29 -0
- package/dist/parity/drivers.js.map +1 -1
- package/dist/parity/wire-fixtures.d.ts +88 -0
- package/dist/parity/wire-fixtures.d.ts.map +1 -0
- package/dist/parity/wire-fixtures.js +405 -0
- package/dist/parity/wire-fixtures.js.map +1 -0
- package/dist/tools/artifact-append.d.ts +23 -0
- package/dist/tools/artifact-append.d.ts.map +1 -1
- package/dist/tools/artifact-append.js +28 -0
- package/dist/tools/artifact-append.js.map +1 -1
- package/dist/tools/ask-user.d.ts +5 -0
- package/dist/tools/ask-user.d.ts.map +1 -1
- package/dist/tools/ask-user.js +51 -5
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/child-agent-entry.js +1 -0
- package/dist/tools/child-agent-entry.js.map +1 -1
- package/dist/tools/child-agent-runtime.d.ts +7 -0
- package/dist/tools/child-agent-runtime.d.ts.map +1 -1
- package/dist/tools/child-agent-runtime.js +116 -6
- package/dist/tools/child-agent-runtime.js.map +1 -1
- package/dist/tools/child-agent-worker.d.ts +15 -0
- package/dist/tools/child-agent-worker.d.ts.map +1 -0
- package/dist/tools/child-agent-worker.js.map +1 -0
- package/dist/tools/coding-tools.d.ts.map +1 -1
- package/dist/tools/coding-tools.js +11 -0
- package/dist/tools/coding-tools.js.map +1 -1
- package/dist/tools/finding-tool.d.ts +6 -1
- package/dist/tools/finding-tool.d.ts.map +1 -1
- package/dist/tools/finding-tool.js +15 -2
- package/dist/tools/finding-tool.js.map +1 -1
- package/dist/tools/followup-utils.d.ts +35 -0
- package/dist/tools/followup-utils.d.ts.map +1 -1
- package/dist/tools/followup-utils.js +36 -0
- package/dist/tools/followup-utils.js.map +1 -1
- package/dist/tools/loop-project-context.d.ts +4 -1
- package/dist/tools/loop-project-context.d.ts.map +1 -1
- package/dist/tools/loop-project-context.js +48 -1
- package/dist/tools/loop-project-context.js.map +1 -1
- package/dist/tools/loop-skill-hint.d.ts.map +1 -1
- package/dist/tools/loop-skill-hint.js +7 -0
- package/dist/tools/loop-skill-hint.js.map +1 -1
- package/dist/tools/modality/image-gen.d.ts +25 -6
- package/dist/tools/modality/image-gen.d.ts.map +1 -1
- package/dist/tools/modality/image-gen.js +46 -27
- package/dist/tools/modality/image-gen.js.map +1 -1
- package/dist/tools/modality/image-providers.d.ts +71 -0
- package/dist/tools/modality/image-providers.d.ts.map +1 -0
- package/dist/tools/modality/image-providers.js +277 -0
- package/dist/tools/modality/image-providers.js.map +1 -0
- package/dist/tools/registry.d.ts +10 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +35 -8
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run-cli.d.ts.map +1 -1
- package/dist/tools/run-cli.js +39 -1
- package/dist/tools/run-cli.js.map +1 -1
- package/dist/tools/run-terminal.d.ts.map +1 -1
- package/dist/tools/run-terminal.js +50 -6
- package/dist/tools/run-terminal.js.map +1 -1
- package/dist/tools/subagent-spawner.d.ts +8 -0
- package/dist/tools/subagent-spawner.d.ts.map +1 -1
- package/dist/tools/subagent-spawner.js +3 -0
- package/dist/tools/subagent-spawner.js.map +1 -1
- package/dist/tools/tool-loop.d.ts +55 -0
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +136 -0
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/tools/untrusted-content.d.ts +29 -0
- package/dist/tools/untrusted-content.d.ts.map +1 -0
- package/dist/tools/untrusted-content.js +53 -0
- package/dist/tools/untrusted-content.js.map +1 -0
- package/dist/tools/web-research.d.ts +76 -14
- package/dist/tools/web-research.d.ts.map +1 -1
- package/dist/tools/web-research.js +280 -35
- package/dist/tools/web-research.js.map +1 -1
- package/dist/utils/effect-verification.d.ts +115 -0
- package/dist/utils/effect-verification.d.ts.map +1 -0
- package/dist/utils/effect-verification.js +337 -0
- package/dist/utils/effect-verification.js.map +1 -0
- package/dist/utils/open-url.d.ts +16 -0
- package/dist/utils/open-url.d.ts.map +1 -0
- package/dist/utils/open-url.js +32 -0
- package/dist/utils/open-url.js.map +1 -0
- package/dist/utils/project-env.d.ts +146 -0
- package/dist/utils/project-env.d.ts.map +1 -0
- package/dist/utils/project-env.js +584 -0
- package/dist/utils/project-env.js.map +1 -0
- package/dist/utils/shell.d.ts +7 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +2 -0
- package/dist/utils/shell.js.map +1 -1
- package/dist/web-dashboard/chat-console.d.ts +2 -0
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +7 -0
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/server.d.ts +3 -1
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +534 -8
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +150 -1
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-guard.d.ts +40 -0
- package/dist/web-dashboard/workspace-guard.d.ts.map +1 -0
- package/dist/web-dashboard/workspace-guard.js +92 -0
- package/dist/web-dashboard/workspace-guard.js.map +1 -0
- package/package.json +4 -1
- package/src/web-dashboard/public/assets/index-Beportyl.js +207 -0
- package/src/web-dashboard/public/assets/index-Beportyl.js.map +1 -0
- package/src/web-dashboard/public/assets/index-gtQyg9nm.css +1 -0
- package/src/web-dashboard/public/index.html +2 -2
- package/src/web-dashboard/public/assets/index-D1YFCcIZ.js +0 -207
- package/src/web-dashboard/public/assets/index-D1YFCcIZ.js.map +0 -1
- package/src/web-dashboard/public/assets/index-jkW5qcf-.css +0 -1
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WS5 (#27) — Phase 4b/4c: the PERSISTENT SESSION STORE (the L2 layer).
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. The resume ledger (`step-checkpoint.ts`) makes a re-run cheap
|
|
5
|
+
* by replaying unchanged MODEL CALLS, and the task checkpoints
|
|
6
|
+
* (`agents/checkpoint-store.ts`) skip COMPLETED pipeline tasks. Both are keyed by
|
|
7
|
+
* what a run FINISHED. Neither survives the one failure a long-running agent
|
|
8
|
+
* actually hits: the PROCESS dies mid-turn (a closed laptop, a killed terminal,
|
|
9
|
+
* a quota-killed provider). At that instant the conversation lived only in
|
|
10
|
+
* memory, and the next process starts cold — it re-derives the whole context and
|
|
11
|
+
* re-pays for the steps that were already done.
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT STORES, AND WHAT IT REFUSES TO STORE. At every step boundary the loop
|
|
14
|
+
* hands over its live `ToolMessage[]` thread plus a small accumulator (steps,
|
|
15
|
+
* successful tools, mutated paths). The store keeps a bounded, REDACTED copy on
|
|
16
|
+
* disk. It is a transcript — nothing more. It does NOT record a completion
|
|
17
|
+
* state, an outcome, or any verdict: completion is still derived from the
|
|
18
|
+
* filesystem and the run's own facts (see the Part 1/Part 2 lesson). Re-importing
|
|
19
|
+
* the transcript gives the next process the CONVERSATION, not a claim that the
|
|
20
|
+
* work is done.
|
|
21
|
+
*
|
|
22
|
+
* WHY THE HEAD IS REPLACED ON REHYDRATION. The first messages of a loop thread
|
|
23
|
+
* are the system prompt (tool contract) and the `[Project context]` block. Those
|
|
24
|
+
* are reconstructible, and they DRIFT between runs (the file tree, the git
|
|
25
|
+
* digest). Persisting them would both age and bloat the record. So the store
|
|
26
|
+
* counts the leading head messages at save time (`headLength`) and the caller
|
|
27
|
+
* re-injects a FRESH head over the stored conversation tail on resume: the
|
|
28
|
+
* contract is current, the history is intact.
|
|
29
|
+
*
|
|
30
|
+
* WHY A COMPLETED SESSION IS NOT RESUMABLE. If a turn ran to a clean end, its
|
|
31
|
+
* transcript is history — re-feeding a finished conversation would have the model
|
|
32
|
+
* continue a conversation that is over, and risk narrating a stale "done". So
|
|
33
|
+
* `finish()` marks a snapshot closed, and the resumable lookup skips closed
|
|
34
|
+
* snapshots. The transcript is still kept (bounded) for a human to read; it is
|
|
35
|
+
* simply not replayed.
|
|
36
|
+
*
|
|
37
|
+
* WHY NOTHING HAPPENS WITHOUT A REQUEST. Opening the store is the ONLY thing that
|
|
38
|
+
* reads or writes the session directory. An ordinary run never touches it, pays
|
|
39
|
+
* no filesystem cost, and cannot inherit another run's conversation.
|
|
40
|
+
*
|
|
41
|
+
* Everything here is best-effort: a corrupt record is a MISS, never a crash, and
|
|
42
|
+
* a failed write reports (or stays silent) without breaking the turn that asked.
|
|
43
|
+
*/
|
|
44
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
45
|
+
import { join } from 'node:path';
|
|
46
|
+
import { checkpointIdFor, goalsLookSame } from '../agents/checkpoint-store.js';
|
|
47
|
+
import { envBuff, resolveNuviraHome } from '../config/paths.js';
|
|
48
|
+
import { redact } from '../enterprise/secrets.js';
|
|
49
|
+
/** The environment key a deployment uses to turn session persistence OFF. */
|
|
50
|
+
export const SESSION_ENABLE_ENV = 'NUVIRA_SESSION_STORE';
|
|
51
|
+
/** Words a reader treats as OFF (same vocabulary the process-env page uses). */
|
|
52
|
+
const OFF_WORDS = new Set(['0', 'false', 'off', 'no']);
|
|
53
|
+
/**
|
|
54
|
+
* Is session persistence on? DEFAULT ON — like checkpointing, a fresh install
|
|
55
|
+
* gets continuity and an operator can turn it off. `0`/`false`/`off`/`no` turn
|
|
56
|
+
* it off; unset (or any other value) leaves it ON.
|
|
57
|
+
*/
|
|
58
|
+
export function sessionStoreEnabled(env = process.env) {
|
|
59
|
+
const raw = env[SESSION_ENABLE_ENV];
|
|
60
|
+
if (typeof raw !== 'string')
|
|
61
|
+
return true;
|
|
62
|
+
return !OFF_WORDS.has(raw.trim().toLowerCase());
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve whether session persistence is on, with the repo's precedence: an
|
|
66
|
+
* explicit FLAG wins, then the environment, then config, then the default (ON).
|
|
67
|
+
* One place decides, so the CLI, the loop and the dashboard cannot disagree.
|
|
68
|
+
*/
|
|
69
|
+
export function resolveSessionStore(input = {}) {
|
|
70
|
+
if (input.flag !== undefined)
|
|
71
|
+
return input.flag;
|
|
72
|
+
const viaEnv = envBuff('SESSION_STORE');
|
|
73
|
+
if (typeof viaEnv === 'string')
|
|
74
|
+
return !OFF_WORDS.has(viaEnv.trim().toLowerCase());
|
|
75
|
+
return input.configManager?.getAll().memory?.sessionStore !== false;
|
|
76
|
+
}
|
|
77
|
+
/** Bound on stored conversation messages — the most recent N are kept. */
|
|
78
|
+
export const MAX_SESSION_MESSAGES = 48;
|
|
79
|
+
/** Bound on a single stored message's content (the rest is clipped). */
|
|
80
|
+
export const MAX_MESSAGE_CHARS = 8000;
|
|
81
|
+
// ─── Storage ────────────────────────────────────────────────────────────────
|
|
82
|
+
function sessionsDir() {
|
|
83
|
+
const base = envBuff('MEMORY_DIR') || join(resolveNuviraHome(), 'memory');
|
|
84
|
+
return join(base, 'sessions');
|
|
85
|
+
}
|
|
86
|
+
function snapshotPath(id) {
|
|
87
|
+
return join(sessionsDir(), `${id}.json`);
|
|
88
|
+
}
|
|
89
|
+
/** Read a snapshot, best-effort. A corrupt record is a miss, never a crash. */
|
|
90
|
+
export function loadSessionSnapshot(id) {
|
|
91
|
+
try {
|
|
92
|
+
const path = snapshotPath(id);
|
|
93
|
+
if (!existsSync(path))
|
|
94
|
+
return null;
|
|
95
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
96
|
+
if (!parsed || typeof parsed !== 'object')
|
|
97
|
+
return null;
|
|
98
|
+
if (typeof parsed.id !== 'string' || !Array.isArray(parsed.messages))
|
|
99
|
+
return null;
|
|
100
|
+
if (typeof parsed.goal !== 'string' || typeof parsed.cwd !== 'string')
|
|
101
|
+
return null;
|
|
102
|
+
return {
|
|
103
|
+
id: parsed.id,
|
|
104
|
+
goal: parsed.goal,
|
|
105
|
+
cwd: parsed.cwd,
|
|
106
|
+
savedAt: typeof parsed.savedAt === 'number' ? parsed.savedAt : 0,
|
|
107
|
+
revision: typeof parsed.revision === 'number' ? parsed.revision : 0,
|
|
108
|
+
open: parsed.open !== false,
|
|
109
|
+
headLength: typeof parsed.headLength === 'number' ? parsed.headLength : 0,
|
|
110
|
+
messages: parsed.messages.filter(isToolMessage),
|
|
111
|
+
accumulators: normalizeAccumulators(parsed.accumulators),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function isToolMessage(value) {
|
|
119
|
+
if (!value || typeof value !== 'object')
|
|
120
|
+
return false;
|
|
121
|
+
const m = value;
|
|
122
|
+
return ((m.role === 'system' || m.role === 'user' || m.role === 'assistant' || m.role === 'tool') &&
|
|
123
|
+
typeof m.content === 'string');
|
|
124
|
+
}
|
|
125
|
+
function normalizeAccumulators(value) {
|
|
126
|
+
const a = (value ?? {});
|
|
127
|
+
return {
|
|
128
|
+
steps: typeof a.steps === 'number' ? a.steps : 0,
|
|
129
|
+
successfulTools: Array.isArray(a.successfulTools) ? a.successfulTools.filter((t) => typeof t === 'string') : [],
|
|
130
|
+
mutatedPaths: Array.isArray(a.mutatedPaths) ? a.mutatedPaths.filter((t) => typeof t === 'string') : [],
|
|
131
|
+
...(Array.isArray(a.erroredTools)
|
|
132
|
+
? { erroredTools: a.erroredTools.filter((t) => typeof t === 'string') }
|
|
133
|
+
: {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** Write a snapshot, best-effort. Returns whether it reached the disk. */
|
|
137
|
+
function writeSnapshot(snapshot) {
|
|
138
|
+
try {
|
|
139
|
+
mkdirSync(sessionsDir(), { recursive: true });
|
|
140
|
+
writeFileSync(snapshotPath(snapshot.id), JSON.stringify(snapshot, null, 2), 'utf-8');
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// ─── Bounds ─────────────────────────────────────────────────────────────────
|
|
148
|
+
/**
|
|
149
|
+
* Bound and redact a thread for storage.
|
|
150
|
+
*
|
|
151
|
+
* The TAIL is kept (the last `MAX_SESSION_MESSAGES` messages): the most recent
|
|
152
|
+
* tool results and the current question are what a resuming model needs, while
|
|
153
|
+
* the opening context is reconstructible. Every content string is clipped to
|
|
154
|
+
* `MAX_MESSAGE_CHARS` and run through the shared `redact` — a transcript that
|
|
155
|
+
* leaks a key is not worth the continuity it buys.
|
|
156
|
+
*
|
|
157
|
+
* Tool-call arguments and `providerMeta` are preserved verbatim only when they
|
|
158
|
+
* survive JSON (they are structured data the provider may need) and are never
|
|
159
|
+
* trusted to be secret-free, so they are redacted through a JSON round-trip.
|
|
160
|
+
*/
|
|
161
|
+
function boundMessages(messages) {
|
|
162
|
+
const headLength = countHead(messages);
|
|
163
|
+
const sliced = messages.length > MAX_SESSION_MESSAGES ? messages.slice(-MAX_SESSION_MESSAGES) : messages.slice();
|
|
164
|
+
const bounded = sliced.map((message) => boundMessage(message));
|
|
165
|
+
// If the slice dropped the head, the stored head is gone too (the caller's
|
|
166
|
+
// `headLength` then refers to a head that is not in the record).
|
|
167
|
+
return { messages: bounded, headLength: Math.min(headLength, bounded.length) };
|
|
168
|
+
}
|
|
169
|
+
/** Leading system messages, plus a directly-following `[Project context]` block. */
|
|
170
|
+
function countHead(messages) {
|
|
171
|
+
let head = 0;
|
|
172
|
+
while (head < messages.length && messages[head].role === 'system')
|
|
173
|
+
head += 1;
|
|
174
|
+
if (head < messages.length && messages[head].role === 'user' && messages[head].content.startsWith('[Project context]')) {
|
|
175
|
+
head += 1;
|
|
176
|
+
}
|
|
177
|
+
return head;
|
|
178
|
+
}
|
|
179
|
+
function boundMessage(message) {
|
|
180
|
+
const out = {
|
|
181
|
+
role: message.role,
|
|
182
|
+
content: clip(message.content),
|
|
183
|
+
};
|
|
184
|
+
if (message.toolCallId)
|
|
185
|
+
out.toolCallId = message.toolCallId;
|
|
186
|
+
if (Array.isArray(message.toolCalls) && message.toolCalls.length > 0) {
|
|
187
|
+
out.toolCalls = message.toolCalls.map((call) => {
|
|
188
|
+
const copy = { ...call };
|
|
189
|
+
if (typeof copy.arguments === 'string')
|
|
190
|
+
copy.arguments = clip(copy.arguments);
|
|
191
|
+
return copy;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
function clip(text) {
|
|
197
|
+
if (typeof text !== 'string')
|
|
198
|
+
return '';
|
|
199
|
+
const redacted = redact(text);
|
|
200
|
+
return redacted.length > MAX_MESSAGE_CHARS ? `${redacted.slice(0, MAX_MESSAGE_CHARS)}…[clipped]` : redacted;
|
|
201
|
+
}
|
|
202
|
+
const STORE_STATES = new WeakMap();
|
|
203
|
+
/**
|
|
204
|
+
* Open (or start) the session for this ask, in this directory.
|
|
205
|
+
*
|
|
206
|
+
* An explicit `id` is honoured exactly. With none, the deterministic
|
|
207
|
+
* `checkpointIdFor(goal, cwd)` is used — the SAME key the resume ledger uses —
|
|
208
|
+
* so a reworded re-ask resolves to the same session through
|
|
209
|
+
* {@link resolveSessionIdFor} / {@link findResumableSessionFor}.
|
|
210
|
+
*/
|
|
211
|
+
/** Whether this process has already opportunistically bounded the store. */
|
|
212
|
+
let PRUNED_THIS_PROCESS = false;
|
|
213
|
+
/** Keep the store from growing without bound — run at most once per process. */
|
|
214
|
+
function pruneOnce() {
|
|
215
|
+
if (PRUNED_THIS_PROCESS)
|
|
216
|
+
return;
|
|
217
|
+
PRUNED_THIS_PROCESS = true;
|
|
218
|
+
try {
|
|
219
|
+
const entries = listSessionSnapshots();
|
|
220
|
+
if (entries.length > 100)
|
|
221
|
+
pruneSessions({ maxCount: 100 });
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// Best-effort — a prune failure must never break an open.
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
export function openSession(input) {
|
|
228
|
+
pruneOnce();
|
|
229
|
+
const id = input.id?.trim() || checkpointIdFor(input.goal, input.cwd);
|
|
230
|
+
const prior = loadSessionSnapshot(id);
|
|
231
|
+
const state = {
|
|
232
|
+
id,
|
|
233
|
+
goal: input.goal,
|
|
234
|
+
cwd: input.cwd,
|
|
235
|
+
revision: prior?.revision ?? 0,
|
|
236
|
+
saved: false,
|
|
237
|
+
lastMessages: prior?.messages ?? [],
|
|
238
|
+
lastAccumulators: prior?.accumulators ?? { steps: 0, successfulTools: [], mutatedPaths: [] },
|
|
239
|
+
};
|
|
240
|
+
const persist = (open) => {
|
|
241
|
+
const { messages, headLength } = boundMessages(state.lastMessages);
|
|
242
|
+
const snapshot = {
|
|
243
|
+
id: state.id,
|
|
244
|
+
goal: state.goal,
|
|
245
|
+
cwd: state.cwd,
|
|
246
|
+
savedAt: Date.now(),
|
|
247
|
+
revision: state.revision + 1,
|
|
248
|
+
open,
|
|
249
|
+
headLength,
|
|
250
|
+
messages,
|
|
251
|
+
accumulators: state.lastAccumulators,
|
|
252
|
+
};
|
|
253
|
+
state.revision += 1;
|
|
254
|
+
state.saved = writeSnapshot(snapshot);
|
|
255
|
+
};
|
|
256
|
+
const store = {
|
|
257
|
+
id,
|
|
258
|
+
save(thread, accumulators) {
|
|
259
|
+
state.lastMessages = thread.slice();
|
|
260
|
+
state.lastAccumulators = normalizeAccumulators(accumulators);
|
|
261
|
+
persist(true);
|
|
262
|
+
},
|
|
263
|
+
finish(thread, accumulators) {
|
|
264
|
+
if (thread)
|
|
265
|
+
state.lastMessages = thread.slice();
|
|
266
|
+
if (accumulators)
|
|
267
|
+
state.lastAccumulators = normalizeAccumulators(accumulators);
|
|
268
|
+
persist(false);
|
|
269
|
+
},
|
|
270
|
+
lastSaved() {
|
|
271
|
+
return state.saved;
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
STORE_STATES.set(store, state);
|
|
275
|
+
return store;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* The session id for this ask, in this directory, possibly WORDED DIFFERENTLY.
|
|
279
|
+
*
|
|
280
|
+
* Same contract as `resolveRecordIdFor` in the resume ledger: the exact id if a
|
|
281
|
+
* snapshot exists, else the newest OPEN snapshot in this directory whose goal
|
|
282
|
+
* `goalsLookSame`. A snapshot for a different ask is never returned — the caller
|
|
283
|
+
* would otherwise rehydrate a conversation it never had.
|
|
284
|
+
*/
|
|
285
|
+
export function resolveSessionIdFor(goal, cwd, options = {}) {
|
|
286
|
+
const exact = checkpointIdFor(goal, cwd);
|
|
287
|
+
const onlyOpen = options.onlyOpen ?? true;
|
|
288
|
+
const exactSnap = loadSessionSnapshot(exact);
|
|
289
|
+
if (exactSnap && (!onlyOpen || exactSnap.open))
|
|
290
|
+
return exact;
|
|
291
|
+
const wanted = normalizeDir(cwd);
|
|
292
|
+
let best = null;
|
|
293
|
+
try {
|
|
294
|
+
for (const file of readdirSync(sessionsDir())) {
|
|
295
|
+
if (!file.endsWith('.json'))
|
|
296
|
+
continue;
|
|
297
|
+
const id = file.slice(0, -'.json'.length);
|
|
298
|
+
if (id === exact)
|
|
299
|
+
continue;
|
|
300
|
+
const snap = loadSessionSnapshot(id);
|
|
301
|
+
if (!snap)
|
|
302
|
+
continue;
|
|
303
|
+
if (onlyOpen && !snap.open)
|
|
304
|
+
continue;
|
|
305
|
+
if (normalizeDir(snap.cwd) !== wanted)
|
|
306
|
+
continue;
|
|
307
|
+
if (!goalsLookSame(snap.goal, goal))
|
|
308
|
+
continue;
|
|
309
|
+
if (!best || snap.savedAt > best.savedAt)
|
|
310
|
+
best = { id, savedAt: snap.savedAt };
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return exact;
|
|
315
|
+
}
|
|
316
|
+
return best?.id ?? exact;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The resumable snapshot for this ask, or null when there is nothing to resume.
|
|
320
|
+
*
|
|
321
|
+
* Only OPEN snapshots qualify (see the module docstring): a transcript from a
|
|
322
|
+
* turn that ended cleanly is history, and re-feeding it would have the model
|
|
323
|
+
* continue a finished conversation.
|
|
324
|
+
*/
|
|
325
|
+
export function findResumableSessionFor(goal, cwd) {
|
|
326
|
+
const id = resolveSessionIdFor(goal, cwd, { onlyOpen: true });
|
|
327
|
+
const snap = loadSessionSnapshot(id);
|
|
328
|
+
if (!snap || !snap.open || snap.messages.length === 0)
|
|
329
|
+
return null;
|
|
330
|
+
return snap;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Rebuild a loop thread from a stored snapshot and a FRESH head.
|
|
334
|
+
*
|
|
335
|
+
* The fresh head (system prompt, current project context) replaces the stored
|
|
336
|
+
* head; the stored conversation tail — the goal and everything done after it —
|
|
337
|
+
* is preserved. The result is what the next process loops over: it already knows
|
|
338
|
+
* what the dead process did, so those steps are neither re-run nor re-paid.
|
|
339
|
+
*/
|
|
340
|
+
export function rehydrateThread(head, snapshot) {
|
|
341
|
+
const start = Math.min(snapshot.headLength, snapshot.messages.length);
|
|
342
|
+
return [...head, ...snapshot.messages.slice(start)];
|
|
343
|
+
}
|
|
344
|
+
/** A bounded, human-facing line describing a resumable snapshot. */
|
|
345
|
+
export function formatSessionResume(snapshot) {
|
|
346
|
+
const steps = snapshot.accumulators.steps;
|
|
347
|
+
const tools = snapshot.accumulators.successfulTools.length;
|
|
348
|
+
const paths = snapshot.accumulators.mutatedPaths.length;
|
|
349
|
+
return (`↩️ session for this ask is still OPEN — ${steps} step(s), ${tools} successful tool call(s)` +
|
|
350
|
+
(paths > 0 ? `, ${paths} path(s) changed` : '') +
|
|
351
|
+
' — the conversation is restored (this is HISTORY, not a status: verify artifacts on disk)');
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* List every stored session snapshot, newest first. Best-effort: a corrupt or
|
|
355
|
+
* unreadable record is skipped, never thrown.
|
|
356
|
+
*/
|
|
357
|
+
export function listSessionSnapshots() {
|
|
358
|
+
try {
|
|
359
|
+
const out = [];
|
|
360
|
+
for (const file of readdirSync(sessionsDir())) {
|
|
361
|
+
if (!file.endsWith('.json'))
|
|
362
|
+
continue;
|
|
363
|
+
const snap = loadSessionSnapshot(file.slice(0, -'.json'.length));
|
|
364
|
+
if (snap)
|
|
365
|
+
out.push(snap);
|
|
366
|
+
}
|
|
367
|
+
return out.sort((a, b) => b.savedAt - a.savedAt);
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
return [];
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/** Remove one snapshot. Returns whether a file was removed. */
|
|
374
|
+
export function clearSession(id) {
|
|
375
|
+
try {
|
|
376
|
+
const path = snapshotPath(id);
|
|
377
|
+
if (!existsSync(path))
|
|
378
|
+
return false;
|
|
379
|
+
rmSync(path, { force: true });
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Bound the session directory: drop snapshots older than `maxAgeMs` and keep at
|
|
388
|
+
* most `maxCount` (newest first). Returns how many files were removed.
|
|
389
|
+
*
|
|
390
|
+
* Best-effort and never throws — a prune that fails leaves the records (a disk
|
|
391
|
+
* full of old transcripts is a smaller problem than a broken turn).
|
|
392
|
+
*/
|
|
393
|
+
export function pruneSessions(options = {}) {
|
|
394
|
+
const now = options.now ?? Date.now();
|
|
395
|
+
const maxAgeMs = options.maxAgeMs ?? 30 * 24 * 60 * 60 * 1000;
|
|
396
|
+
const maxCount = options.maxCount ?? 50;
|
|
397
|
+
let removed = 0;
|
|
398
|
+
try {
|
|
399
|
+
const entries = [];
|
|
400
|
+
for (const file of readdirSync(sessionsDir())) {
|
|
401
|
+
if (!file.endsWith('.json'))
|
|
402
|
+
continue;
|
|
403
|
+
const id = file.slice(0, -'.json'.length);
|
|
404
|
+
const snap = loadSessionSnapshot(id);
|
|
405
|
+
entries.push({ id, savedAt: snap?.savedAt ?? 0 });
|
|
406
|
+
}
|
|
407
|
+
entries.sort((a, b) => b.savedAt - a.savedAt);
|
|
408
|
+
for (let i = 0; i < entries.length; i += 1) {
|
|
409
|
+
const { id, savedAt } = entries[i];
|
|
410
|
+
const tooOld = maxAgeMs > 0 && now - savedAt > maxAgeMs;
|
|
411
|
+
const overCount = i >= maxCount;
|
|
412
|
+
if ((tooOld || overCount) && clearSession(id))
|
|
413
|
+
removed += 1;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
return removed;
|
|
418
|
+
}
|
|
419
|
+
return removed;
|
|
420
|
+
}
|
|
421
|
+
function normalizeDir(dir) {
|
|
422
|
+
return (dir || '').replace(/[\\/]+$/, '');
|
|
423
|
+
}
|
|
424
|
+
//# sourceMappingURL=session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/learning/session-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -68,6 +68,21 @@ export declare function resolveResumeRequest(input: {
|
|
|
68
68
|
* not a change of input — while a different VALUE anywhere hashes differently.
|
|
69
69
|
*/
|
|
70
70
|
export declare function stepDigest(thread: readonly unknown[], schemas: readonly unknown[]): string;
|
|
71
|
+
/**
|
|
72
|
+
* B3 — the record id for this ask, in this directory, possibly WORDED DIFFERENTLY.
|
|
73
|
+
*
|
|
74
|
+
* `checkpointIdFor` hashes the literal goal, so "fix the hotkey" and "hotkey
|
|
75
|
+
* still not working" are two unrelated ids and the second ask cannot find the
|
|
76
|
+
* first's record — the reworded-ask miss the pipeline arm already solves with
|
|
77
|
+
* `findRelatedCheckpointFor` / `goalsLookSame`. The loop's replay ledger had no
|
|
78
|
+
* equivalent, so a reworded `--resume` replayed nothing.
|
|
79
|
+
*
|
|
80
|
+
* Order of preference: the EXACT id (a record we know is this ask), then the
|
|
81
|
+
* newest record in this directory whose goal `goalsLookSame` (the same ask,
|
|
82
|
+
* worded differently). A record for a DIFFERENT ask is never returned — the
|
|
83
|
+
* caller would otherwise replay answers to questions it never asked.
|
|
84
|
+
*/
|
|
85
|
+
export declare function resolveRecordIdFor(goal: string, cwd: string): string;
|
|
71
86
|
/** ─── The ledger ───────────────────────────────────────────────────────────── */
|
|
72
87
|
/**
|
|
73
88
|
* The ledger a loop threads its steps through.
|
|
@@ -99,6 +114,12 @@ export declare function openResume(input: {
|
|
|
99
114
|
goal: string;
|
|
100
115
|
cwd: string;
|
|
101
116
|
resume: ResumeRequest;
|
|
117
|
+
/**
|
|
118
|
+
* B4 — reuse recorded steps (default true). `false` = checkpoint-only: write a
|
|
119
|
+
* resume point without replaying anything, which is what makes default-on
|
|
120
|
+
* checkpointing safe. A caller that only wants to LEAVE a record passes false.
|
|
121
|
+
*/
|
|
122
|
+
replay?: boolean;
|
|
102
123
|
}): OpenResume;
|
|
103
124
|
/** What a resumed run replayed, what it paid for, and whether the record survived. */
|
|
104
125
|
export interface ResumeOutcome {
|
|
@@ -107,6 +128,13 @@ export interface ResumeOutcome {
|
|
|
107
128
|
replayed: number;
|
|
108
129
|
/** Model calls the RESUMED run still made (0 = every step came from the record). */
|
|
109
130
|
modelCalls: number;
|
|
131
|
+
/**
|
|
132
|
+
* Phase 5 — the CONTINUATION SAVING, stated as a first-class number: each
|
|
133
|
+
* replayed step is one model call the resumed run did NOT pay for. Equal to
|
|
134
|
+
* `replayed` by construction, but named so a surface can report the saving
|
|
135
|
+
* without knowing that identity.
|
|
136
|
+
*/
|
|
137
|
+
callsAvoided: number;
|
|
110
138
|
/** False when the record could not be written (the run itself still happened). */
|
|
111
139
|
saved: boolean;
|
|
112
140
|
/** The operator-facing line: the counts, and why nothing replayed when it didn't. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step-checkpoint.d.ts","sourceRoot":"","sources":["../../src/learning/step-checkpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAQH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"step-checkpoint.d.ts","sourceRoot":"","sources":["../../src/learning/step-checkpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAQH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AASjD,mFAAmF;AAEnF;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,GAAG,aAAa,GAAG,SAAS,CAgBpG;AAED,mFAAmF;AAEnF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,EAAE,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAE1F;AAgFD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAoBpE;AAED,mFAAmF;AAEnF;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,kFAAkF;IAClF,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAAC;IAC7D,uEAAuE;IACvE,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACtE,gEAAgE;IAChE,UAAU,IAAI,MAAM,CAAC;CACtB;AAmCD,iFAAiF;AACjF,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,UAAU,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,aAAa,CAAC;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GAAG,UAAU,CAqEb;AAMD,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,KAAK,EAAE,OAAO,CAAC;IACf,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CA4CnG"}
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
* anything — including on runs that then replayed everything.
|
|
35
35
|
*/
|
|
36
36
|
import { createHash } from 'node:crypto';
|
|
37
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
37
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
38
38
|
import { join } from 'node:path';
|
|
39
|
-
import { checkpointIdFor } from '../agents/checkpoint-store.js';
|
|
39
|
+
import { checkpointIdFor, goalsLookSame } from '../agents/checkpoint-store.js';
|
|
40
40
|
import { envBuff, resolveNuviraHome } from '../config/paths.js';
|
|
41
41
|
/** The environment key a deployment asks for a resume through. */
|
|
42
42
|
export const RESUME_ENABLE_ENV = 'NUVIRA_RESUME';
|
|
@@ -44,6 +44,8 @@ export const RESUME_ENABLE_ENV = 'NUVIRA_RESUME';
|
|
|
44
44
|
const CHANGED_REASON = 'its input changed';
|
|
45
45
|
const EMPTY_REASON = 'the recorded step was an empty provider response';
|
|
46
46
|
const MISSING_REASON = 'not in the record';
|
|
47
|
+
/** B4 — a checkpoint-only run does not replay, so every step reports this. */
|
|
48
|
+
const NOT_REQUESTED_REASON = 'replay was not requested (checkpoint only)';
|
|
47
49
|
/**
|
|
48
50
|
* Resolve a resume request from a caller's option, with the environment as the
|
|
49
51
|
* fallback.
|
|
@@ -129,6 +131,64 @@ function writeRecord(file) {
|
|
|
129
131
|
return false;
|
|
130
132
|
}
|
|
131
133
|
}
|
|
134
|
+
/** Read a record's metadata without trusting its steps (null when missing/corrupt). */
|
|
135
|
+
function readRecordMeta(id) {
|
|
136
|
+
try {
|
|
137
|
+
const path = recordPath(id);
|
|
138
|
+
if (!existsSync(path))
|
|
139
|
+
return null;
|
|
140
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
141
|
+
if (!parsed || typeof parsed.goal !== 'string' || typeof parsed.cwd !== 'string')
|
|
142
|
+
return null;
|
|
143
|
+
return { goal: parsed.goal, cwd: parsed.cwd, savedAt: typeof parsed.savedAt === 'number' ? parsed.savedAt : 0 };
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* B3 — the record id for this ask, in this directory, possibly WORDED DIFFERENTLY.
|
|
151
|
+
*
|
|
152
|
+
* `checkpointIdFor` hashes the literal goal, so "fix the hotkey" and "hotkey
|
|
153
|
+
* still not working" are two unrelated ids and the second ask cannot find the
|
|
154
|
+
* first's record — the reworded-ask miss the pipeline arm already solves with
|
|
155
|
+
* `findRelatedCheckpointFor` / `goalsLookSame`. The loop's replay ledger had no
|
|
156
|
+
* equivalent, so a reworded `--resume` replayed nothing.
|
|
157
|
+
*
|
|
158
|
+
* Order of preference: the EXACT id (a record we know is this ask), then the
|
|
159
|
+
* newest record in this directory whose goal `goalsLookSame` (the same ask,
|
|
160
|
+
* worded differently). A record for a DIFFERENT ask is never returned — the
|
|
161
|
+
* caller would otherwise replay answers to questions it never asked.
|
|
162
|
+
*/
|
|
163
|
+
export function resolveRecordIdFor(goal, cwd) {
|
|
164
|
+
const exact = checkpointIdFor(goal, cwd);
|
|
165
|
+
if (existsSync(recordPath(exact)))
|
|
166
|
+
return exact;
|
|
167
|
+
const wanted = cwd.replace(/[\\/]+$/, '');
|
|
168
|
+
let best = null;
|
|
169
|
+
try {
|
|
170
|
+
for (const file of readdirSync(stepsDir())) {
|
|
171
|
+
if (!file.endsWith('.json'))
|
|
172
|
+
continue;
|
|
173
|
+
const id = file.slice(0, -'.json'.length);
|
|
174
|
+
if (id === exact)
|
|
175
|
+
continue;
|
|
176
|
+
const meta = readRecordMeta(id);
|
|
177
|
+
if (!meta)
|
|
178
|
+
continue;
|
|
179
|
+
if (meta.cwd.replace(/[\\/]+$/, '') !== wanted)
|
|
180
|
+
continue;
|
|
181
|
+
if (!goalsLookSame(meta.goal, goal))
|
|
182
|
+
continue;
|
|
183
|
+
if (!best || meta.savedAt > best.savedAt)
|
|
184
|
+
best = { id, savedAt: meta.savedAt };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return exact;
|
|
189
|
+
}
|
|
190
|
+
return best?.id ?? exact;
|
|
191
|
+
}
|
|
132
192
|
/** The ledger instances this module created, so `closeResume` can read its state. */
|
|
133
193
|
const STATES = new WeakMap();
|
|
134
194
|
/** A response with no text and no tool call is a provider failure, not an answer. */
|
|
@@ -143,7 +203,13 @@ function isEmptyResponse(response) {
|
|
|
143
203
|
* Open the record for this ask, reading it once (a record is not re-read per step).
|
|
144
204
|
*/
|
|
145
205
|
export function openResume(input) {
|
|
146
|
-
const
|
|
206
|
+
const replayEnabled = input.replay !== false;
|
|
207
|
+
// B3 — an explicit id is honoured exactly; otherwise resolve the record for
|
|
208
|
+
// this ask HERE, matching a reworded version of the same ask too. A
|
|
209
|
+
// checkpoint-only run does NOT go looking for a reworded record: it is
|
|
210
|
+
// writing forward, not resuming, so it uses this ask's own id.
|
|
211
|
+
const id = input.resume.id?.trim() ||
|
|
212
|
+
(replayEnabled ? resolveRecordIdFor(input.goal, input.cwd) : checkpointIdFor(input.goal, input.cwd));
|
|
147
213
|
const prior = readRecord(id);
|
|
148
214
|
const state = {
|
|
149
215
|
id,
|
|
@@ -153,11 +219,20 @@ export function openResume(input) {
|
|
|
153
219
|
replayed: 0,
|
|
154
220
|
modelCalls: 0,
|
|
155
221
|
misses: new Map(),
|
|
156
|
-
|
|
222
|
+
replayEnabled,
|
|
223
|
+
// Checkpoint-only: seed the staged map with what the record already holds so
|
|
224
|
+
// `closeResume` MERGES this run's steps into it instead of replacing a
|
|
225
|
+
// usable record with only the latest run's work.
|
|
226
|
+
staged: replayEnabled ? new Map() : new Map(prior.map((entry) => [entry.key, entry])),
|
|
157
227
|
};
|
|
158
228
|
const byKey = new Map(prior.map((entry) => [entry.key, entry]));
|
|
159
229
|
const ledger = {
|
|
160
230
|
replay(key, digest) {
|
|
231
|
+
if (!state.replayEnabled) {
|
|
232
|
+
// Checkpoint-only: nothing is reused this run (see `replayEnabled`).
|
|
233
|
+
countMiss(state, NOT_REQUESTED_REASON);
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
161
236
|
const entry = byKey.get(key);
|
|
162
237
|
if (!entry) {
|
|
163
238
|
countMiss(state, MISSING_REASON);
|
|
@@ -182,6 +257,11 @@ export function openResume(input) {
|
|
|
182
257
|
state.staged.set(key, { key, digest, response });
|
|
183
258
|
},
|
|
184
259
|
openNotice() {
|
|
260
|
+
if (!state.replayEnabled) {
|
|
261
|
+
return state.loaded > 0
|
|
262
|
+
? `💾 checkpointing this run — the existing record (${state.loaded} step(s)) will be updated`
|
|
263
|
+
: '💾 checkpointing this run — a resume point will be written for this ask';
|
|
264
|
+
}
|
|
185
265
|
return state.loaded > 0
|
|
186
266
|
? `↩️ resumed: ${state.loaded} recorded step(s) loaded — a step replays only when its whole input is unchanged`
|
|
187
267
|
: '↩️ no record for this ask in this directory yet — this run will write one';
|
|
@@ -205,7 +285,7 @@ export function closeResume(resume, input) {
|
|
|
205
285
|
const state = STATES.get(resume.ledger);
|
|
206
286
|
if (!state) {
|
|
207
287
|
const notice = '↩️ resume probe: replayed 0, made 0 model call(s)';
|
|
208
|
-
return { id: resume.id, replayed: 0, modelCalls: 0, saved: false, notice };
|
|
288
|
+
return { id: resume.id, replayed: 0, modelCalls: 0, callsAvoided: 0, saved: false, notice };
|
|
209
289
|
}
|
|
210
290
|
const steps = [...state.staged.values()];
|
|
211
291
|
let saved;
|
|
@@ -223,8 +303,11 @@ export function closeResume(resume, input) {
|
|
|
223
303
|
steps,
|
|
224
304
|
});
|
|
225
305
|
}
|
|
306
|
+
const avoided = state.replayed > 0
|
|
307
|
+
? ` — ${state.replayed} model call(s) REUSED from the record (not re-paid)`
|
|
308
|
+
: '';
|
|
226
309
|
const lines = [
|
|
227
|
-
`↩️ resume probe: replayed ${state.replayed}, made ${state.modelCalls} model call(s)`,
|
|
310
|
+
`↩️ resume probe: replayed ${state.replayed}, made ${state.modelCalls} model call(s)${avoided}`,
|
|
228
311
|
];
|
|
229
312
|
if (state.replayed === 0 && state.misses.size > 0) {
|
|
230
313
|
const reasons = [...state.misses.entries()].map(([reason, count]) => `${reason} (${count})`);
|
|
@@ -237,6 +320,7 @@ export function closeResume(resume, input) {
|
|
|
237
320
|
id: state.id,
|
|
238
321
|
replayed: state.replayed,
|
|
239
322
|
modelCalls: state.modelCalls,
|
|
323
|
+
callsAvoided: state.replayed,
|
|
240
324
|
saved,
|
|
241
325
|
notice: lines.join('\n'),
|
|
242
326
|
};
|