deepclause-sdk 0.0.89 → 0.1.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 +417 -948
- package/dist/agent.d.ts +18 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +74 -21
- package/dist/agent.js.map +1 -1
- package/dist/cli/config.d.ts +323 -176
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +146 -188
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +61 -425
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/login.d.ts +29 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/cli/login.js +184 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/readline.d.ts +6 -0
- package/dist/cli/readline.d.ts.map +1 -0
- package/dist/cli/readline.js +15 -0
- package/dist/cli/readline.js.map +1 -0
- package/dist/cli/repl-bin.d.ts +9 -0
- package/dist/cli/repl-bin.d.ts.map +1 -0
- package/dist/cli/repl-bin.js +16 -0
- package/dist/cli/repl-bin.js.map +1 -0
- package/dist/cli/repl.d.ts +191 -0
- package/dist/cli/repl.d.ts.map +1 -0
- package/dist/cli/repl.js +1232 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/cli/run.d.ts +17 -18
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +196 -183
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/set-backend.d.ts +53 -0
- package/dist/cli/set-backend.d.ts.map +1 -0
- package/dist/cli/set-backend.js +259 -0
- package/dist/cli/set-backend.js.map +1 -0
- package/dist/cli/visualize.d.ts +38 -0
- package/dist/cli/visualize.d.ts.map +1 -0
- package/dist/cli/visualize.js +125 -0
- package/dist/cli/visualize.js.map +1 -0
- package/dist/compiler.d.ts +5 -52
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +4 -768
- package/dist/compiler.js.map +1 -1
- package/dist/index.d.ts +27 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/prolog/bridge.d.ts +3 -1
- package/dist/prolog/bridge.d.ts.map +1 -1
- package/dist/prolog/bridge.js +8 -1
- package/dist/prolog/bridge.js.map +1 -1
- package/dist/prolog-src/deepclause_analysis.pl +10 -2
- package/dist/prolog-src/deepclause_mi.pl +2245 -41
- package/dist/repl/agent.d.ts +72 -0
- package/dist/repl/agent.d.ts.map +1 -0
- package/dist/repl/agent.js +366 -0
- package/dist/repl/agent.js.map +1 -0
- package/dist/repl/auth.d.ts +45 -0
- package/dist/repl/auth.d.ts.map +1 -0
- package/dist/repl/auth.js +116 -0
- package/dist/repl/auth.js.map +1 -0
- package/dist/repl/backend.d.ts +290 -0
- package/dist/repl/backend.d.ts.map +1 -0
- package/dist/repl/backend.js +370 -0
- package/dist/repl/backend.js.map +1 -0
- package/dist/repl/compaction.d.ts +122 -0
- package/dist/repl/compaction.d.ts.map +1 -0
- package/dist/repl/compaction.js +318 -0
- package/dist/repl/compaction.js.map +1 -0
- package/dist/repl/export.d.ts +30 -0
- package/dist/repl/export.d.ts.map +1 -0
- package/dist/repl/export.js +23 -0
- package/dist/repl/export.js.map +1 -0
- package/dist/repl/frames.d.ts +21 -0
- package/dist/repl/frames.d.ts.map +1 -0
- package/dist/repl/frames.js +79 -0
- package/dist/repl/frames.js.map +1 -0
- package/dist/repl/harness-tools.d.ts +74 -0
- package/dist/repl/harness-tools.d.ts.map +1 -0
- package/dist/repl/harness-tools.js +121 -0
- package/dist/repl/harness-tools.js.map +1 -0
- package/dist/repl/highlight.d.ts +30 -0
- package/dist/repl/highlight.d.ts.map +1 -0
- package/dist/repl/highlight.js +285 -0
- package/dist/repl/highlight.js.map +1 -0
- package/dist/repl/history.d.ts +234 -0
- package/dist/repl/history.d.ts.map +1 -0
- package/dist/repl/history.js +661 -0
- package/dist/repl/history.js.map +1 -0
- package/dist/repl/index.d.ts +120 -0
- package/dist/repl/index.d.ts.map +1 -0
- package/dist/repl/index.js +107 -0
- package/dist/repl/index.js.map +1 -0
- package/dist/repl/judge.d.ts +51 -0
- package/dist/repl/judge.d.ts.map +1 -0
- package/dist/repl/judge.js +58 -0
- package/dist/repl/judge.js.map +1 -0
- package/dist/repl/markdown.d.ts +25 -0
- package/dist/repl/markdown.d.ts.map +1 -0
- package/dist/repl/markdown.js +103 -0
- package/dist/repl/markdown.js.map +1 -0
- package/dist/repl/picker.d.ts +34 -0
- package/dist/repl/picker.d.ts.map +1 -0
- package/dist/repl/picker.js +81 -0
- package/dist/repl/picker.js.map +1 -0
- package/dist/repl/project-context.d.ts +16 -0
- package/dist/repl/project-context.d.ts.map +1 -0
- package/dist/repl/project-context.js +23 -0
- package/dist/repl/project-context.js.map +1 -0
- package/dist/repl/render.d.ts +160 -0
- package/dist/repl/render.d.ts.map +1 -0
- package/dist/repl/render.js +715 -0
- package/dist/repl/render.js.map +1 -0
- package/dist/repl/session.d.ts +356 -0
- package/dist/repl/session.d.ts.map +1 -0
- package/dist/repl/session.js +1275 -0
- package/dist/repl/session.js.map +1 -0
- package/dist/repl/status.d.ts +100 -0
- package/dist/repl/status.d.ts.map +1 -0
- package/dist/repl/status.js +218 -0
- package/dist/repl/status.js.map +1 -0
- package/dist/repl/tools.d.ts +53 -0
- package/dist/repl/tools.d.ts.map +1 -0
- package/dist/repl/tools.js +307 -0
- package/dist/repl/tools.js.map +1 -0
- package/dist/repl/toolset.d.ts +32 -0
- package/dist/repl/toolset.d.ts.map +1 -0
- package/dist/repl/toolset.js +82 -0
- package/dist/repl/toolset.js.map +1 -0
- package/dist/repl/tui/editor.d.ts +17 -0
- package/dist/repl/tui/editor.d.ts.map +1 -0
- package/dist/repl/tui/editor.js +39 -0
- package/dist/repl/tui/editor.js.map +1 -0
- package/dist/repl/tui/status-bar.d.ts +26 -0
- package/dist/repl/tui/status-bar.d.ts.map +1 -0
- package/dist/repl/tui/status-bar.js +47 -0
- package/dist/repl/tui/status-bar.js.map +1 -0
- package/dist/repl/tui/theme.d.ts +30 -0
- package/dist/repl/tui/theme.d.ts.map +1 -0
- package/dist/repl/tui/theme.js +81 -0
- package/dist/repl/tui/theme.js.map +1 -0
- package/dist/repl/tui/transcript.d.ts +39 -0
- package/dist/repl/tui/transcript.d.ts.map +1 -0
- package/dist/repl/tui/transcript.js +123 -0
- package/dist/repl/tui/transcript.js.map +1 -0
- package/dist/runner.d.ts +132 -13
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +434 -277
- package/dist/runner.js.map +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +0 -32
- package/dist/sdk.js.map +1 -1
- package/dist/system/assets/docs/AGENT_PROMPT.md +1373 -0
- package/dist/system/assets/docs/DML_AUTHORING.md +195 -0
- package/dist/system/assets/docs/DML_REFERENCE.md +312 -72
- package/dist/system/assets/docs/HARNESS_EXAMPLES.md +172 -0
- package/dist/system/assets/docs/HARNESS_PATTERNS.md +562 -0
- package/dist/system/assets/docs/META_PROMPT.md +68 -0
- package/dist/system/assets/docs/PROLOG_SNIPPETS.md +117 -0
- package/dist/system/assets/index.d.ts +9 -17
- package/dist/system/assets/index.d.ts.map +1 -1
- package/dist/system/assets/index.js +26 -85
- package/dist/system/assets/index.js.map +1 -1
- package/dist/system/runtime/agentvm-manager.d.ts +9 -2
- package/dist/system/runtime/agentvm-manager.d.ts.map +1 -1
- package/dist/system/runtime/agentvm-manager.js +70 -44
- package/dist/system/runtime/agentvm-manager.js.map +1 -1
- package/dist/system/runtime/media.d.ts +23 -0
- package/dist/system/runtime/media.d.ts.map +1 -0
- package/dist/system/runtime/media.js +55 -0
- package/dist/system/runtime/media.js.map +1 -0
- package/dist/system/runtime/shell-limits.d.ts +21 -0
- package/dist/system/runtime/shell-limits.d.ts.map +1 -0
- package/dist/system/runtime/shell-limits.js +39 -0
- package/dist/system/runtime/shell-limits.js.map +1 -0
- package/dist/system/runtime/shell-manager.d.ts +8 -3
- package/dist/system/runtime/shell-manager.d.ts.map +1 -1
- package/dist/system/runtime/shell-manager.js +56 -39
- package/dist/system/runtime/shell-manager.js.map +1 -1
- package/dist/system/runtime/shell-tool-events.js +5 -0
- package/dist/system/runtime/shell-tool-events.js.map +1 -1
- package/dist/system/runtime/token-usage.d.ts +2 -0
- package/dist/system/runtime/token-usage.d.ts.map +1 -1
- package/dist/system/runtime/token-usage.js +7 -0
- package/dist/system/runtime/token-usage.js.map +1 -1
- package/dist/tools.d.ts +3 -21
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +14 -168
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +80 -118
- package/dist/types.d.ts.map +1 -1
- package/dist/validate.d.ts +19 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +338 -0
- package/dist/validate.js.map +1 -0
- package/dist/visualize/index.d.ts +52 -0
- package/dist/visualize/index.d.ts.map +1 -0
- package/dist/visualize/index.js +189 -0
- package/dist/visualize/index.js.map +1 -0
- package/package.json +17 -15
- package/src/prolog-src/deepclause_analysis.pl +10 -2
- package/src/prolog-src/deepclause_mi.pl +2245 -41
- package/dist/cli/commands.d.ts +0 -42
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -246
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/compile.d.ts +0 -99
- package/dist/cli/compile.d.ts.map +0 -1
- package/dist/cli/compile.js +0 -363
- package/dist/cli/compile.js.map +0 -1
- package/dist/cli/default-skills.d.ts +0 -10
- package/dist/cli/default-skills.d.ts.map +0 -1
- package/dist/cli/default-skills.js +0 -206
- package/dist/cli/default-skills.js.map +0 -1
- package/dist/cli/interactive.d.ts +0 -2
- package/dist/cli/interactive.d.ts.map +0 -1
- package/dist/cli/interactive.js +0 -20
- package/dist/cli/interactive.js.map +0 -1
- package/dist/cli/mcp.d.ts +0 -56
- package/dist/cli/mcp.d.ts.map +0 -1
- package/dist/cli/mcp.js +0 -138
- package/dist/cli/mcp.js.map +0 -1
- package/dist/cli/prompt.d.ts +0 -2
- package/dist/cli/prompt.d.ts.map +0 -1
- package/dist/cli/prompt.js +0 -2
- package/dist/cli/prompt.js.map +0 -1
- package/dist/cli/search.d.ts +0 -19
- package/dist/cli/search.d.ts.map +0 -1
- package/dist/cli/search.js +0 -189
- package/dist/cli/search.js.map +0 -1
- package/dist/cli/tool-args.d.ts +0 -2
- package/dist/cli/tool-args.d.ts.map +0 -1
- package/dist/cli/tool-args.js +0 -39
- package/dist/cli/tool-args.js.map +0 -1
- package/dist/cli/tools.d.ts +0 -36
- package/dist/cli/tools.d.ts.map +0 -1
- package/dist/cli/tools.js +0 -229
- package/dist/cli/tools.js.map +0 -1
- package/dist/cli/tui/index.d.ts +0 -22
- package/dist/cli/tui/index.d.ts.map +0 -1
- package/dist/cli/tui/index.js +0 -29
- package/dist/cli/tui/index.js.map +0 -1
- package/dist/cli/tui.d.ts +0 -167
- package/dist/cli/tui.d.ts.map +0 -1
- package/dist/cli/tui.js +0 -5228
- package/dist/cli/tui.js.map +0 -1
- package/dist/compiler_prompt.d.ts +0 -20
- package/dist/compiler_prompt.d.ts.map +0 -1
- package/dist/compiler_prompt.js +0 -870
- package/dist/compiler_prompt.js.map +0 -1
- package/dist/deepclause-cli.skill +0 -0
- package/dist/prolog-src/deepclause_mi.pl.bak +0 -570
- package/dist/system/assets/docs/CONDUCTOR_PROMPT.md +0 -105
- package/dist/system/assets/docs/DML_COMPILER_PROMPT.md +0 -700
- package/dist/system/assets/docs/TUI.md +0 -150
- package/dist/system/assets/recipes/deepclause-coding-workflow/SKILL.md +0 -100
- package/dist/system/assets/skills/conductor.dml +0 -76
- package/dist/system/assets/skills/deep-planner.dml +0 -412
- package/dist/system/assets/skills/plan.dml +0 -429
- package/dist/system/assets/skills/security-planner.dml +0 -337
- package/dist/system/assets/skills/skill-creator.dml +0 -187
- package/dist/system/runtime/catalog-recipes.d.ts +0 -24
- package/dist/system/runtime/catalog-recipes.d.ts.map +0 -1
- package/dist/system/runtime/catalog-recipes.js +0 -273
- package/dist/system/runtime/catalog-recipes.js.map +0 -1
- package/dist/system/runtime/catalog-skills.d.ts +0 -50
- package/dist/system/runtime/catalog-skills.d.ts.map +0 -1
- package/dist/system/runtime/catalog-skills.js +0 -152
- package/dist/system/runtime/catalog-skills.js.map +0 -1
- package/dist/system/runtime/conductor.d.ts +0 -105
- package/dist/system/runtime/conductor.d.ts.map +0 -1
- package/dist/system/runtime/conductor.js +0 -732
- package/dist/system/runtime/conductor.js.map +0 -1
- package/dist/system/runtime/console-capture.d.ts +0 -9
- package/dist/system/runtime/console-capture.d.ts.map +0 -1
- package/dist/system/runtime/console-capture.js +0 -67
- package/dist/system/runtime/console-capture.js.map +0 -1
- package/dist/system/runtime/dml-executor.d.ts +0 -52
- package/dist/system/runtime/dml-executor.d.ts.map +0 -1
- package/dist/system/runtime/dml-executor.js +0 -203
- package/dist/system/runtime/dml-executor.js.map +0 -1
- package/dist/system/runtime/runtime-tools.d.ts +0 -31
- package/dist/system/runtime/runtime-tools.d.ts.map +0 -1
- package/dist/system/runtime/runtime-tools.js +0 -312
- package/dist/system/runtime/runtime-tools.js.map +0 -1
- package/dist/system/runtime/skill-creator.d.ts +0 -38
- package/dist/system/runtime/skill-creator.d.ts.map +0 -1
- package/dist/system/runtime/skill-creator.js +0 -763
- package/dist/system/runtime/skill-creator.js.map +0 -1
- package/src/prolog-src/deepclause_mi.pl.bak +0 -570
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent session / trace / harness history.
|
|
3
|
+
*
|
|
4
|
+
* See `docs/DML_REPL_SESSION_HISTORY.md`. Storage lives under a
|
|
5
|
+
* workspace-relative directory (default `.deepclause/repl/`):
|
|
6
|
+
*
|
|
7
|
+
* history.dml append-only harness_* facts, consulted at
|
|
8
|
+
* session start and queryable from DML
|
|
9
|
+
* harness/HEAD.json current version pointer
|
|
10
|
+
* harness/changes.jsonl one line per harness change
|
|
11
|
+
* harness/versions/NNNN_<ts>.json full module snapshot per version
|
|
12
|
+
* sessions/<id>/session.json session metadata
|
|
13
|
+
* sessions/<id>/turns.jsonl one summary per turn
|
|
14
|
+
* sessions/<id>/events.jsonl runtime event trace
|
|
15
|
+
*
|
|
16
|
+
* The recorder is best-effort: callers swallow its errors so a turn never fails
|
|
17
|
+
* because history could not be written.
|
|
18
|
+
*/
|
|
19
|
+
import { createHash } from 'crypto';
|
|
20
|
+
import * as fs from 'fs/promises';
|
|
21
|
+
import * as path from 'path';
|
|
22
|
+
const DEFAULT_DIR = '.deepclause/repl';
|
|
23
|
+
/** Stable hash of a harness module plus its linked scripts. */
|
|
24
|
+
export function harnessHash(module, scripts) {
|
|
25
|
+
return createHash('sha256')
|
|
26
|
+
.update(module)
|
|
27
|
+
.update('\n')
|
|
28
|
+
.update(JSON.stringify(scripts))
|
|
29
|
+
.digest('hex');
|
|
30
|
+
}
|
|
31
|
+
function nowIso() {
|
|
32
|
+
return new Date().toISOString();
|
|
33
|
+
}
|
|
34
|
+
/** Single-quoted Prolog atom. */
|
|
35
|
+
export function prologAtom(value) {
|
|
36
|
+
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
|
|
37
|
+
}
|
|
38
|
+
export class HistoryStore {
|
|
39
|
+
sessionId;
|
|
40
|
+
harnessName;
|
|
41
|
+
rootRel;
|
|
42
|
+
sessionDirRel;
|
|
43
|
+
rootAbs;
|
|
44
|
+
sessionDirAbs;
|
|
45
|
+
trace;
|
|
46
|
+
maxEventBytes;
|
|
47
|
+
maxSessions;
|
|
48
|
+
maxVersions;
|
|
49
|
+
redactions;
|
|
50
|
+
eventBuffer = [];
|
|
51
|
+
/** Current HEAD version (0 = empty harness) and the highest ever issued. */
|
|
52
|
+
headVersion = 0;
|
|
53
|
+
maxVersion = 0;
|
|
54
|
+
/** Hash recorded for the current HEAD (for session-snapshot consistency). */
|
|
55
|
+
headHash;
|
|
56
|
+
constructor(workspacePath, sessionId, options = {},
|
|
57
|
+
/** Harness this session edits (a project can hold several). */
|
|
58
|
+
harnessName = 'main') {
|
|
59
|
+
this.sessionId = sessionId;
|
|
60
|
+
this.harnessName = harnessName;
|
|
61
|
+
this.rootRel = (options.dir ?? DEFAULT_DIR).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
62
|
+
this.rootAbs = path.resolve(workspacePath, this.rootRel);
|
|
63
|
+
this.sessionDirRel = `${this.rootRel}/sessions/${sessionId}`;
|
|
64
|
+
this.sessionDirAbs = path.resolve(workspacePath, this.sessionDirRel);
|
|
65
|
+
this.trace = options.trace ?? 'summary';
|
|
66
|
+
this.maxEventBytes = options.maxEventBytes ?? 8192;
|
|
67
|
+
this.maxSessions = options.maxSessions ?? 50;
|
|
68
|
+
this.maxVersions = options.maxVersions ?? 200;
|
|
69
|
+
this.redactions = (options.redact ?? []).map((source) => new RegExp(source, 'g'));
|
|
70
|
+
}
|
|
71
|
+
get currentVersion() {
|
|
72
|
+
return this.headVersion;
|
|
73
|
+
}
|
|
74
|
+
get currentHash() {
|
|
75
|
+
return this.headHash;
|
|
76
|
+
}
|
|
77
|
+
get tracePath() {
|
|
78
|
+
return `${this.sessionDirRel}/events.jsonl`;
|
|
79
|
+
}
|
|
80
|
+
get historyFile() {
|
|
81
|
+
return path.join(this.rootAbs, 'history.dml');
|
|
82
|
+
}
|
|
83
|
+
/** Per-harness store: `.deepclause/repl/harnesses/<name>/`. */
|
|
84
|
+
get harnessDir() {
|
|
85
|
+
return path.join(this.rootAbs, 'harnesses', this.harnessName);
|
|
86
|
+
}
|
|
87
|
+
/** The single pre-harnesses store, migrated to `harnesses/main` once. */
|
|
88
|
+
get legacyHarnessDir() {
|
|
89
|
+
return path.join(this.rootAbs, 'harness');
|
|
90
|
+
}
|
|
91
|
+
/** The current content (source of truth; portable as a `.dml`). */
|
|
92
|
+
get harnessFile() {
|
|
93
|
+
return path.join(this.harnessDir, 'harness.dml');
|
|
94
|
+
}
|
|
95
|
+
/** The module the harness started from (version 0 / rollback floor). */
|
|
96
|
+
get baselineFile() {
|
|
97
|
+
return path.join(this.harnessDir, 'baseline.dml');
|
|
98
|
+
}
|
|
99
|
+
get headFile() {
|
|
100
|
+
return path.join(this.harnessDir, 'HEAD.json');
|
|
101
|
+
}
|
|
102
|
+
get changesFile() {
|
|
103
|
+
return path.join(this.harnessDir, 'changes.jsonl');
|
|
104
|
+
}
|
|
105
|
+
/** Whether the harness has any committed versions (HEAD > baseline). */
|
|
106
|
+
get hasCommits() {
|
|
107
|
+
return this.headVersion > 0;
|
|
108
|
+
}
|
|
109
|
+
/** Facts for this harness only (`harness_change`/`changed`/`head`/`rollback`). */
|
|
110
|
+
get harnessFactsFile() {
|
|
111
|
+
return path.join(this.harnessDir, 'history.dml');
|
|
112
|
+
}
|
|
113
|
+
/** Create directories, read the current version, and prune old entries. */
|
|
114
|
+
async init() {
|
|
115
|
+
await fs.mkdir(this.sessionDirAbs, { recursive: true });
|
|
116
|
+
await this.migrateLegacyHarness();
|
|
117
|
+
await fs.mkdir(path.join(this.harnessDir, 'versions'), { recursive: true });
|
|
118
|
+
try {
|
|
119
|
+
const head = JSON.parse(await fs.readFile(this.headFile, 'utf8'));
|
|
120
|
+
if (typeof head.version === 'number')
|
|
121
|
+
this.headVersion = head.version;
|
|
122
|
+
if (typeof head.hash === 'string')
|
|
123
|
+
this.headHash = head.hash;
|
|
124
|
+
this.maxVersion = typeof head.maxVersion === 'number' ? head.maxVersion : this.headVersion;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// no HEAD yet
|
|
128
|
+
}
|
|
129
|
+
// A harness always has a baseline (empty for a fresh one) and a current file.
|
|
130
|
+
await this.ensureHarnessFiles();
|
|
131
|
+
await this.prune();
|
|
132
|
+
}
|
|
133
|
+
/** Move the legacy single `harness/` store into `harnesses/main/` (once). */
|
|
134
|
+
async migrateLegacyHarness() {
|
|
135
|
+
if (this.harnessName !== 'main')
|
|
136
|
+
return;
|
|
137
|
+
try {
|
|
138
|
+
await fs.access(path.join(this.legacyHarnessDir, 'HEAD.json'));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return; // nothing to migrate
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
await fs.access(this.harnessDir);
|
|
145
|
+
return; // the per-harness dir already exists
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// proceed
|
|
149
|
+
}
|
|
150
|
+
await fs.mkdir(path.dirname(this.harnessDir), { recursive: true });
|
|
151
|
+
await fs.rename(this.legacyHarnessDir, this.harnessDir);
|
|
152
|
+
}
|
|
153
|
+
/** Ensure `baseline.dml` and `harness.dml` exist (materializing from HEAD). */
|
|
154
|
+
async ensureHarnessFiles() {
|
|
155
|
+
let baseline = '';
|
|
156
|
+
try {
|
|
157
|
+
baseline = await fs.readFile(this.baselineFile, 'utf8');
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
await fs.writeFile(this.baselineFile, '', 'utf8');
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const current = await fs.readFile(this.harnessFile, 'utf8');
|
|
164
|
+
if (current.trim() || this.headVersion <= 0)
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// write below
|
|
169
|
+
}
|
|
170
|
+
const head = this.headVersion > 0 ? await this.readVersion(this.headVersion) : null;
|
|
171
|
+
await fs.writeFile(this.harnessFile, head?.module ?? baseline, 'utf8');
|
|
172
|
+
}
|
|
173
|
+
/** The module at HEAD: the live `harness.dml`, or the HEAD version snapshot. */
|
|
174
|
+
async readHeadModule() {
|
|
175
|
+
let module = '';
|
|
176
|
+
try {
|
|
177
|
+
module = await fs.readFile(this.harnessFile, 'utf8');
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
module = '';
|
|
181
|
+
}
|
|
182
|
+
if (module.trim())
|
|
183
|
+
return { version: this.headVersion, module };
|
|
184
|
+
if (this.headVersion > 0) {
|
|
185
|
+
const current = await this.readVersion(this.headVersion);
|
|
186
|
+
if (current?.module.trim())
|
|
187
|
+
return { version: this.headVersion, module: current.module };
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
/** The module this harness started from (version 0). */
|
|
192
|
+
async readBaseline() {
|
|
193
|
+
try {
|
|
194
|
+
return await fs.readFile(this.baselineFile, 'utf8');
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return '';
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Establish the baseline of a fresh harness from its first content (e.g. a
|
|
202
|
+
* harness file loaded with `-l`). No-op once the harness has a baseline or
|
|
203
|
+
* any committed version.
|
|
204
|
+
*/
|
|
205
|
+
async setInitialBaseline(module) {
|
|
206
|
+
if (this.headVersion > 0)
|
|
207
|
+
return;
|
|
208
|
+
if ((await this.readBaseline()).trim())
|
|
209
|
+
return;
|
|
210
|
+
await fs.mkdir(this.harnessDir, { recursive: true });
|
|
211
|
+
await fs.writeFile(this.baselineFile, module, 'utf8');
|
|
212
|
+
await fs.writeFile(this.harnessFile, module, 'utf8');
|
|
213
|
+
}
|
|
214
|
+
/** The persisted facts, to be loaded into the module at session start. */
|
|
215
|
+
async readFacts() {
|
|
216
|
+
const read = async (file) => {
|
|
217
|
+
try {
|
|
218
|
+
return await fs.readFile(file, 'utf8');
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return '';
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
const [workspace, harness] = await Promise.all([
|
|
225
|
+
read(this.historyFile),
|
|
226
|
+
read(this.harnessFactsFile),
|
|
227
|
+
]);
|
|
228
|
+
return `${workspace}${harness}`;
|
|
229
|
+
}
|
|
230
|
+
/** The turns recorded for a session, oldest first (used for input history). */
|
|
231
|
+
async readTurns(sessionId = this.sessionId) {
|
|
232
|
+
const file = path.join(this.sessionsDir(), sessionId, 'turns.jsonl');
|
|
233
|
+
let text;
|
|
234
|
+
try {
|
|
235
|
+
text = await fs.readFile(file, 'utf8');
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
const turns = [];
|
|
241
|
+
for (const line of text.split('\n')) {
|
|
242
|
+
if (!line.trim())
|
|
243
|
+
continue;
|
|
244
|
+
try {
|
|
245
|
+
const parsed = JSON.parse(line);
|
|
246
|
+
if (typeof parsed?.request === 'string')
|
|
247
|
+
turns.push(parsed);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// skip malformed lines
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return turns;
|
|
254
|
+
}
|
|
255
|
+
async startSession(info, options = {}) {
|
|
256
|
+
const sessionFile = path.join(this.sessionDirAbs, 'session.json');
|
|
257
|
+
if (options.resume) {
|
|
258
|
+
const resumedAt = nowIso();
|
|
259
|
+
const facts = [
|
|
260
|
+
`harness_session_resume(${prologAtom(this.sessionId)}, ${prologAtom(resumedAt)}).`,
|
|
261
|
+
];
|
|
262
|
+
await this.appendFacts(facts);
|
|
263
|
+
let existing = {};
|
|
264
|
+
try {
|
|
265
|
+
existing = JSON.parse(await fs.readFile(sessionFile, 'utf8'));
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// first resume of a session that never wrote session.json
|
|
269
|
+
}
|
|
270
|
+
const previous = Array.isArray(existing.resumedAt) ? existing.resumedAt : [];
|
|
271
|
+
await this.writeJson(sessionFile, {
|
|
272
|
+
...existing,
|
|
273
|
+
sessionId: this.sessionId,
|
|
274
|
+
model: info.model,
|
|
275
|
+
backend: info.backend,
|
|
276
|
+
traceDir: this.sessionDirRel,
|
|
277
|
+
endedAt: null,
|
|
278
|
+
exit: null,
|
|
279
|
+
resumedAt: [...previous, resumedAt],
|
|
280
|
+
});
|
|
281
|
+
return facts;
|
|
282
|
+
}
|
|
283
|
+
const startedAt = nowIso();
|
|
284
|
+
const facts = [
|
|
285
|
+
`harness_session(${prologAtom(this.sessionId)}, ${prologAtom(startedAt)}, `
|
|
286
|
+
+ `${prologAtom(info.model)}, ${prologAtom(info.backend)}, ${prologAtom(this.sessionDirRel)}).`,
|
|
287
|
+
];
|
|
288
|
+
await this.appendFacts(facts);
|
|
289
|
+
await this.writeJson(sessionFile, {
|
|
290
|
+
sessionId: this.sessionId,
|
|
291
|
+
startedAt,
|
|
292
|
+
endedAt: null,
|
|
293
|
+
exit: null,
|
|
294
|
+
model: info.model,
|
|
295
|
+
backend: info.backend,
|
|
296
|
+
traceDir: this.sessionDirRel,
|
|
297
|
+
});
|
|
298
|
+
return facts;
|
|
299
|
+
}
|
|
300
|
+
async endSession(reason) {
|
|
301
|
+
await this.flushEvents();
|
|
302
|
+
const endedAt = nowIso();
|
|
303
|
+
const facts = [`harness_session_end(${prologAtom(this.sessionId)}, ${prologAtom(endedAt)}).`];
|
|
304
|
+
await this.appendFacts(facts);
|
|
305
|
+
const sessionFile = path.join(this.sessionDirAbs, 'session.json');
|
|
306
|
+
try {
|
|
307
|
+
const existing = JSON.parse(await fs.readFile(sessionFile, 'utf8'));
|
|
308
|
+
await this.writeJson(sessionFile, { ...existing, endedAt, exit: reason });
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// best effort
|
|
312
|
+
}
|
|
313
|
+
return facts;
|
|
314
|
+
}
|
|
315
|
+
async recordTurn(record) {
|
|
316
|
+
const ts = nowIso();
|
|
317
|
+
const facts = [
|
|
318
|
+
`harness_turn(${prologAtom(this.sessionId)}, ${record.turn}, ${prologAtom(ts)}, `
|
|
319
|
+
+ `${prologAtom(record.mode)}, ${prologAtom(record.outcome)}).`,
|
|
320
|
+
`harness_trace(${prologAtom(this.sessionId)}, ${record.turn}, ${prologAtom(this.tracePath)}).`,
|
|
321
|
+
];
|
|
322
|
+
await this.appendFacts(facts);
|
|
323
|
+
await this.appendJsonl(path.join(this.sessionDirAbs, 'turns.jsonl'), { ts, ...record });
|
|
324
|
+
return facts;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Record a harness version. Returns the facts to assert and the new version.
|
|
328
|
+
*/
|
|
329
|
+
async recordChange(record) {
|
|
330
|
+
const ts = nowIso();
|
|
331
|
+
const hash = harnessHash(record.module, record.scripts);
|
|
332
|
+
// Committing content identical to HEAD is a no-op: do not burn a version.
|
|
333
|
+
if (hash === this.headHash && this.headVersion > 0) {
|
|
334
|
+
return { facts: [], version: this.headVersion };
|
|
335
|
+
}
|
|
336
|
+
const version = this.maxVersion + 1;
|
|
337
|
+
this.maxVersion = version;
|
|
338
|
+
const parentVersion = this.headVersion;
|
|
339
|
+
const facts = [
|
|
340
|
+
`harness_change(${version}, ${prologAtom(ts)}, ${prologAtom(record.kind)}, `
|
|
341
|
+
+ `${prologAtom(record.sessionId)}, ${record.turn}).`,
|
|
342
|
+
...record.changed.map((change) => {
|
|
343
|
+
const [name, arity] = splitPredicate(change.predicate);
|
|
344
|
+
return `harness_changed(${version}, ${prologAtom(name)}, ${arity}, ${prologAtom(change.action)}).`;
|
|
345
|
+
}),
|
|
346
|
+
`harness_head(${version}, ${prologAtom(ts)}, ${prologAtom(record.sessionId)}, ${record.turn}).`,
|
|
347
|
+
];
|
|
348
|
+
await this.appendHarnessFacts(facts);
|
|
349
|
+
await this.appendJsonl(this.changesFile, {
|
|
350
|
+
version,
|
|
351
|
+
ts,
|
|
352
|
+
sessionId: record.sessionId,
|
|
353
|
+
turn: record.turn,
|
|
354
|
+
kind: record.kind,
|
|
355
|
+
hash,
|
|
356
|
+
changed: record.changed,
|
|
357
|
+
scripts: record.scripts,
|
|
358
|
+
});
|
|
359
|
+
await this.writeJson(path.join(this.harnessDir, 'versions', `${String(version).padStart(4, '0')}_${fileStamp(ts)}.json`), {
|
|
360
|
+
version,
|
|
361
|
+
ts,
|
|
362
|
+
parentVersion: parentVersion > 0 ? parentVersion : null,
|
|
363
|
+
kind: record.kind,
|
|
364
|
+
sessionId: record.sessionId,
|
|
365
|
+
turn: record.turn,
|
|
366
|
+
hash,
|
|
367
|
+
changed: record.changed,
|
|
368
|
+
scripts: record.scripts,
|
|
369
|
+
module: record.module,
|
|
370
|
+
manifest: record.manifest,
|
|
371
|
+
});
|
|
372
|
+
await this.writeJson(this.headFile, {
|
|
373
|
+
version,
|
|
374
|
+
maxVersion: this.maxVersion,
|
|
375
|
+
ts,
|
|
376
|
+
sessionId: record.sessionId,
|
|
377
|
+
turn: record.turn,
|
|
378
|
+
hash,
|
|
379
|
+
});
|
|
380
|
+
// Keep the live harness file in step with HEAD (it is the source of truth).
|
|
381
|
+
await fs.writeFile(this.harnessFile, record.module, 'utf8');
|
|
382
|
+
this.headVersion = version;
|
|
383
|
+
this.headHash = hash;
|
|
384
|
+
return { facts, version };
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Plan a rollback to the parent of HEAD. Returns null when there is nothing
|
|
388
|
+
* to undo, otherwise the module source to restore (floored at the baseline).
|
|
389
|
+
*/
|
|
390
|
+
async prepareRollback() {
|
|
391
|
+
if (this.headVersion <= 0)
|
|
392
|
+
return null;
|
|
393
|
+
const current = await this.readVersion(this.headVersion);
|
|
394
|
+
const to = current?.parentVersion ?? Math.max(this.headVersion - 1, 0);
|
|
395
|
+
const module = to > 0 ? (await this.readVersion(to))?.module ?? '' : await this.readBaseline();
|
|
396
|
+
return { from: this.headVersion, to, module };
|
|
397
|
+
}
|
|
398
|
+
/** Persist a rollback (facts + HEAD + change log + harness file). */
|
|
399
|
+
async recordRollback(from, to, turn, sessionId, module) {
|
|
400
|
+
const ts = nowIso();
|
|
401
|
+
const facts = [
|
|
402
|
+
`harness_rollback(${from}, ${to}, ${prologAtom(ts)}, ${prologAtom(sessionId)}, ${turn}).`,
|
|
403
|
+
`harness_head(${to}, ${prologAtom(ts)}, ${prologAtom(sessionId)}, ${turn}).`,
|
|
404
|
+
];
|
|
405
|
+
await this.appendHarnessFacts(facts);
|
|
406
|
+
await this.appendJsonl(this.changesFile, { ts, sessionId, turn, kind: 'rollback', from, to });
|
|
407
|
+
await this.writeJson(this.headFile, {
|
|
408
|
+
version: to,
|
|
409
|
+
maxVersion: this.maxVersion,
|
|
410
|
+
ts,
|
|
411
|
+
sessionId,
|
|
412
|
+
turn,
|
|
413
|
+
rollback: true,
|
|
414
|
+
});
|
|
415
|
+
if (module !== undefined) {
|
|
416
|
+
await fs.writeFile(this.harnessFile, module, 'utf8');
|
|
417
|
+
}
|
|
418
|
+
this.headVersion = to;
|
|
419
|
+
// The hash no longer matches HEAD's content until the next commit.
|
|
420
|
+
this.headHash = undefined;
|
|
421
|
+
return facts;
|
|
422
|
+
}
|
|
423
|
+
async readVersion(version) {
|
|
424
|
+
const dir = path.join(this.harnessDir, 'versions');
|
|
425
|
+
try {
|
|
426
|
+
const prefix = `${String(version).padStart(4, '0')}_`;
|
|
427
|
+
const name = (await fs.readdir(dir)).find((entry) => entry.startsWith(prefix));
|
|
428
|
+
if (!name)
|
|
429
|
+
return null;
|
|
430
|
+
const data = JSON.parse(await fs.readFile(path.join(dir, name), 'utf8'));
|
|
431
|
+
return { module: data.module ?? '', parentVersion: data.parentVersion ?? null };
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// ---- Session lookup & durable harness snapshots (resume) ----
|
|
438
|
+
sessionsDir() {
|
|
439
|
+
return path.join(this.rootAbs, 'sessions');
|
|
440
|
+
}
|
|
441
|
+
sessionHarnessFile(sessionId) {
|
|
442
|
+
return path.join(this.sessionsDir(), sessionId, 'harness.json');
|
|
443
|
+
}
|
|
444
|
+
/** True when a session directory exists for `sessionId`. */
|
|
445
|
+
async sessionExists(sessionId) {
|
|
446
|
+
try {
|
|
447
|
+
return (await fs.stat(path.join(this.sessionsDir(), sessionId))).isDirectory();
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/** All recorded sessions, oldest first. */
|
|
454
|
+
async sessionSummaries() {
|
|
455
|
+
let ids;
|
|
456
|
+
try {
|
|
457
|
+
ids = await fs.readdir(this.sessionsDir());
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
return [];
|
|
461
|
+
}
|
|
462
|
+
const summaries = [];
|
|
463
|
+
for (const id of ids) {
|
|
464
|
+
let meta = {};
|
|
465
|
+
try {
|
|
466
|
+
meta = JSON.parse(await fs.readFile(path.join(this.sessionsDir(), id, 'session.json'), 'utf8'));
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
// session without metadata (e.g. history disabled mid-way)
|
|
470
|
+
}
|
|
471
|
+
summaries.push({
|
|
472
|
+
id,
|
|
473
|
+
startedAt: typeof meta.startedAt === 'string' ? meta.startedAt : undefined,
|
|
474
|
+
endedAt: typeof meta.endedAt === 'string' ? meta.endedAt : null,
|
|
475
|
+
resumedAt: Array.isArray(meta.resumedAt) ? meta.resumedAt : undefined,
|
|
476
|
+
lastTurn: await this.lastTurn(id),
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
summaries.sort((a, b) => (a.startedAt ?? '').localeCompare(b.startedAt ?? ''));
|
|
480
|
+
return summaries;
|
|
481
|
+
}
|
|
482
|
+
/** The most recently started session, or null when there is none. */
|
|
483
|
+
async latestSessionId() {
|
|
484
|
+
const summaries = await this.sessionSummaries();
|
|
485
|
+
return summaries.length > 0 ? summaries[summaries.length - 1].id : null;
|
|
486
|
+
}
|
|
487
|
+
/** The highest turn number recorded for a session (0 when none). */
|
|
488
|
+
async lastTurn(sessionId = this.sessionId) {
|
|
489
|
+
const file = path.join(this.sessionsDir(), sessionId, 'turns.jsonl');
|
|
490
|
+
let text;
|
|
491
|
+
try {
|
|
492
|
+
text = await fs.readFile(file, 'utf8');
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
return 0;
|
|
496
|
+
}
|
|
497
|
+
const lines = text.split('\n').filter((line) => line.trim());
|
|
498
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
499
|
+
try {
|
|
500
|
+
const record = JSON.parse(lines[index]);
|
|
501
|
+
if (typeof record.turn === 'number')
|
|
502
|
+
return record.turn;
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
// skip a malformed line
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return 0;
|
|
509
|
+
}
|
|
510
|
+
/** Persist the durable harness snapshot beside a session (best effort). */
|
|
511
|
+
async writeSessionHarness(snapshot, sessionId = this.sessionId) {
|
|
512
|
+
await this.writeJson(this.sessionHarnessFile(sessionId), snapshot);
|
|
513
|
+
}
|
|
514
|
+
/** Read a session's durable harness snapshot, or null when absent. */
|
|
515
|
+
async readSessionHarness(sessionId = this.sessionId) {
|
|
516
|
+
try {
|
|
517
|
+
const data = JSON.parse(await fs.readFile(this.sessionHarnessFile(sessionId), 'utf8'));
|
|
518
|
+
return typeof data?.module === 'string' ? data : null;
|
|
519
|
+
}
|
|
520
|
+
catch {
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/** Buffer one event line (flushed at turn end / dispose). */
|
|
525
|
+
recordEvent(context, event) {
|
|
526
|
+
if (this.trace === 'off')
|
|
527
|
+
return;
|
|
528
|
+
try {
|
|
529
|
+
const line = this.serializeEvent(context, event);
|
|
530
|
+
if (!line)
|
|
531
|
+
return;
|
|
532
|
+
this.eventBuffer.push(line);
|
|
533
|
+
if (this.eventBuffer.length >= 200)
|
|
534
|
+
void this.flushEvents();
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
// never let tracing break the turn
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async flushEvents() {
|
|
541
|
+
if (this.eventBuffer.length === 0)
|
|
542
|
+
return;
|
|
543
|
+
const payload = `${this.eventBuffer.join('\n')}\n`;
|
|
544
|
+
this.eventBuffer = [];
|
|
545
|
+
await this.appendRaw(path.join(this.sessionDirAbs, 'events.jsonl'), payload);
|
|
546
|
+
}
|
|
547
|
+
serializeEvent(context, event) {
|
|
548
|
+
const base = {
|
|
549
|
+
ts: nowIso(),
|
|
550
|
+
sessionId: this.sessionId,
|
|
551
|
+
turn: context.turn,
|
|
552
|
+
frame: context.frame,
|
|
553
|
+
type: event.type,
|
|
554
|
+
};
|
|
555
|
+
const put = (key, value) => {
|
|
556
|
+
if (value !== undefined && value !== null)
|
|
557
|
+
base[key] = this.scrub(value);
|
|
558
|
+
};
|
|
559
|
+
switch (event.type) {
|
|
560
|
+
case 'stream':
|
|
561
|
+
if (this.trace === 'full')
|
|
562
|
+
put('content', event.content);
|
|
563
|
+
break;
|
|
564
|
+
case 'output':
|
|
565
|
+
case 'answer':
|
|
566
|
+
case 'error':
|
|
567
|
+
case 'log':
|
|
568
|
+
put('content', event.content);
|
|
569
|
+
break;
|
|
570
|
+
case 'tool_call':
|
|
571
|
+
put('toolName', event.toolName);
|
|
572
|
+
put('toolState', event.toolState);
|
|
573
|
+
put('toolSummary', event.toolSummary);
|
|
574
|
+
put('toolArgs', event.toolArgs);
|
|
575
|
+
break;
|
|
576
|
+
case 'solution':
|
|
577
|
+
put('bindings', event.solution?.bindings);
|
|
578
|
+
break;
|
|
579
|
+
case 'usage':
|
|
580
|
+
put('usage', event.usage);
|
|
581
|
+
break;
|
|
582
|
+
case 'task_activity':
|
|
583
|
+
put('taskState', event.taskState);
|
|
584
|
+
put('taskDescription', event.taskDescription);
|
|
585
|
+
break;
|
|
586
|
+
default:
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
return JSON.stringify(base);
|
|
590
|
+
}
|
|
591
|
+
/** Redact and truncate strings recursively. */
|
|
592
|
+
scrub(value) {
|
|
593
|
+
if (typeof value === 'string') {
|
|
594
|
+
let text = value;
|
|
595
|
+
for (const pattern of this.redactions)
|
|
596
|
+
text = text.replace(pattern, '[redacted]');
|
|
597
|
+
if (Buffer.byteLength(text, 'utf8') > this.maxEventBytes) {
|
|
598
|
+
text = `${text.slice(0, this.maxEventBytes)}…[truncated]`;
|
|
599
|
+
}
|
|
600
|
+
return text;
|
|
601
|
+
}
|
|
602
|
+
if (Array.isArray(value))
|
|
603
|
+
return value.map((entry) => this.scrub(entry));
|
|
604
|
+
if (value && typeof value === 'object') {
|
|
605
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, this.scrub(entry)]));
|
|
606
|
+
}
|
|
607
|
+
return value;
|
|
608
|
+
}
|
|
609
|
+
async appendFacts(facts) {
|
|
610
|
+
if (facts.length === 0)
|
|
611
|
+
return;
|
|
612
|
+
await this.appendRaw(this.historyFile, `${facts.join('\n')}\n`);
|
|
613
|
+
}
|
|
614
|
+
async appendHarnessFacts(facts) {
|
|
615
|
+
if (facts.length === 0)
|
|
616
|
+
return;
|
|
617
|
+
await this.appendRaw(this.harnessFactsFile, `${facts.join('\n')}\n`);
|
|
618
|
+
}
|
|
619
|
+
async appendJsonl(file, record) {
|
|
620
|
+
if (this.trace === 'off' && file.endsWith('events.jsonl'))
|
|
621
|
+
return;
|
|
622
|
+
await this.appendRaw(file, `${JSON.stringify(record)}\n`);
|
|
623
|
+
}
|
|
624
|
+
async appendRaw(file, text) {
|
|
625
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
626
|
+
await fs.appendFile(file, text, 'utf8');
|
|
627
|
+
}
|
|
628
|
+
async writeJson(file, value) {
|
|
629
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
630
|
+
await fs.writeFile(file, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
631
|
+
}
|
|
632
|
+
/** Keep only the newest sessions and versions. */
|
|
633
|
+
async prune() {
|
|
634
|
+
await this.pruneDir(path.join(this.rootAbs, 'sessions'), this.maxSessions);
|
|
635
|
+
await this.pruneDir(path.join(this.harnessDir, 'versions'), this.maxVersions);
|
|
636
|
+
}
|
|
637
|
+
async pruneDir(dir, keep) {
|
|
638
|
+
try {
|
|
639
|
+
const entries = await fs.readdir(dir);
|
|
640
|
+
if (entries.length <= keep)
|
|
641
|
+
return;
|
|
642
|
+
const sorted = entries.sort();
|
|
643
|
+
for (const entry of sorted.slice(0, entries.length - keep)) {
|
|
644
|
+
await fs.rm(path.join(dir, entry), { recursive: true, force: true });
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
// best effort
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function splitPredicate(predicate) {
|
|
653
|
+
const slash = predicate.lastIndexOf('/');
|
|
654
|
+
if (slash === -1)
|
|
655
|
+
return [predicate, 0];
|
|
656
|
+
return [predicate.slice(0, slash), Number(predicate.slice(slash + 1)) || 0];
|
|
657
|
+
}
|
|
658
|
+
function fileStamp(iso) {
|
|
659
|
+
return iso.replace(/[:.]/g, '-');
|
|
660
|
+
}
|
|
661
|
+
//# sourceMappingURL=history.js.map
|