agent-nuvira 3.3.8 → 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 +3 -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-J3sGSCLr.js +0 -207
- package/src/web-dashboard/public/assets/index-J3sGSCLr.js.map +0 -1
- package/src/web-dashboard/public/assets/index-u7mu-fxK.css +0 -1
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session recall (Phase 4 follow-on) — SEMANTIC recall across past asks.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS, AND WHY IT IS OPT-IN.
|
|
5
|
+
* The deterministic session digest (`session-digest.ts`) shows the LAST few asks
|
|
6
|
+
* in a project. That is good for continuity, but it is recency-based: an ask from
|
|
7
|
+
* three weeks ago about the SAME subject is invisible if ten unrelated asks
|
|
8
|
+
* happened since. This module adds *semantic* recall — embed each finished ask,
|
|
9
|
+
* and at the start of a new turn retrieve the past asks that mean the same thing,
|
|
10
|
+
* above an explicit cosine THRESHOLD, so relevance is by meaning rather than by
|
|
11
|
+
* a literal token overlap (`goalsLookSame`) or by recency.
|
|
12
|
+
*
|
|
13
|
+
* It is deliberately OFF unless asked for (`NUVIRA_SESSION_RECALL=1`). Semantics
|
|
14
|
+
* introduce an embedding dependency whose tiers can be slow to fail over; a
|
|
15
|
+
* feature that makes an ordinary turn wait on a model download would be a worse
|
|
16
|
+
* bug than the recall it buys. So, exactly like `--resume`, an ordinary run pays
|
|
17
|
+
* nothing: no read, no embed, no query.
|
|
18
|
+
*
|
|
19
|
+
* WHY IT CANNOT RE-OPEN FALSE SUCCESS. A recalled ask is HISTORY, presented as
|
|
20
|
+
* however it ENDED (`acted` / `incomplete` / `failed` / `cancelled`) — never as a
|
|
21
|
+
* completion the current work inherits. The block says so outright, and nothing
|
|
22
|
+
* here is read to decide whether a step is done: completion still derives from
|
|
23
|
+
* artifacts on disk. A recalled "the build works" is a sentence the past model
|
|
24
|
+
* wrote, quoted, not a fact the framework adopts.
|
|
25
|
+
*
|
|
26
|
+
* Bounded + best-effort throughout: a fixed entry cap, a fixed top-k, a fixed
|
|
27
|
+
* block size, and every read/write/embed wrapped so a corrupt index or a missing
|
|
28
|
+
* embedding tier degrades to "no recall", never a broken turn.
|
|
29
|
+
*/
|
|
30
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
|
|
31
|
+
import { join, resolve } from 'node:path';
|
|
32
|
+
import { envBuff, resolveNuviraHome } from '../config/paths.js';
|
|
33
|
+
import { embed } from '../memory/embedder.js';
|
|
34
|
+
import { cosineSimilarity } from '../memory/vector-store.js';
|
|
35
|
+
/** The environment key that turns semantic recall on (`1`/`true`/`yes`). */
|
|
36
|
+
export const SESSION_RECALL_ENV = 'NUVIRA_SESSION_RECALL';
|
|
37
|
+
/** Cosine floor a past ask must clear to be recalled (0-1). */
|
|
38
|
+
export const DEFAULT_RECALL_THRESHOLD = 0.5;
|
|
39
|
+
/** How many past asks to surface at most. */
|
|
40
|
+
export const DEFAULT_RECALL_TOP_K = 3;
|
|
41
|
+
/** Hard cap on the index (oldest dropped) — recall must never grow unbounded. */
|
|
42
|
+
export const MAX_INDEX_ENTRIES = 500;
|
|
43
|
+
/** Hard cap on the rendered block — it rides in prompts. */
|
|
44
|
+
export const MAX_RECALL_BLOCK_CHARS = 1_200;
|
|
45
|
+
const CURRENT_VERSION = 1;
|
|
46
|
+
// ─── Enablement ─────────────────────────────────────────────────────────────
|
|
47
|
+
/** Words a reader treats as OFF (same vocabulary the process-env page uses). */
|
|
48
|
+
const OFF_WORDS = new Set(['0', 'false', 'off', 'no']);
|
|
49
|
+
/**
|
|
50
|
+
* Is semantic recall on? DEFAULT ON — like checkpointing, an operator can turn
|
|
51
|
+
* it off, but a fresh install gets the feature rather than a switch that does
|
|
52
|
+
* nothing until someone reads the docs. `0`/`false`/`off`/`no` turn it off;
|
|
53
|
+
* unset (or any other value) leaves it ON.
|
|
54
|
+
*/
|
|
55
|
+
export function sessionRecallEnabled(env = process.env) {
|
|
56
|
+
const raw = env[SESSION_RECALL_ENV];
|
|
57
|
+
if (typeof raw !== 'string')
|
|
58
|
+
return true;
|
|
59
|
+
return !OFF_WORDS.has(raw.trim().toLowerCase());
|
|
60
|
+
}
|
|
61
|
+
// Keep the repo's env indirection working too (`NUVIRA_` then `BUFF_`).
|
|
62
|
+
function recallEnabledViaHelper() {
|
|
63
|
+
const raw = envBuff('SESSION_RECALL');
|
|
64
|
+
if (typeof raw !== 'string')
|
|
65
|
+
return undefined;
|
|
66
|
+
return !OFF_WORDS.has(raw.trim().toLowerCase());
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve whether semantic recall is on, with the repo's precedence:
|
|
70
|
+
* an explicit FLAG wins, then the environment, then config, then the default
|
|
71
|
+
* (ON). This is the single place the decision is made, so the CLI, the loop and
|
|
72
|
+
* the dashboard cannot disagree.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveSessionRecall(input = {}) {
|
|
75
|
+
if (input.flag !== undefined)
|
|
76
|
+
return input.flag;
|
|
77
|
+
const viaEnv = recallEnabledViaHelper();
|
|
78
|
+
if (viaEnv !== undefined)
|
|
79
|
+
return viaEnv;
|
|
80
|
+
const cfg = input.configManager?.getAll().memory?.sessionRecall;
|
|
81
|
+
return cfg !== false;
|
|
82
|
+
}
|
|
83
|
+
function enabled() {
|
|
84
|
+
return resolveSessionRecall();
|
|
85
|
+
}
|
|
86
|
+
// ─── Storage ────────────────────────────────────────────────────────────────
|
|
87
|
+
function memoryDir() {
|
|
88
|
+
return envBuff('MEMORY_DIR') || join(resolveNuviraHome(), 'memory');
|
|
89
|
+
}
|
|
90
|
+
function indexPath() {
|
|
91
|
+
return join(memoryDir(), 'session-recall-index.json');
|
|
92
|
+
}
|
|
93
|
+
function normalizeProjectPath(projectPath) {
|
|
94
|
+
try {
|
|
95
|
+
return resolve(projectPath);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return projectPath;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function readIndex() {
|
|
102
|
+
try {
|
|
103
|
+
if (!existsSync(indexPath()))
|
|
104
|
+
return { version: CURRENT_VERSION, entries: [] };
|
|
105
|
+
const data = JSON.parse(readFileSync(indexPath(), 'utf-8'));
|
|
106
|
+
if (!data || typeof data !== 'object' || !Array.isArray(data.entries)) {
|
|
107
|
+
return { version: CURRENT_VERSION, entries: [] };
|
|
108
|
+
}
|
|
109
|
+
const entries = data.entries.filter(isRecallEntry);
|
|
110
|
+
return { version: CURRENT_VERSION, entries };
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return { version: CURRENT_VERSION, entries: [] };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function isRecallEntry(value) {
|
|
117
|
+
if (!value || typeof value !== 'object')
|
|
118
|
+
return false;
|
|
119
|
+
const e = value;
|
|
120
|
+
return (typeof e.id === 'string' &&
|
|
121
|
+
typeof e.projectPath === 'string' &&
|
|
122
|
+
typeof e.goal === 'string' &&
|
|
123
|
+
Array.isArray(e.vector) &&
|
|
124
|
+
e.vector.every((n) => typeof n === 'number' && Number.isFinite(n)));
|
|
125
|
+
}
|
|
126
|
+
function writeIndex(index) {
|
|
127
|
+
try {
|
|
128
|
+
mkdirSync(memoryDir(), { recursive: true });
|
|
129
|
+
writeFileSync(indexPath(), JSON.stringify(index, null, 2), 'utf-8');
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function isZeroVector(vector) {
|
|
137
|
+
return vector.length === 0 || vector.every((n) => n === 0);
|
|
138
|
+
}
|
|
139
|
+
// ─── Indexing ───────────────────────────────────────────────────────────────
|
|
140
|
+
/** A stable id for one ask in a project, so re-indexing a turn overwrites it. */
|
|
141
|
+
function entryId(projectPath, goal, savedAt) {
|
|
142
|
+
return `${normalizeProjectPath(projectPath)}#${savedAt}#${goal.slice(0, 48)}`;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Index one finished ask for later semantic recall. Returns whether an entry was
|
|
146
|
+
* written. Best-effort and NO-OP when recall is off or embeddings are
|
|
147
|
+
* unavailable (a zero vector is never stored — it would only ever be a
|
|
148
|
+
* meaningless match).
|
|
149
|
+
*/
|
|
150
|
+
export async function indexSessionTurn(input, opts = {}) {
|
|
151
|
+
const on = opts.enabled ?? enabled();
|
|
152
|
+
if ((!opts.force && !on) || !input.goal.trim())
|
|
153
|
+
return false;
|
|
154
|
+
const embedFn = opts.embedFn ?? ((t) => embed(t));
|
|
155
|
+
let vector;
|
|
156
|
+
try {
|
|
157
|
+
vector = await embedFn(input.goal);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
if (isZeroVector(vector))
|
|
163
|
+
return false;
|
|
164
|
+
const savedAt = input.savedAt ?? Date.now();
|
|
165
|
+
const entry = {
|
|
166
|
+
id: entryId(input.projectPath, input.goal, savedAt),
|
|
167
|
+
projectPath: normalizeProjectPath(input.projectPath),
|
|
168
|
+
goal: input.goal.slice(0, 240),
|
|
169
|
+
outcome: (input.outcome ?? 'unknown').slice(0, 24),
|
|
170
|
+
savedAt,
|
|
171
|
+
vector,
|
|
172
|
+
};
|
|
173
|
+
const index = readIndex();
|
|
174
|
+
const entries = [...index.entries.filter((e) => e.id !== entry.id), entry];
|
|
175
|
+
// Oldest-first eviction past the cap.
|
|
176
|
+
if (entries.length > MAX_INDEX_ENTRIES)
|
|
177
|
+
entries.splice(0, entries.length - MAX_INDEX_ENTRIES);
|
|
178
|
+
return writeIndex({ version: CURRENT_VERSION, entries });
|
|
179
|
+
}
|
|
180
|
+
// ─── Recall ─────────────────────────────────────────────────────────────────
|
|
181
|
+
/**
|
|
182
|
+
* The past asks semantically closest to `query`, above the threshold.
|
|
183
|
+
*
|
|
184
|
+
* `scope: 'project'` (default) limits recall to the current project directory —
|
|
185
|
+
* the same scoping the rest of the continuity stack uses, so a new task is not
|
|
186
|
+
* handed another project's history. `scope: 'all'` searches every project and
|
|
187
|
+
* names each hit's project in the block.
|
|
188
|
+
*/
|
|
189
|
+
export async function recallPastSessions(query, opts = {}) {
|
|
190
|
+
if (!query.trim())
|
|
191
|
+
return [];
|
|
192
|
+
const on = opts.enabled ?? enabled();
|
|
193
|
+
if (!opts.force && !on)
|
|
194
|
+
return [];
|
|
195
|
+
const index = readIndex();
|
|
196
|
+
if (index.entries.length === 0)
|
|
197
|
+
return [];
|
|
198
|
+
const scope = opts.scope ?? 'project';
|
|
199
|
+
const wanted = opts.projectPath ? normalizeProjectPath(opts.projectPath) : '';
|
|
200
|
+
const candidates = scope === 'all' || !wanted ? index.entries : index.entries.filter((e) => e.projectPath === wanted);
|
|
201
|
+
if (candidates.length === 0)
|
|
202
|
+
return [];
|
|
203
|
+
const embedFn = opts.embedFn ?? ((t) => embed(t));
|
|
204
|
+
let queryVector;
|
|
205
|
+
try {
|
|
206
|
+
queryVector = await embedFn(query);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
if (isZeroVector(queryVector))
|
|
212
|
+
return [];
|
|
213
|
+
const threshold = opts.threshold ?? DEFAULT_RECALL_THRESHOLD;
|
|
214
|
+
const topK = Math.max(1, opts.topK ?? DEFAULT_RECALL_TOP_K);
|
|
215
|
+
const hits = [];
|
|
216
|
+
for (const entry of candidates) {
|
|
217
|
+
let similarity;
|
|
218
|
+
try {
|
|
219
|
+
similarity = cosineSimilarity(queryVector, entry.vector);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (!Number.isFinite(similarity) || similarity < threshold)
|
|
225
|
+
continue;
|
|
226
|
+
hits.push({ entry, similarity });
|
|
227
|
+
}
|
|
228
|
+
hits.sort((a, b) => b.similarity - a.similarity);
|
|
229
|
+
return hits.slice(0, topK);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Render recall hits as a bounded, model-readable block ('' when there are none).
|
|
233
|
+
*
|
|
234
|
+
* The header is load-bearing: it states that these are PAST asks — history for
|
|
235
|
+
* context only, NOT a status — so a recalled line cannot be read as proof the
|
|
236
|
+
* current work is done. Each hit carries how it ENDED and its project when the
|
|
237
|
+
* recall crossed projects.
|
|
238
|
+
*/
|
|
239
|
+
export function formatSessionRecall(hits, opts = {}) {
|
|
240
|
+
if (hits.length === 0)
|
|
241
|
+
return '';
|
|
242
|
+
const now = opts.now ?? Date.now();
|
|
243
|
+
const projectScoped = opts.projectPath !== undefined;
|
|
244
|
+
const lines = [
|
|
245
|
+
'[Semantically similar PAST asks — history for context only. This is NOT a status of the current work; verify artifacts on disk before assuming anything is done.]',
|
|
246
|
+
];
|
|
247
|
+
for (const { entry, similarity } of hits) {
|
|
248
|
+
const age = relativeAge(entry.savedAt, now);
|
|
249
|
+
const where = projectScoped ? '' : ` [${shortenPath(entry.projectPath)}]`;
|
|
250
|
+
lines.push(`• (${Math.round(similarity * 100)}% match)${where} ${age} — ${entry.outcome}: "${entry.goal}"`);
|
|
251
|
+
}
|
|
252
|
+
let block = lines.join('\n');
|
|
253
|
+
if (block.length > MAX_RECALL_BLOCK_CHARS) {
|
|
254
|
+
block = `${block.slice(0, MAX_RECALL_BLOCK_CHARS)}\n[recall truncated to fit the context budget]`;
|
|
255
|
+
}
|
|
256
|
+
return block;
|
|
257
|
+
}
|
|
258
|
+
function relativeAge(ts, now) {
|
|
259
|
+
const mins = Math.max(0, Math.round((now - ts) / 60_000));
|
|
260
|
+
if (mins < 1)
|
|
261
|
+
return 'just now';
|
|
262
|
+
if (mins < 60)
|
|
263
|
+
return `${mins}m ago`;
|
|
264
|
+
const hours = Math.round(mins / 60);
|
|
265
|
+
if (hours < 24)
|
|
266
|
+
return `${hours}h ago`;
|
|
267
|
+
return `${Math.round(hours / 24)}d ago`;
|
|
268
|
+
}
|
|
269
|
+
function shortenPath(p) {
|
|
270
|
+
const parts = p.split(/[\\/]/).filter(Boolean);
|
|
271
|
+
return parts.slice(-2).join('/') || p;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* List every indexed past ask, newest first. Best-effort. The vectors are part of
|
|
275
|
+
* each entry; a caller that only needs a summary maps them away.
|
|
276
|
+
*/
|
|
277
|
+
export function listRecallEntries() {
|
|
278
|
+
return readIndex().entries.slice().sort((a, b) => b.savedAt - a.savedAt);
|
|
279
|
+
}
|
|
280
|
+
/** Remove one indexed past ask by id. Returns whether it was present. */
|
|
281
|
+
export function removeRecallEntry(id) {
|
|
282
|
+
try {
|
|
283
|
+
const index = readIndex();
|
|
284
|
+
const next = index.entries.filter((e) => e.id !== id);
|
|
285
|
+
if (next.length === index.entries.length)
|
|
286
|
+
return false;
|
|
287
|
+
return writeIndex({ version: CURRENT_VERSION, entries: next });
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/** Remove the recall index (used by `nuvira retrieval clear`-style resets / tests). */
|
|
294
|
+
export function clearSessionRecallIndex() {
|
|
295
|
+
try {
|
|
296
|
+
if (!existsSync(indexPath()))
|
|
297
|
+
return false;
|
|
298
|
+
rmSync(indexPath(), { force: true });
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=session-recall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-recall.js","sourceRoot":"","sources":["../../src/learning/session-recall.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,172 @@
|
|
|
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 type { ToolMessage } from '../inference/interface.js';
|
|
45
|
+
/** The environment key a deployment uses to turn session persistence OFF. */
|
|
46
|
+
export declare const SESSION_ENABLE_ENV = "NUVIRA_SESSION_STORE";
|
|
47
|
+
/**
|
|
48
|
+
* Is session persistence on? DEFAULT ON — like checkpointing, a fresh install
|
|
49
|
+
* gets continuity and an operator can turn it off. `0`/`false`/`off`/`no` turn
|
|
50
|
+
* it off; unset (or any other value) leaves it ON.
|
|
51
|
+
*/
|
|
52
|
+
export declare function sessionStoreEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
53
|
+
/** The config/env shape a resolver reads (structural, so no import cycle). */
|
|
54
|
+
export interface StoreConfigSource {
|
|
55
|
+
getAll(): {
|
|
56
|
+
memory?: {
|
|
57
|
+
sessionStore?: boolean;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve whether session persistence is on, with the repo's precedence: an
|
|
63
|
+
* explicit FLAG wins, then the environment, then config, then the default (ON).
|
|
64
|
+
* One place decides, so the CLI, the loop and the dashboard cannot disagree.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveSessionStore(input?: {
|
|
67
|
+
flag?: boolean;
|
|
68
|
+
configManager?: StoreConfigSource | null;
|
|
69
|
+
}): boolean;
|
|
70
|
+
/** Bound on stored conversation messages — the most recent N are kept. */
|
|
71
|
+
export declare const MAX_SESSION_MESSAGES = 48;
|
|
72
|
+
/** Bound on a single stored message's content (the rest is clipped). */
|
|
73
|
+
export declare const MAX_MESSAGE_CHARS = 8000;
|
|
74
|
+
/** The accumulators the loop reports at a step boundary. */
|
|
75
|
+
export interface SessionAccumulators {
|
|
76
|
+
/** Steps completed so far this turn. */
|
|
77
|
+
steps: number;
|
|
78
|
+
/** Tool names that ran successfully (in order). */
|
|
79
|
+
successfulTools: string[];
|
|
80
|
+
/** Paths mutated successfully (in order). */
|
|
81
|
+
mutatedPaths: string[];
|
|
82
|
+
/** Tool names that errored, when the caller tracks them. */
|
|
83
|
+
erroredTools?: string[];
|
|
84
|
+
}
|
|
85
|
+
/** A persisted session snapshot: the conversation tail plus the accumulators. */
|
|
86
|
+
export interface SessionSnapshot {
|
|
87
|
+
id: string;
|
|
88
|
+
goal: string;
|
|
89
|
+
cwd: string;
|
|
90
|
+
savedAt: number;
|
|
91
|
+
/** Monotonic save counter, so a reader can tell updates apart. */
|
|
92
|
+
revision: number;
|
|
93
|
+
/** False once the turn ran to a clean end (and is therefore not resumable). */
|
|
94
|
+
open: boolean;
|
|
95
|
+
/** Leading thread messages that are reconstructible and must be replaced. */
|
|
96
|
+
headLength: number;
|
|
97
|
+
messages: ToolMessage[];
|
|
98
|
+
accumulators: SessionAccumulators;
|
|
99
|
+
}
|
|
100
|
+
/** Read a snapshot, best-effort. A corrupt record is a miss, never a crash. */
|
|
101
|
+
export declare function loadSessionSnapshot(id: string): SessionSnapshot | null;
|
|
102
|
+
/**
|
|
103
|
+
* The writer a loop threads through its step boundaries.
|
|
104
|
+
*
|
|
105
|
+
* `save` is called once per completed step; `finish` once, when the turn ends
|
|
106
|
+
* cleanly. Both are best-effort — a read-only home directory degrades to "no
|
|
107
|
+
* continuity", never to a failed turn.
|
|
108
|
+
*/
|
|
109
|
+
export interface SessionStore {
|
|
110
|
+
readonly id: string;
|
|
111
|
+
/** Snapshot the current thread + accumulators at a step boundary. */
|
|
112
|
+
save(thread: readonly ToolMessage[], accumulators: SessionAccumulators): void;
|
|
113
|
+
/** Mark the turn finished so the transcript is history, not a resume point. */
|
|
114
|
+
finish(thread?: readonly ToolMessage[], accumulators?: SessionAccumulators): void;
|
|
115
|
+
/** Whether the most recent write reached the disk. */
|
|
116
|
+
lastSaved(): boolean;
|
|
117
|
+
}
|
|
118
|
+
export declare function openSession(input: {
|
|
119
|
+
goal: string;
|
|
120
|
+
cwd: string;
|
|
121
|
+
id?: string;
|
|
122
|
+
}): SessionStore;
|
|
123
|
+
/**
|
|
124
|
+
* The session id for this ask, in this directory, possibly WORDED DIFFERENTLY.
|
|
125
|
+
*
|
|
126
|
+
* Same contract as `resolveRecordIdFor` in the resume ledger: the exact id if a
|
|
127
|
+
* snapshot exists, else the newest OPEN snapshot in this directory whose goal
|
|
128
|
+
* `goalsLookSame`. A snapshot for a different ask is never returned — the caller
|
|
129
|
+
* would otherwise rehydrate a conversation it never had.
|
|
130
|
+
*/
|
|
131
|
+
export declare function resolveSessionIdFor(goal: string, cwd: string, options?: {
|
|
132
|
+
onlyOpen?: boolean;
|
|
133
|
+
}): string;
|
|
134
|
+
/**
|
|
135
|
+
* The resumable snapshot for this ask, or null when there is nothing to resume.
|
|
136
|
+
*
|
|
137
|
+
* Only OPEN snapshots qualify (see the module docstring): a transcript from a
|
|
138
|
+
* turn that ended cleanly is history, and re-feeding it would have the model
|
|
139
|
+
* continue a finished conversation.
|
|
140
|
+
*/
|
|
141
|
+
export declare function findResumableSessionFor(goal: string, cwd: string): SessionSnapshot | null;
|
|
142
|
+
/**
|
|
143
|
+
* Rebuild a loop thread from a stored snapshot and a FRESH head.
|
|
144
|
+
*
|
|
145
|
+
* The fresh head (system prompt, current project context) replaces the stored
|
|
146
|
+
* head; the stored conversation tail — the goal and everything done after it —
|
|
147
|
+
* is preserved. The result is what the next process loops over: it already knows
|
|
148
|
+
* what the dead process did, so those steps are neither re-run nor re-paid.
|
|
149
|
+
*/
|
|
150
|
+
export declare function rehydrateThread(head: readonly ToolMessage[], snapshot: SessionSnapshot): ToolMessage[];
|
|
151
|
+
/** A bounded, human-facing line describing a resumable snapshot. */
|
|
152
|
+
export declare function formatSessionResume(snapshot: SessionSnapshot): string;
|
|
153
|
+
/**
|
|
154
|
+
* List every stored session snapshot, newest first. Best-effort: a corrupt or
|
|
155
|
+
* unreadable record is skipped, never thrown.
|
|
156
|
+
*/
|
|
157
|
+
export declare function listSessionSnapshots(): SessionSnapshot[];
|
|
158
|
+
/** Remove one snapshot. Returns whether a file was removed. */
|
|
159
|
+
export declare function clearSession(id: string): boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Bound the session directory: drop snapshots older than `maxAgeMs` and keep at
|
|
162
|
+
* most `maxCount` (newest first). Returns how many files were removed.
|
|
163
|
+
*
|
|
164
|
+
* Best-effort and never throws — a prune that fails leaves the records (a disk
|
|
165
|
+
* full of old transcripts is a smaller problem than a broken turn).
|
|
166
|
+
*/
|
|
167
|
+
export declare function pruneSessions(options?: {
|
|
168
|
+
maxAgeMs?: number;
|
|
169
|
+
maxCount?: number;
|
|
170
|
+
now?: number;
|
|
171
|
+
}): number;
|
|
172
|
+
//# sourceMappingURL=session-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/learning/session-store.ts"],"names":[],"mappings":"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"}
|