@nxuss/lemma 1.44.0 → 1.45.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 +25 -7
- package/bin/brain-ingest.js +30 -13
- package/dist/cjs/cli/hostHooks.d.ts +38 -7
- package/dist/cjs/cli/hostHooks.d.ts.map +1 -1
- package/dist/cjs/cli/hostHooks.js +189 -9
- package/dist/cjs/cli/hostHooks.js.map +1 -1
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +13 -16
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/cloud/billing/stripeApi.d.ts.map +1 -1
- package/dist/cjs/cloud/billing/stripeApi.js +6 -1
- package/dist/cjs/cloud/billing/stripeApi.js.map +1 -1
- package/dist/cjs/cloud/billing/stripeWebhook.d.ts.map +1 -1
- package/dist/cjs/cloud/billing/stripeWebhook.js +5 -0
- package/dist/cjs/cloud/billing/stripeWebhook.js.map +1 -1
- package/dist/cjs/cloud/console/consoleRouter.d.ts +2 -2
- package/dist/cjs/cloud/console/consoleRouter.d.ts.map +1 -1
- package/dist/cjs/cloud/console/consoleRouter.js +39 -1
- package/dist/cjs/cloud/console/consoleRouter.js.map +1 -1
- package/dist/cjs/cloud/identity/PlanPolicy.d.ts +26 -5
- package/dist/cjs/cloud/identity/PlanPolicy.d.ts.map +1 -1
- package/dist/cjs/cloud/identity/PlanPolicy.js +56 -16
- package/dist/cjs/cloud/identity/PlanPolicy.js.map +1 -1
- package/dist/cjs/cloud/identity/TenantResolver.d.ts +2 -0
- package/dist/cjs/cloud/identity/TenantResolver.d.ts.map +1 -1
- package/dist/cjs/cloud/identity/TenantResolver.js +1 -1
- package/dist/cjs/cloud/identity/TenantResolver.js.map +1 -1
- package/dist/cjs/cloud/identity/authenticateV2.js +1 -1
- package/dist/cjs/cloud/identity/authenticateV2.js.map +1 -1
- package/dist/cjs/cloud/identity/identityStore.d.ts +11 -0
- package/dist/cjs/cloud/identity/identityStore.d.ts.map +1 -1
- package/dist/cjs/cloud/identity/identityStore.js +26 -9
- package/dist/cjs/cloud/identity/identityStore.js.map +1 -1
- package/dist/cjs/cloud/metering/PostgresLedgerStore.d.ts +3 -1
- package/dist/cjs/cloud/metering/PostgresLedgerStore.d.ts.map +1 -1
- package/dist/cjs/cloud/metering/PostgresLedgerStore.js +12 -0
- package/dist/cjs/cloud/metering/PostgresLedgerStore.js.map +1 -1
- package/dist/cjs/cloud/metering/UsageLedger.d.ts +37 -0
- package/dist/cjs/cloud/metering/UsageLedger.d.ts.map +1 -1
- package/dist/cjs/cloud/metering/UsageLedger.js +42 -0
- package/dist/cjs/cloud/metering/UsageLedger.js.map +1 -1
- package/dist/cjs/cloud/store/FileBrainStore.d.ts +5 -1
- package/dist/cjs/cloud/store/FileBrainStore.d.ts.map +1 -1
- package/dist/cjs/cloud/store/FileBrainStore.js +5 -0
- package/dist/cjs/cloud/store/FileBrainStore.js.map +1 -1
- package/dist/cjs/cloud/store/PostgresBrainStore.d.ts +10 -1
- package/dist/cjs/cloud/store/PostgresBrainStore.d.ts.map +1 -1
- package/dist/cjs/cloud/store/PostgresBrainStore.js +28 -0
- package/dist/cjs/cloud/store/PostgresBrainStore.js.map +1 -1
- package/dist/cjs/cloud/store/memorySummary.d.ts +29 -0
- package/dist/cjs/cloud/store/memorySummary.d.ts.map +1 -0
- package/dist/cjs/cloud/store/memorySummary.js +66 -0
- package/dist/cjs/cloud/store/memorySummary.js.map +1 -0
- package/dist/cjs/cloud/v2/routes/admin.d.ts.map +1 -1
- package/dist/cjs/cloud/v2/routes/admin.js +20 -5
- package/dist/cjs/cloud/v2/routes/admin.js.map +1 -1
- package/dist/cjs/cloud/v2/routes/memories.js +1 -1
- package/dist/cjs/cloud/v2/routes/memories.js.map +1 -1
- package/dist/cjs/cloud/v2/routes/tenant.d.ts +1 -0
- package/dist/cjs/cloud/v2/routes/tenant.d.ts.map +1 -1
- package/dist/cjs/cloud/v2/routes/tenant.js +4 -1
- package/dist/cjs/cloud/v2/routes/tenant.js.map +1 -1
- package/dist/cjs/contracts/api-v2.types.d.ts +2 -0
- package/dist/cjs/contracts/api-v2.types.d.ts.map +1 -1
- package/dist/cjs/contracts/store.d.ts +35 -0
- package/dist/cjs/contracts/store.d.ts.map +1 -1
- package/dist/cjs/mcp/index.js +4 -1
- package/dist/cjs/mcp/index.js.map +1 -1
- package/dist/cjs/mcp/tools/misc.d.ts.map +1 -1
- package/dist/cjs/mcp/tools/misc.js +2 -1
- package/dist/cjs/mcp/tools/misc.js.map +1 -1
- package/dist/cjs/mcp/tools/workspace.d.ts.map +1 -1
- package/dist/cjs/mcp/tools/workspace.js +4 -1
- package/dist/cjs/mcp/tools/workspace.js.map +1 -1
- package/dist/cjs/mcp/tools.d.ts +10 -0
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +47 -12
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/subconscious/HostRecall.d.ts.map +1 -1
- package/dist/cjs/subconscious/HostRecall.js +11 -4
- package/dist/cjs/subconscious/HostRecall.js.map +1 -1
- package/dist/cjs/utils/FixCapture.d.ts +7 -5
- package/dist/cjs/utils/FixCapture.d.ts.map +1 -1
- package/dist/cjs/utils/FixCapture.js +64 -32
- package/dist/cjs/utils/FixCapture.js.map +1 -1
- package/dist/cjs/utils/NoTerminal.d.ts +27 -0
- package/dist/cjs/utils/NoTerminal.d.ts.map +1 -0
- package/dist/cjs/utils/NoTerminal.js +37 -0
- package/dist/cjs/utils/NoTerminal.js.map +1 -0
- package/dist/cjs/utils/OpenCodeSession.d.ts +70 -0
- package/dist/cjs/utils/OpenCodeSession.d.ts.map +1 -0
- package/dist/cjs/utils/OpenCodeSession.js +154 -0
- package/dist/cjs/utils/OpenCodeSession.js.map +1 -0
- package/dist/cjs/utils/SessionCapture.d.ts +29 -7
- package/dist/cjs/utils/SessionCapture.d.ts.map +1 -1
- package/dist/cjs/utils/SessionCapture.js +180 -32
- package/dist/cjs/utils/SessionCapture.js.map +1 -1
- package/dist/cjs/utils/TestAutofixInterceptor.d.ts.map +1 -1
- package/dist/cjs/utils/TestAutofixInterceptor.js +2 -0
- package/dist/cjs/utils/TestAutofixInterceptor.js.map +1 -1
- package/dist/esm/cli/hostHooks.d.ts +38 -7
- package/dist/esm/cli/hostHooks.d.ts.map +1 -1
- package/dist/esm/cli/hostHooks.js +187 -8
- package/dist/esm/cli/hostHooks.js.map +1 -1
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +14 -17
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/cloud/billing/stripeApi.d.ts.map +1 -1
- package/dist/esm/cloud/billing/stripeApi.js +6 -1
- package/dist/esm/cloud/billing/stripeApi.js.map +1 -1
- package/dist/esm/cloud/billing/stripeWebhook.d.ts.map +1 -1
- package/dist/esm/cloud/billing/stripeWebhook.js +5 -0
- package/dist/esm/cloud/billing/stripeWebhook.js.map +1 -1
- package/dist/esm/cloud/console/consoleRouter.d.ts +2 -2
- package/dist/esm/cloud/console/consoleRouter.d.ts.map +1 -1
- package/dist/esm/cloud/console/consoleRouter.js +39 -1
- package/dist/esm/cloud/console/consoleRouter.js.map +1 -1
- package/dist/esm/cloud/identity/PlanPolicy.d.ts +26 -5
- package/dist/esm/cloud/identity/PlanPolicy.d.ts.map +1 -1
- package/dist/esm/cloud/identity/PlanPolicy.js +54 -15
- package/dist/esm/cloud/identity/PlanPolicy.js.map +1 -1
- package/dist/esm/cloud/identity/TenantResolver.d.ts +2 -0
- package/dist/esm/cloud/identity/TenantResolver.d.ts.map +1 -1
- package/dist/esm/cloud/identity/TenantResolver.js +1 -1
- package/dist/esm/cloud/identity/TenantResolver.js.map +1 -1
- package/dist/esm/cloud/identity/authenticateV2.js +1 -1
- package/dist/esm/cloud/identity/authenticateV2.js.map +1 -1
- package/dist/esm/cloud/identity/identityStore.d.ts +11 -0
- package/dist/esm/cloud/identity/identityStore.d.ts.map +1 -1
- package/dist/esm/cloud/identity/identityStore.js +26 -9
- package/dist/esm/cloud/identity/identityStore.js.map +1 -1
- package/dist/esm/cloud/metering/PostgresLedgerStore.d.ts +3 -1
- package/dist/esm/cloud/metering/PostgresLedgerStore.d.ts.map +1 -1
- package/dist/esm/cloud/metering/PostgresLedgerStore.js +12 -0
- package/dist/esm/cloud/metering/PostgresLedgerStore.js.map +1 -1
- package/dist/esm/cloud/metering/UsageLedger.d.ts +37 -0
- package/dist/esm/cloud/metering/UsageLedger.d.ts.map +1 -1
- package/dist/esm/cloud/metering/UsageLedger.js +41 -0
- package/dist/esm/cloud/metering/UsageLedger.js.map +1 -1
- package/dist/esm/cloud/store/FileBrainStore.d.ts +5 -1
- package/dist/esm/cloud/store/FileBrainStore.d.ts.map +1 -1
- package/dist/esm/cloud/store/FileBrainStore.js +5 -0
- package/dist/esm/cloud/store/FileBrainStore.js.map +1 -1
- package/dist/esm/cloud/store/PostgresBrainStore.d.ts +10 -1
- package/dist/esm/cloud/store/PostgresBrainStore.d.ts.map +1 -1
- package/dist/esm/cloud/store/PostgresBrainStore.js +28 -0
- package/dist/esm/cloud/store/PostgresBrainStore.js.map +1 -1
- package/dist/esm/cloud/store/memorySummary.d.ts +29 -0
- package/dist/esm/cloud/store/memorySummary.d.ts.map +1 -0
- package/dist/esm/cloud/store/memorySummary.js +60 -0
- package/dist/esm/cloud/store/memorySummary.js.map +1 -0
- package/dist/esm/cloud/v2/routes/admin.d.ts.map +1 -1
- package/dist/esm/cloud/v2/routes/admin.js +20 -5
- package/dist/esm/cloud/v2/routes/admin.js.map +1 -1
- package/dist/esm/cloud/v2/routes/memories.js +1 -1
- package/dist/esm/cloud/v2/routes/memories.js.map +1 -1
- package/dist/esm/cloud/v2/routes/tenant.d.ts +1 -0
- package/dist/esm/cloud/v2/routes/tenant.d.ts.map +1 -1
- package/dist/esm/cloud/v2/routes/tenant.js +4 -1
- package/dist/esm/cloud/v2/routes/tenant.js.map +1 -1
- package/dist/esm/contracts/api-v2.types.d.ts +2 -0
- package/dist/esm/contracts/api-v2.types.d.ts.map +1 -1
- package/dist/esm/contracts/store.d.ts +35 -0
- package/dist/esm/contracts/store.d.ts.map +1 -1
- package/dist/esm/mcp/index.js +5 -2
- package/dist/esm/mcp/index.js.map +1 -1
- package/dist/esm/mcp/tools/misc.d.ts.map +1 -1
- package/dist/esm/mcp/tools/misc.js +2 -1
- package/dist/esm/mcp/tools/misc.js.map +1 -1
- package/dist/esm/mcp/tools/workspace.d.ts.map +1 -1
- package/dist/esm/mcp/tools/workspace.js +4 -1
- package/dist/esm/mcp/tools/workspace.js.map +1 -1
- package/dist/esm/mcp/tools.d.ts +10 -0
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +46 -12
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/subconscious/HostRecall.d.ts.map +1 -1
- package/dist/esm/subconscious/HostRecall.js +11 -4
- package/dist/esm/subconscious/HostRecall.js.map +1 -1
- package/dist/esm/utils/FixCapture.d.ts +7 -5
- package/dist/esm/utils/FixCapture.d.ts.map +1 -1
- package/dist/esm/utils/FixCapture.js +64 -33
- package/dist/esm/utils/FixCapture.js.map +1 -1
- package/dist/esm/utils/NoTerminal.d.ts +27 -0
- package/dist/esm/utils/NoTerminal.d.ts.map +1 -0
- package/dist/esm/utils/NoTerminal.js +33 -0
- package/dist/esm/utils/NoTerminal.js.map +1 -0
- package/dist/esm/utils/OpenCodeSession.d.ts +70 -0
- package/dist/esm/utils/OpenCodeSession.d.ts.map +1 -0
- package/dist/esm/utils/OpenCodeSession.js +146 -0
- package/dist/esm/utils/OpenCodeSession.js.map +1 -0
- package/dist/esm/utils/SessionCapture.d.ts +29 -7
- package/dist/esm/utils/SessionCapture.d.ts.map +1 -1
- package/dist/esm/utils/SessionCapture.js +180 -32
- package/dist/esm/utils/SessionCapture.js.map +1 -1
- package/dist/esm/utils/TestAutofixInterceptor.d.ts.map +1 -1
- package/dist/esm/utils/TestAutofixInterceptor.js +2 -0
- package/dist/esm/utils/TestAutofixInterceptor.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenCode sessions → the hand-off. OpenCode has no SessionEnd hook and no transcript file: its
|
|
4
|
+
* plugin gets the session from the SDK (`client.session.messages`, `client.session.children`) as
|
|
5
|
+
* `{ info, parts }` messages, and subagents are child sessions the `task` tool creates. The plugin
|
|
6
|
+
* `lemma init` installs sends that snapshot here when the session goes idle (and before a
|
|
7
|
+
* compaction); it is rewritten as a Claude Code shaped transcript, subagents beside it in
|
|
8
|
+
* `<name>/subagents/agent-<id>.jsonl` + `.meta.json`, so captureSession builds the same hand-off
|
|
9
|
+
* it builds for Claude Code instead of a second implementation drifting from the first.
|
|
10
|
+
*
|
|
11
|
+
* Measured on OpenCode 1.19.3 (session snapshot from a real `opencode run` with a `task` subagent):
|
|
12
|
+
* tool parts carry `state.input` with `filePath` (read/edit/write) or `command` (bash), and `patch`
|
|
13
|
+
* parts list the files the step changed on disk, which also catches edits made through bash.
|
|
14
|
+
*/
|
|
15
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
16
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
17
|
+
};
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.toClaudeEvents = toClaudeEvents;
|
|
20
|
+
exports.writeClaudeTranscript = writeClaudeTranscript;
|
|
21
|
+
exports.captureOpenCodeSession = captureOpenCodeSession;
|
|
22
|
+
const fs_1 = __importDefault(require("fs"));
|
|
23
|
+
const os_1 = __importDefault(require("os"));
|
|
24
|
+
const path_1 = __importDefault(require("path"));
|
|
25
|
+
const SessionCapture_1 = require("./SessionCapture");
|
|
26
|
+
const HostRecall_1 = require("../subconscious/HostRecall");
|
|
27
|
+
const AtomicFile_1 = require("./AtomicFile");
|
|
28
|
+
const TRANSCRIPT_DIR = path_1.default.join(os_1.default.tmpdir(), 'lemma-opencode');
|
|
29
|
+
const KEEP_MS = 3 * 24 * 60 * 60 * 1000;
|
|
30
|
+
/** OpenCode tool ids → the names SessionCapture knows. MCP tools arrive as `<server>_<tool>`. */
|
|
31
|
+
const TOOL_NAMES = { read: 'Read', edit: 'Edit', write: 'Write', multiedit: 'MultiEdit', bash: 'Bash', task: 'Agent', grep: 'Grep', glob: 'Glob', list: 'LS' };
|
|
32
|
+
const FILE_TOOLS = new Set(['Read', 'Edit', 'Write', 'MultiEdit']);
|
|
33
|
+
function toolName(tool) {
|
|
34
|
+
if (TOOL_NAMES[tool])
|
|
35
|
+
return TOOL_NAMES[tool];
|
|
36
|
+
const lemma = /^lemma_(.+)$/.exec(tool);
|
|
37
|
+
return lemma ? `mcp__lemma__${lemma[1]}` : tool;
|
|
38
|
+
}
|
|
39
|
+
/** Files named in an apply_patch body (the tool GPT models get in OpenCode). */
|
|
40
|
+
function patchFiles(patchText) {
|
|
41
|
+
if (typeof patchText !== 'string')
|
|
42
|
+
return [];
|
|
43
|
+
return [...patchText.matchAll(/^\*\*\* (?:Add|Update|Delete) File: (.+)$/gm)].map((m) => m[1].trim());
|
|
44
|
+
}
|
|
45
|
+
const iso = (ms, fallback) => (typeof ms === 'number' && ms > 0 ? new Date(ms).toISOString() : fallback);
|
|
46
|
+
/** One OpenCode session as Claude Code transcript events (one JSON object per line). */
|
|
47
|
+
function toClaudeEvents(messages, directory, sidechain) {
|
|
48
|
+
const lines = [];
|
|
49
|
+
const extra = sidechain ? { isSidechain: true, agentId: sidechain } : {};
|
|
50
|
+
const push = (timestamp, o) => lines.push(JSON.stringify({ timestamp, ...extra, ...o }));
|
|
51
|
+
const abs = (f) => path_1.default.resolve(directory, f);
|
|
52
|
+
let n = 0;
|
|
53
|
+
for (const m of messages ?? []) {
|
|
54
|
+
const role = m?.info?.role;
|
|
55
|
+
const at = iso(m?.info?.time?.created, new Date(0).toISOString());
|
|
56
|
+
for (const p of m?.parts ?? []) {
|
|
57
|
+
if (p?.type === 'text' && typeof p.text === 'string' && p.text.trim() && !p.synthetic) {
|
|
58
|
+
push(at, role === 'user'
|
|
59
|
+
? { type: 'user', message: { role: 'user', content: p.text } }
|
|
60
|
+
: { type: 'assistant', message: { role: 'assistant', content: [{ type: 'text', text: p.text }] } });
|
|
61
|
+
}
|
|
62
|
+
else if (p?.type === 'patch' && Array.isArray(p.files)) {
|
|
63
|
+
// The step's on-disk changes, whichever tool made them.
|
|
64
|
+
for (const f of p.files)
|
|
65
|
+
if (typeof f === 'string')
|
|
66
|
+
push(at, { type: 'assistant', message: { role: 'assistant', content: [{ type: 'tool_use', id: `patch-${n++}`, name: 'Edit', input: { file_path: abs(f) } }] } });
|
|
67
|
+
}
|
|
68
|
+
else if (p?.type === 'tool' && typeof p.tool === 'string') {
|
|
69
|
+
const st = p.state ?? {};
|
|
70
|
+
const start = iso(st.time?.start, at);
|
|
71
|
+
const name = toolName(p.tool);
|
|
72
|
+
const input = { ...(st.input && typeof st.input === 'object' ? st.input : {}) };
|
|
73
|
+
if (FILE_TOOLS.has(name) && typeof input.filePath === 'string')
|
|
74
|
+
input.file_path = abs(input.filePath);
|
|
75
|
+
const id = p.callID || `call-${n++}`;
|
|
76
|
+
const files = /^(apply_)?patch$/.test(p.tool) ? patchFiles(input.patchText) : [];
|
|
77
|
+
if (files.length) {
|
|
78
|
+
for (const f of files)
|
|
79
|
+
push(start, { type: 'assistant', message: { role: 'assistant', content: [{ type: 'tool_use', id: `${id}-${n++}`, name: 'Edit', input: { file_path: abs(f) } }] } });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
push(start, { type: 'assistant', message: { role: 'assistant', content: [{ type: 'tool_use', id, name, input }] } });
|
|
83
|
+
if (st.status === 'completed' || st.status === 'error') {
|
|
84
|
+
const content = st.status === 'error' ? String(st.error ?? '') : String(st.output ?? '');
|
|
85
|
+
push(iso(st.time?.end, start), { type: 'user', message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: id, content }] } });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return lines;
|
|
91
|
+
}
|
|
92
|
+
/** The description the parent gave each subagent: the `task` call whose metadata names the child session. */
|
|
93
|
+
function taskDescriptions(messages) {
|
|
94
|
+
const out = new Map();
|
|
95
|
+
for (const m of messages ?? [])
|
|
96
|
+
for (const p of m?.parts ?? []) {
|
|
97
|
+
if (p?.type !== 'tool' || p.tool !== 'task')
|
|
98
|
+
continue;
|
|
99
|
+
const child = p.state?.metadata?.sessionId;
|
|
100
|
+
const description = p.state?.input?.description;
|
|
101
|
+
if (typeof child === 'string' && typeof description === 'string')
|
|
102
|
+
out.set(child, description);
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
function prune(dir) {
|
|
107
|
+
try {
|
|
108
|
+
for (const name of fs_1.default.readdirSync(dir)) {
|
|
109
|
+
const f = path_1.default.join(dir, name);
|
|
110
|
+
if (Date.now() - fs_1.default.statSync(f).mtimeMs > KEEP_MS)
|
|
111
|
+
fs_1.default.rmSync(f, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// a leftover temp file costs nothing
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Writes the snapshot as `<dir>/<session>.jsonl` plus its subagents, and returns the transcript path. */
|
|
119
|
+
function writeClaudeTranscript(snap, dir = TRANSCRIPT_DIR) {
|
|
120
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
121
|
+
prune(dir);
|
|
122
|
+
const safe = (id) => id.replace(/[^A-Za-z0-9_-]/g, '_');
|
|
123
|
+
const base = path_1.default.join(dir, safe(snap.sessionID));
|
|
124
|
+
(0, AtomicFile_1.writeFileAtomic)(`${base}.jsonl`, toClaudeEvents(snap.messages, snap.directory).join('\n'));
|
|
125
|
+
const subdir = path_1.default.join(base, 'subagents');
|
|
126
|
+
fs_1.default.rmSync(subdir, { recursive: true, force: true });
|
|
127
|
+
const descriptions = taskDescriptions(snap.messages);
|
|
128
|
+
for (const child of snap.children ?? []) {
|
|
129
|
+
const id = child?.session?.id;
|
|
130
|
+
if (typeof id !== 'string')
|
|
131
|
+
continue;
|
|
132
|
+
fs_1.default.mkdirSync(subdir, { recursive: true });
|
|
133
|
+
fs_1.default.writeFileSync(path_1.default.join(subdir, `agent-${safe(id)}.jsonl`), toClaudeEvents(child.messages, snap.directory, id).join('\n'));
|
|
134
|
+
const description = descriptions.get(id) ?? child.session.title?.replace(/\s*\(@\S+ subagent\)\s*$/, '');
|
|
135
|
+
fs_1.default.writeFileSync(path_1.default.join(subdir, `agent-${safe(id)}.meta.json`), JSON.stringify({ ...(child.session.agent ? { agentType: child.session.agent } : {}), ...(description ? { description } : {}) }));
|
|
136
|
+
}
|
|
137
|
+
return `${base}.jsonl`;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Save (or refresh) the session's hand-off. OpenCode has no session end: `session.idle` fires after
|
|
141
|
+
* every turn, so this runs in the mid-session mode (PreCompact's): it refreshes one hand-off in place
|
|
142
|
+
* and never marks the session done. `compacting` also returns the text to put back in the context.
|
|
143
|
+
*/
|
|
144
|
+
function captureOpenCodeSession(snap, opts = {}) {
|
|
145
|
+
if (!snap || typeof snap.sessionID !== 'string' || typeof snap.directory !== 'string' || !Array.isArray(snap.messages))
|
|
146
|
+
return { skipped: 'no-transcript' };
|
|
147
|
+
const transcript = writeClaudeTranscript(snap);
|
|
148
|
+
const res = (0, SessionCapture_1.captureSession)({ session_id: snap.sessionID, transcript_path: transcript, cwd: snap.directory }, snap.directory, undefined, { preCompact: true });
|
|
149
|
+
if (!opts.compacting)
|
|
150
|
+
return res;
|
|
151
|
+
const recall = (0, HostRecall_1.compactRecall)({ source: 'compact', session_id: snap.sessionID, cwd: snap.directory }, snap.directory);
|
|
152
|
+
return recall ? { ...res, text: recall.text } : res;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=OpenCodeSession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OpenCodeSession.js","sourceRoot":"","sources":["../../../src/utils/OpenCodeSession.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,14 +33,36 @@ export interface SessionDigest {
|
|
|
33
33
|
/** Timestamp of the first transcript event: where "commits made during this session" starts. */
|
|
34
34
|
startedAt?: string;
|
|
35
35
|
/** The last test/build/lint run the agent made and how it ended; unset when the output didn't say. */
|
|
36
|
-
lastVerdict?:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
headline: string;
|
|
40
|
-
};
|
|
36
|
+
lastVerdict?: Verdict;
|
|
37
|
+
/** What the session's subagents did, when asked for (digestTranscript's `subagents` option). Not counted in the fields above. */
|
|
38
|
+
subagents?: SubagentDigest[];
|
|
41
39
|
}
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
export interface Verdict {
|
|
41
|
+
command: string;
|
|
42
|
+
passed: boolean;
|
|
43
|
+
headline: string;
|
|
44
|
+
/** Timestamp of the run's result, when the transcript had one: which of several agents ran last. */
|
|
45
|
+
at?: string;
|
|
46
|
+
}
|
|
47
|
+
/** One subagent's share of the work, from its own transcript. */
|
|
48
|
+
export interface SubagentDigest {
|
|
49
|
+
agentId: string;
|
|
50
|
+
/** What the parent called it (the Agent tool's description), from the host's meta file. */
|
|
51
|
+
description?: string;
|
|
52
|
+
agentType?: string;
|
|
53
|
+
startedAt?: string;
|
|
54
|
+
toolCalls: number;
|
|
55
|
+
edited: string[];
|
|
56
|
+
lastVerdict?: Verdict;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Reads the transcript (JSONL, one event per line) into the few facts capture needs. The fields
|
|
60
|
+
* describe the main agent only; `subagents: true` adds what each subagent did, read from the
|
|
61
|
+
* sidechain events in this file and from the host's per-subagent transcripts next to it.
|
|
62
|
+
*/
|
|
63
|
+
export declare function digestTranscript(transcriptPath: string, opts?: {
|
|
64
|
+
subagents?: boolean;
|
|
65
|
+
}): SessionDigest | null;
|
|
44
66
|
/**
|
|
45
67
|
* Where a session that edited code left off, from the transcript and git alone. The task is the
|
|
46
68
|
* person's first prompt, uncut up to a limit, because that is where a multi-part plan lives; "what
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionCapture.d.ts","sourceRoot":"","sources":["../../../src/utils/SessionCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,EAA0C,UAAU,EAAE,MAAM,4BAA4B,CAAC;AA2BhG,eAAO,MAAM,YAAY,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"SessionCapture.d.ts","sourceRoot":"","sources":["../../../src/utils/SessionCapture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,EAA0C,UAAU,EAAE,MAAM,4BAA4B,CAAC;AA2BhG,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAkBlD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,sGAAsG;IACtG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,eAAe,EAAE,OAAO,CAAC;IACzB,0GAA0G;IAC1G,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gGAAgG;IAChG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sGAAsG;IACtG,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iIAAiI;IACjI,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,oGAAoG;IACpG,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AA2CD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,aAAa,GAAG,IAAI,CAYjH;AA2LD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAyC9F;AAED,qEAAqE;AACrE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIhE;AAsBD,MAAM,WAAW,oBAAoB;IACnC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,eAAe,GAAG,UAAU,GAAG,kBAAkB,GAAG,cAAc,GAAG,eAAe,GAAG,WAAW,GAAG,YAAY,GAAG,SAAS,CAAC;CACzI;AAED,0GAA0G;AAC1G,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,EAAE,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,oBAAoB,CAwE5I;AAuBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAWtH"}
|
|
@@ -62,9 +62,18 @@ const AGENT_NOTE_MARK = "AGENT'S NOTE";
|
|
|
62
62
|
const MAX_AGENT_NOTE_CHARS = 1500;
|
|
63
63
|
const MAX_HANDOFF_FILES = 10;
|
|
64
64
|
const MAX_SAID_CHARS = 700;
|
|
65
|
+
/** Subagent transcripts read per session (newest first) and the bytes read from each: an orchestrator can spawn dozens. */
|
|
66
|
+
const MAX_SUBAGENT_TRANSCRIPTS = 30;
|
|
67
|
+
const MAX_SUBAGENT_BYTES = 2 * 1024 * 1024;
|
|
68
|
+
const SUBAGENT_HEAD_BYTES = 128 * 1024;
|
|
69
|
+
const SUBAGENT_TAIL_BYTES = 1536 * 1024;
|
|
70
|
+
const MAX_SUBAGENT_LINES = 6;
|
|
71
|
+
const MAX_DESCRIPTION_CHARS = 80;
|
|
65
72
|
const REMEMBERED_SESSIONS = 50;
|
|
66
73
|
/** Tools that mean the agent already wrote (or deliberately declined to write) its own memory. */
|
|
67
74
|
const OWN_MEMORY_TOOL = /^mcp__lemma__(store|update|forget)_memory$/;
|
|
75
|
+
/** Lemma's own editing tools: `filePath` is relative to the project root (multi_file_patch carries a list of them). */
|
|
76
|
+
const LEMMA_EDIT_TOOL = /^mcp__lemma__(write_workspace_file|create_workspace_file|apply_workspace_patch|multi_file_patch)$/;
|
|
68
77
|
function textOf(content) {
|
|
69
78
|
if (typeof content === 'string')
|
|
70
79
|
return content;
|
|
@@ -81,13 +90,13 @@ function textOf(content) {
|
|
|
81
90
|
* done; dropping them would leave no hand-off where it matters most. Counts such as `toolCalls`
|
|
82
91
|
* then describe the tail only.
|
|
83
92
|
*/
|
|
84
|
-
function readEnds(file, size) {
|
|
93
|
+
function readEnds(file, size, headBytes = HEAD_BYTES, tailBytes = TAIL_BYTES) {
|
|
85
94
|
const fd = fs_1.default.openSync(file, 'r');
|
|
86
95
|
try {
|
|
87
|
-
const head = Buffer.alloc(
|
|
88
|
-
fs_1.default.readSync(fd, head, 0,
|
|
89
|
-
const tail = Buffer.alloc(
|
|
90
|
-
fs_1.default.readSync(fd, tail, 0,
|
|
96
|
+
const head = Buffer.alloc(headBytes);
|
|
97
|
+
fs_1.default.readSync(fd, head, 0, headBytes, 0);
|
|
98
|
+
const tail = Buffer.alloc(tailBytes);
|
|
99
|
+
fs_1.default.readSync(fd, tail, 0, tailBytes, size - tailBytes);
|
|
91
100
|
const h = head.toString('utf8');
|
|
92
101
|
const t = tail.toString('utf8');
|
|
93
102
|
// Drop the partial line at each cut; an unparseable line would be skipped anyway, but a cut
|
|
@@ -98,8 +107,24 @@ function readEnds(file, size) {
|
|
|
98
107
|
fs_1.default.closeSync(fd);
|
|
99
108
|
}
|
|
100
109
|
}
|
|
101
|
-
|
|
102
|
-
|
|
110
|
+
function parseLines(raw) {
|
|
111
|
+
const events = [];
|
|
112
|
+
for (const line of raw.split('\n')) {
|
|
113
|
+
if (!line)
|
|
114
|
+
continue;
|
|
115
|
+
try {
|
|
116
|
+
events.push(JSON.parse(line));
|
|
117
|
+
}
|
|
118
|
+
catch { /* a cut or garbled line */ }
|
|
119
|
+
}
|
|
120
|
+
return events;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Reads the transcript (JSONL, one event per line) into the few facts capture needs. The fields
|
|
124
|
+
* describe the main agent only; `subagents: true` adds what each subagent did, read from the
|
|
125
|
+
* sidechain events in this file and from the host's per-subagent transcripts next to it.
|
|
126
|
+
*/
|
|
127
|
+
function digestTranscript(transcriptPath, opts = {}) {
|
|
103
128
|
let raw;
|
|
104
129
|
try {
|
|
105
130
|
const stat = fs_1.default.statSync(transcriptPath);
|
|
@@ -108,21 +133,19 @@ function digestTranscript(transcriptPath) {
|
|
|
108
133
|
catch {
|
|
109
134
|
return null;
|
|
110
135
|
}
|
|
136
|
+
const events = parseLines(raw);
|
|
137
|
+
const digest = scan(events.filter((ev) => !ev?.isSidechain));
|
|
138
|
+
if (opts.subagents)
|
|
139
|
+
digest.subagents = digestSubagents(transcriptPath, events.filter((ev) => ev?.isSidechain));
|
|
140
|
+
return digest;
|
|
141
|
+
}
|
|
142
|
+
function scan(events) {
|
|
111
143
|
const digest = { prompt: '', answer: '', toolCalls: 0, edited: [], read: [], storedOwnMemory: false };
|
|
112
144
|
const readCount = new Map();
|
|
113
145
|
const verdictCommands = new Map(); // tool_use id -> command, for verdict-shaped Bash runs
|
|
114
146
|
let edited = false;
|
|
115
|
-
for (const
|
|
116
|
-
if (!
|
|
117
|
-
continue;
|
|
118
|
-
let ev;
|
|
119
|
-
try {
|
|
120
|
-
ev = JSON.parse(line);
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
if (ev.isSidechain)
|
|
147
|
+
for (const ev of events) {
|
|
148
|
+
if (!ev || typeof ev !== 'object')
|
|
126
149
|
continue;
|
|
127
150
|
if (!digest.startedAt && typeof ev.timestamp === 'string')
|
|
128
151
|
digest.startedAt = ev.timestamp;
|
|
@@ -138,7 +161,7 @@ function digestTranscript(transcriptPath) {
|
|
|
138
161
|
const passed = (0, FixCapture_1.inferVerdict)(output);
|
|
139
162
|
if (passed === null)
|
|
140
163
|
continue;
|
|
141
|
-
digest.lastVerdict = { command, passed, headline: passed ? '' : (0, FixCapture_1.extractFailure)(output).headline };
|
|
164
|
+
digest.lastVerdict = { command, passed, headline: passed ? '' : (0, FixCapture_1.extractFailure)(output).headline, ...(typeof ev.timestamp === 'string' ? { at: ev.timestamp } : {}) };
|
|
142
165
|
}
|
|
143
166
|
}
|
|
144
167
|
if (ev.type === 'user') {
|
|
@@ -170,11 +193,15 @@ function digestTranscript(transcriptPath) {
|
|
|
170
193
|
}
|
|
171
194
|
if (name === 'Bash')
|
|
172
195
|
digest.ranAfterEdit = edited;
|
|
173
|
-
|
|
196
|
+
const lemmaEdits = LEMMA_EDIT_TOOL.test(name)
|
|
197
|
+
? [block.input?.filePath, ...(Array.isArray(block.input?.patches) ? block.input.patches.map((x) => x?.filePath) : [])].filter((f) => typeof f === 'string' && !!f)
|
|
198
|
+
: [];
|
|
199
|
+
if ((typeof file === 'string' && /^(Edit|Write|MultiEdit|NotebookEdit)$/.test(name)) || lemmaEdits.length) {
|
|
174
200
|
edited = true;
|
|
175
201
|
digest.ranAfterEdit = false;
|
|
176
|
-
|
|
177
|
-
digest.edited.
|
|
202
|
+
for (const f of lemmaEdits.length ? lemmaEdits : [file])
|
|
203
|
+
if (!digest.edited.includes(f))
|
|
204
|
+
digest.edited.push(f);
|
|
178
205
|
}
|
|
179
206
|
else if (typeof file === 'string' && name === 'Read') {
|
|
180
207
|
readCount.set(file, (readCount.get(file) ?? 0) + 1);
|
|
@@ -184,6 +211,94 @@ function digestTranscript(transcriptPath) {
|
|
|
184
211
|
digest.read = [...readCount.entries()].sort((a, b) => b[1] - a[1]).map(([f]) => f);
|
|
185
212
|
return digest;
|
|
186
213
|
}
|
|
214
|
+
function readMeta(file) {
|
|
215
|
+
try {
|
|
216
|
+
const m = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
|
|
217
|
+
return {
|
|
218
|
+
...(typeof m?.description === 'string' && m.description.trim() ? { description: m.description.trim() } : {}),
|
|
219
|
+
...(typeof m?.agentType === 'string' && m.agentType ? { agentType: m.agentType } : {}),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return {};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function subagentDigest(agentId, events, meta) {
|
|
227
|
+
const d = scan(events);
|
|
228
|
+
return { agentId, ...meta, ...(d.startedAt ? { startedAt: d.startedAt } : {}), toolCalls: d.toolCalls, edited: d.edited, ...(d.lastVerdict ? { lastVerdict: d.lastVerdict } : {}) };
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* What the session's subagents did. Claude Code writes each one to
|
|
232
|
+
* `<transcript without .jsonl>/subagents/agent-<id>.jsonl`, with a `.meta.json` beside it naming
|
|
233
|
+
* the description the parent gave; older builds put the same events in the main transcript,
|
|
234
|
+
* flagged isSidechain. A subagent found in both is read from its own file, which is whole.
|
|
235
|
+
* An orchestrator that delegated the edits used to leave an empty hand-off: its own transcript
|
|
236
|
+
* holds the Agent calls, not the files they changed or the tests they ran.
|
|
237
|
+
*/
|
|
238
|
+
function digestSubagents(transcriptPath, sidechain) {
|
|
239
|
+
const byId = new Map();
|
|
240
|
+
const dir = path_1.default.join(transcriptPath.replace(/\.jsonl$/, ''), 'subagents');
|
|
241
|
+
let files = [];
|
|
242
|
+
try {
|
|
243
|
+
files = fs_1.default.readdirSync(dir)
|
|
244
|
+
.map((name) => /^agent-(.+)\.jsonl$/.exec(name))
|
|
245
|
+
.filter((m) => !!m)
|
|
246
|
+
.map((m) => {
|
|
247
|
+
const file = path_1.default.join(dir, m[0]);
|
|
248
|
+
const st = fs_1.default.statSync(file);
|
|
249
|
+
return { id: m[1], file, mtime: st.mtimeMs, size: st.size };
|
|
250
|
+
})
|
|
251
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
252
|
+
.slice(0, MAX_SUBAGENT_TRANSCRIPTS);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// no subagents directory: a session that delegated nothing, or an older host
|
|
256
|
+
}
|
|
257
|
+
for (const f of files) {
|
|
258
|
+
try {
|
|
259
|
+
const raw = f.size > MAX_SUBAGENT_BYTES ? readEnds(f.file, f.size, SUBAGENT_HEAD_BYTES, SUBAGENT_TAIL_BYTES) : fs_1.default.readFileSync(f.file, 'utf8');
|
|
260
|
+
byId.set(f.id, subagentDigest(f.id, parseLines(raw), readMeta(f.file.replace(/\.jsonl$/, '.meta.json'))));
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// unreadable: that subagent simply isn't listed
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const inline = new Map();
|
|
267
|
+
for (const ev of sidechain) {
|
|
268
|
+
const id = typeof ev.agentId === 'string' && ev.agentId ? ev.agentId : 'sidechain';
|
|
269
|
+
if (byId.has(id))
|
|
270
|
+
continue;
|
|
271
|
+
if (!inline.has(id))
|
|
272
|
+
inline.set(id, []);
|
|
273
|
+
inline.get(id).push(ev);
|
|
274
|
+
}
|
|
275
|
+
for (const [id, evs] of inline)
|
|
276
|
+
byId.set(id, subagentDigest(id, evs, {}));
|
|
277
|
+
return [...byId.values()].sort((a, b) => String(a.startedAt ?? '').localeCompare(String(b.startedAt ?? '')));
|
|
278
|
+
}
|
|
279
|
+
/** Files edited by the main agent first, then by its subagents, once each. */
|
|
280
|
+
function allEdited(digest) {
|
|
281
|
+
const out = [...digest.edited];
|
|
282
|
+
for (const s of digest.subagents ?? [])
|
|
283
|
+
for (const f of s.edited)
|
|
284
|
+
if (!out.includes(f))
|
|
285
|
+
out.push(f);
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
/** The latest test/build run by any agent of the session, and which subagent ran it (unset for the main agent). */
|
|
289
|
+
function latestVerdict(digest) {
|
|
290
|
+
let best = digest.lastVerdict ? { verdict: digest.lastVerdict } : null;
|
|
291
|
+
for (const s of digest.subagents ?? []) {
|
|
292
|
+
const v = s.lastVerdict;
|
|
293
|
+
if (!v)
|
|
294
|
+
continue;
|
|
295
|
+
// Without timestamps on both there is no telling who ran last, and the run already chosen stands.
|
|
296
|
+
if (!best || (v.at && best.verdict.at && v.at > best.verdict.at))
|
|
297
|
+
best = { verdict: v, by: s };
|
|
298
|
+
}
|
|
299
|
+
return best;
|
|
300
|
+
}
|
|
301
|
+
const subagentName = (s) => `${s.description ? `"${clip(s.description, MAX_DESCRIPTION_CHARS)}"` : `agent ${s.agentId}`}${s.agentType ? ` (${s.agentType})` : ''}`;
|
|
187
302
|
function seen(root) {
|
|
188
303
|
try {
|
|
189
304
|
const parsed = JSON.parse(fs_1.default.readFileSync(path_1.default.join(root, STATE_FILE), 'utf8'));
|
|
@@ -212,6 +327,20 @@ function git(root, args) {
|
|
|
212
327
|
}
|
|
213
328
|
}
|
|
214
329
|
const clip = (text, max) => (text.length > max ? `${text.slice(0, max)}…` : text);
|
|
330
|
+
/**
|
|
331
|
+
* A path as the filesystem resolves it. git reports the root resolved (macOS /var and /tmp are links to
|
|
332
|
+
* /private/…) while a host can report the same files through the link, and path.relative between the two
|
|
333
|
+
* spellings gave `../../../../var/folders/…/src/tax.js`, and the file was not tracked.
|
|
334
|
+
*/
|
|
335
|
+
function realPath(root, f) {
|
|
336
|
+
const abs = path_1.default.resolve(root, f);
|
|
337
|
+
try {
|
|
338
|
+
return fs_1.default.realpathSync(abs);
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
return abs; // deleted since: the spelling the host gave is the best there is
|
|
342
|
+
}
|
|
343
|
+
}
|
|
215
344
|
/**
|
|
216
345
|
* Where a session that edited code left off, from the transcript and git alone. The task is the
|
|
217
346
|
* person's first prompt, uncut up to a limit, because that is where a multi-part plan lives; "what
|
|
@@ -219,8 +348,9 @@ const clip = (text, max) => (text.length > max ? `${text.slice(0, max)}…` : te
|
|
|
219
348
|
* against what changed. Exported for tests.
|
|
220
349
|
*/
|
|
221
350
|
function buildHandoff(root, digest, carriedPlan) {
|
|
222
|
-
|
|
223
|
-
const
|
|
351
|
+
// Lemma's edit tools record paths relative to the project root, the host's are absolute.
|
|
352
|
+
const rel = (f) => path_1.default.relative(root, realPath(root, f)) || f;
|
|
353
|
+
const edited = allEdited(digest).map(rel);
|
|
224
354
|
// A continuation's own prompt ("continue where we left off") says nothing: the plan is the one
|
|
225
355
|
// the previous hand-off held, carried forward as it was first given.
|
|
226
356
|
const taskBlock = carriedPlan
|
|
@@ -238,9 +368,24 @@ function buildHandoff(root, digest, carriedPlan) {
|
|
|
238
368
|
const commits = digest.startedAt ? git(root, ['log', `--since=${digest.startedAt}`, '-5', '--format=%h %s']) : '';
|
|
239
369
|
const dirty = git(root, ['status', '--porcelain']).split('\n').filter((l) => l && !/\s\.lemma(\/|$)/.test(l));
|
|
240
370
|
lines.push(`GIT${branch ? ` (${branch})` : ''}: ${commits ? `commits this session: ${commits.split('\n').join('; ')}` : 'no commits this session'}; ${dirty.length} uncommitted file(s)${dirty.length ? `: ${dirty.slice(0, MAX_HANDOFF_FILES).map((l) => l.slice(3)).join(', ')}` : ''}`);
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
371
|
+
const ran = (v) => `\`${clip(v.command, 100)}\` ${v.passed ? 'passed' : `FAILED${v.headline ? ` — ${clip(v.headline, 160)}` : ''}`}`;
|
|
372
|
+
const last = latestVerdict(digest);
|
|
373
|
+
if (last)
|
|
374
|
+
lines.push(`LAST TEST/BUILD RUN${last.by ? ` (by subagent ${subagentName(last.by)})` : ''}: ${ran(last.verdict)}`);
|
|
375
|
+
const subs = digest.subagents ?? [];
|
|
376
|
+
const worked = subs.filter((s) => s.edited.length > 0 || s.lastVerdict);
|
|
377
|
+
if (worked.length) {
|
|
378
|
+
lines.push('', `SUBAGENTS (${subs.length} ran, ${subs.filter((s) => s.edited.length > 0).length} edited code):`);
|
|
379
|
+
for (const s of worked.slice(0, MAX_SUBAGENT_LINES)) {
|
|
380
|
+
const parts = [
|
|
381
|
+
...(s.edited.length ? [`edited ${s.edited.slice(0, MAX_FILES).map(rel).join(', ')}${s.edited.length > MAX_FILES ? ', …' : ''}`] : []),
|
|
382
|
+
...(s.lastVerdict ? [`last run ${ran(s.lastVerdict)}`] : []),
|
|
383
|
+
];
|
|
384
|
+
lines.push(`- ${subagentName(s)}: ${parts.join('; ')}`);
|
|
385
|
+
}
|
|
386
|
+
if (worked.length > MAX_SUBAGENT_LINES)
|
|
387
|
+
lines.push(`- …and ${worked.length - MAX_SUBAGENT_LINES} more`);
|
|
388
|
+
}
|
|
244
389
|
if (digest.answer)
|
|
245
390
|
lines.push('', 'LAST SAID BY THE AGENT:', clip(digest.answer, MAX_SAID_CHARS));
|
|
246
391
|
return lines.join('\n');
|
|
@@ -294,10 +439,11 @@ function captureSession(payload, cwd, brain, opts = {}) {
|
|
|
294
439
|
// PreCompact runs mid-session and again at SessionEnd: it must neither be stopped by nor leave the "captured" mark.
|
|
295
440
|
if (!opts.preCompact && sessionId && seen(root).includes(sessionId))
|
|
296
441
|
return { skipped: 'already-captured' };
|
|
297
|
-
const digest = digestTranscript(p.transcript_path);
|
|
442
|
+
const digest = digestTranscript(p.transcript_path, { subagents: true });
|
|
298
443
|
if (!digest)
|
|
299
444
|
return { skipped: 'no-transcript' };
|
|
300
|
-
const
|
|
445
|
+
const edited = allEdited(digest);
|
|
446
|
+
const handoff = edited.length > 0;
|
|
301
447
|
if (opts.preCompact && !handoff)
|
|
302
448
|
return { skipped: 'no-edits' }; // only a hand-off is worth re-injecting
|
|
303
449
|
// The closing answer is skipped when the agent wrote its own memory, but the hand-off is not:
|
|
@@ -306,7 +452,9 @@ function captureSession(payload, cwd, brain, opts = {}) {
|
|
|
306
452
|
// task verbatim. Both live side by side: the note is the agent's reading, this is the source.
|
|
307
453
|
if (digest.storedOwnMemory && !handoff)
|
|
308
454
|
return { skipped: 'agent-stored' };
|
|
309
|
-
|
|
455
|
+
// An orchestrator's own calls are few (it delegates); its subagents' calls are the session's work too.
|
|
456
|
+
const toolCalls = digest.toolCalls + (digest.subagents ?? []).reduce((n, s) => n + s.toolCalls, 0);
|
|
457
|
+
if (toolCalls < MIN_TOOL_CALLS)
|
|
310
458
|
return { skipped: 'small-session' };
|
|
311
459
|
if (!digest.prompt)
|
|
312
460
|
return { skipped: 'no-answer' };
|
|
@@ -314,10 +462,10 @@ function captureSession(payload, cwd, brain, opts = {}) {
|
|
|
314
462
|
if (!handoff && digest.answer.length < MIN_ANSWER_CHARS)
|
|
315
463
|
return { skipped: 'no-answer' };
|
|
316
464
|
const inRoot = (f) => {
|
|
317
|
-
const abs =
|
|
465
|
+
const abs = realPath(root, f);
|
|
318
466
|
return abs.startsWith(root + path_1.default.sep) && fs_1.default.existsSync(abs) ? abs : null;
|
|
319
467
|
};
|
|
320
|
-
const tracked = (
|
|
468
|
+
const tracked = (edited.length > 0 ? edited : digest.read)
|
|
321
469
|
.map(inRoot)
|
|
322
470
|
.filter((f) => !!f)
|
|
323
471
|
.slice(0, MAX_FILES);
|