context-mode 1.0.67 → 1.0.68
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/build/opencode-plugin.js +1 -1
- package/build/session/extract.d.ts +1 -1
- package/build/session/extract.js +39 -42
- package/build/session/snapshot.d.ts +15 -48
- package/build/session/snapshot.js +267 -208
- package/build/truncate.d.ts +0 -10
- package/build/truncate.js +0 -17
- package/hooks/codex/sessionstart.mjs +5 -13
- package/hooks/cursor/sessionstart.mjs +5 -13
- package/hooks/gemini-cli/sessionstart.mjs +6 -14
- package/hooks/session-directive.mjs +3 -2
- package/hooks/session-extract.bundle.mjs +1 -1
- package/hooks/session-snapshot.bundle.mjs +29 -14
- package/hooks/sessionstart.mjs +6 -18
- package/hooks/vscode-copilot/sessionstart.mjs +6 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -1,41 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Snapshot builder — converts stored SessionEvents into
|
|
2
|
+
* Snapshot builder — converts stored SessionEvents into a reference-based
|
|
3
|
+
* XML resume snapshot.
|
|
3
4
|
*
|
|
4
5
|
* Pure functions only. No database access, no file system, no side effects.
|
|
5
|
-
* The output XML is injected into Claude's context after a compact event to
|
|
6
|
-
* restore session awareness.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* The output XML is injected into the LLM's context after a compact event to
|
|
8
|
+
* restore session awareness. Instead of truncated inline data, each section
|
|
9
|
+
* contains a natural summary plus a runnable search tool call that retrieves
|
|
10
|
+
* full details from the indexed knowledge base on demand.
|
|
11
|
+
*
|
|
12
|
+
* Zero truncation. Zero information loss. Full data lives in SessionDB;
|
|
13
|
+
* the snapshot is a table of contents.
|
|
12
14
|
*/
|
|
13
|
-
import { escapeXML
|
|
14
|
-
// ──
|
|
15
|
-
const DEFAULT_MAX_BYTES = 2048;
|
|
15
|
+
import { escapeXML } from "../truncate.js";
|
|
16
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
16
17
|
const MAX_ACTIVE_FILES = 10;
|
|
17
|
-
// Priority tier category groupings
|
|
18
|
-
const P1_CATEGORIES = new Set(["file", "task", "rule"]);
|
|
19
|
-
const P2_CATEGORIES = new Set(["cwd", "error", "decision", "env", "git"]);
|
|
20
|
-
// P3-P4: everything else (subagent, skill, role, data, intent, mcp)
|
|
21
|
-
// ── Section renderers ────────────────────────────────────────────────────────
|
|
22
18
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
19
|
+
* Extract 2-4 keyword phrases from a list of strings for BM25 search queries.
|
|
20
|
+
* Takes actual data values and picks representative terms.
|
|
21
|
+
*/
|
|
22
|
+
function buildQueries(items, maxQueries = 4) {
|
|
23
|
+
const unique = [...new Set(items.filter(s => s.length > 0))];
|
|
24
|
+
const selected = unique.slice(0, maxQueries);
|
|
25
|
+
return selected.map(s => {
|
|
26
|
+
// Take the first ~80 chars as a query — enough for BM25 matching
|
|
27
|
+
const trimmed = s.length > 80 ? s.slice(0, 80) : s;
|
|
28
|
+
return trimmed;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Format a runnable tool call block for a section.
|
|
25
33
|
*/
|
|
26
|
-
|
|
34
|
+
function toolCall(toolName, queries) {
|
|
35
|
+
if (queries.length === 0)
|
|
36
|
+
return "";
|
|
37
|
+
const escaped = queries.map(q => `"${escapeXML(q)}"`).join(", ");
|
|
38
|
+
return `\n For full details:\n ${escapeXML(toolName)}(\n queries: [${escaped}],\n source: "session-events"\n )`;
|
|
39
|
+
}
|
|
40
|
+
// ── Section builders ─────────────────────────────────────────────────────────
|
|
41
|
+
function buildFilesSection(fileEvents, searchTool) {
|
|
27
42
|
if (fileEvents.length === 0)
|
|
28
43
|
return "";
|
|
29
|
-
// Build per-file operation counts
|
|
44
|
+
// Build per-file operation counts
|
|
30
45
|
const fileMap = new Map();
|
|
31
46
|
for (const ev of fileEvents) {
|
|
32
47
|
const path = ev.data;
|
|
33
48
|
let entry = fileMap.get(path);
|
|
34
49
|
if (!entry) {
|
|
35
|
-
entry = { ops: new Map()
|
|
50
|
+
entry = { ops: new Map() };
|
|
36
51
|
fileMap.set(path, entry);
|
|
37
52
|
}
|
|
38
|
-
// Derive operation from event type
|
|
39
53
|
let op;
|
|
40
54
|
if (ev.type === "file_write")
|
|
41
55
|
op = "write";
|
|
@@ -46,19 +60,117 @@ export function renderActiveFiles(fileEvents) {
|
|
|
46
60
|
else
|
|
47
61
|
op = ev.type;
|
|
48
62
|
entry.ops.set(op, (entry.ops.get(op) ?? 0) + 1);
|
|
49
|
-
entry.last = op;
|
|
50
63
|
}
|
|
51
64
|
// Limit to last MAX_ACTIVE_FILES files (by insertion order = chronological)
|
|
52
65
|
const entries = Array.from(fileMap.entries());
|
|
53
66
|
const limited = entries.slice(-MAX_ACTIVE_FILES);
|
|
54
|
-
const
|
|
55
|
-
|
|
67
|
+
const summaryLines = [];
|
|
68
|
+
const queryTerms = [];
|
|
69
|
+
for (const [path, { ops }] of limited) {
|
|
56
70
|
const opsStr = Array.from(ops.entries())
|
|
57
|
-
.map(([k, v]) => `${k}
|
|
58
|
-
.join(",");
|
|
59
|
-
|
|
71
|
+
.map(([k, v]) => `${k}×${v}`)
|
|
72
|
+
.join(", ");
|
|
73
|
+
// Use just the filename for concise display
|
|
74
|
+
const fileName = path.split("/").pop() ?? path;
|
|
75
|
+
summaryLines.push(` ${escapeXML(fileName)} (${escapeXML(opsStr)})`);
|
|
76
|
+
queryTerms.push(`${fileName} ${Array.from(ops.keys()).join(" ")}`);
|
|
77
|
+
}
|
|
78
|
+
const queries = buildQueries(queryTerms);
|
|
79
|
+
const lines = [
|
|
80
|
+
` <files count="${fileMap.size}">`,
|
|
81
|
+
...summaryLines,
|
|
82
|
+
toolCall(searchTool, queries),
|
|
83
|
+
` </files>`,
|
|
84
|
+
];
|
|
85
|
+
return lines.join("\n");
|
|
86
|
+
}
|
|
87
|
+
function buildErrorsSection(errorEvents, searchTool) {
|
|
88
|
+
if (errorEvents.length === 0)
|
|
89
|
+
return "";
|
|
90
|
+
const summaryLines = [];
|
|
91
|
+
const queryTerms = [];
|
|
92
|
+
for (const ev of errorEvents) {
|
|
93
|
+
summaryLines.push(` ${escapeXML(ev.data)}`);
|
|
94
|
+
queryTerms.push(ev.data);
|
|
60
95
|
}
|
|
61
|
-
|
|
96
|
+
const queries = buildQueries(queryTerms);
|
|
97
|
+
const lines = [
|
|
98
|
+
` <errors count="${errorEvents.length}">`,
|
|
99
|
+
...summaryLines,
|
|
100
|
+
toolCall(searchTool, queries),
|
|
101
|
+
` </errors>`,
|
|
102
|
+
];
|
|
103
|
+
return lines.join("\n");
|
|
104
|
+
}
|
|
105
|
+
function buildDecisionsSection(decisionEvents, searchTool) {
|
|
106
|
+
if (decisionEvents.length === 0)
|
|
107
|
+
return "";
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
const summaryLines = [];
|
|
110
|
+
const queryTerms = [];
|
|
111
|
+
for (const ev of decisionEvents) {
|
|
112
|
+
if (seen.has(ev.data))
|
|
113
|
+
continue;
|
|
114
|
+
seen.add(ev.data);
|
|
115
|
+
summaryLines.push(` ${escapeXML(ev.data)}`);
|
|
116
|
+
queryTerms.push(ev.data);
|
|
117
|
+
}
|
|
118
|
+
if (summaryLines.length === 0)
|
|
119
|
+
return "";
|
|
120
|
+
const queries = buildQueries(queryTerms);
|
|
121
|
+
const lines = [
|
|
122
|
+
` <decisions count="${summaryLines.length}">`,
|
|
123
|
+
...summaryLines,
|
|
124
|
+
toolCall(searchTool, queries),
|
|
125
|
+
` </decisions>`,
|
|
126
|
+
];
|
|
127
|
+
return lines.join("\n");
|
|
128
|
+
}
|
|
129
|
+
function buildRulesSection(ruleEvents, searchTool) {
|
|
130
|
+
if (ruleEvents.length === 0)
|
|
131
|
+
return "";
|
|
132
|
+
const seen = new Set();
|
|
133
|
+
const summaryLines = [];
|
|
134
|
+
const queryTerms = [];
|
|
135
|
+
for (const ev of ruleEvents) {
|
|
136
|
+
if (seen.has(ev.data))
|
|
137
|
+
continue;
|
|
138
|
+
seen.add(ev.data);
|
|
139
|
+
if (ev.type === "rule_content") {
|
|
140
|
+
summaryLines.push(` ${escapeXML(ev.data)}`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
summaryLines.push(` ${escapeXML(ev.data)}`);
|
|
144
|
+
}
|
|
145
|
+
queryTerms.push(ev.data);
|
|
146
|
+
}
|
|
147
|
+
if (summaryLines.length === 0)
|
|
148
|
+
return "";
|
|
149
|
+
const queries = buildQueries(queryTerms);
|
|
150
|
+
const lines = [
|
|
151
|
+
` <rules count="${summaryLines.length}">`,
|
|
152
|
+
...summaryLines,
|
|
153
|
+
toolCall(searchTool, queries),
|
|
154
|
+
` </rules>`,
|
|
155
|
+
];
|
|
156
|
+
return lines.join("\n");
|
|
157
|
+
}
|
|
158
|
+
function buildGitSection(gitEvents, searchTool) {
|
|
159
|
+
if (gitEvents.length === 0)
|
|
160
|
+
return "";
|
|
161
|
+
const summaryLines = [];
|
|
162
|
+
const queryTerms = [];
|
|
163
|
+
for (const ev of gitEvents) {
|
|
164
|
+
summaryLines.push(` ${escapeXML(ev.data)}`);
|
|
165
|
+
queryTerms.push(ev.data);
|
|
166
|
+
}
|
|
167
|
+
const queries = buildQueries(queryTerms);
|
|
168
|
+
const lines = [
|
|
169
|
+
` <git count="${gitEvents.length}">`,
|
|
170
|
+
...summaryLines,
|
|
171
|
+
toolCall(searchTool, queries),
|
|
172
|
+
` </git>`,
|
|
173
|
+
];
|
|
62
174
|
return lines.join("\n");
|
|
63
175
|
}
|
|
64
176
|
/**
|
|
@@ -67,7 +179,7 @@ export function renderActiveFiles(fileEvents) {
|
|
|
67
179
|
* filters out completed tasks, and renders only pending/in-progress work.
|
|
68
180
|
*
|
|
69
181
|
* TaskCreate events have `{ subject }`, TaskUpdate events have `{ taskId, status }`.
|
|
70
|
-
* Match by chronological order: creates[0]
|
|
182
|
+
* Match by chronological order: creates[0] -> lowest taskId from updates.
|
|
71
183
|
*/
|
|
72
184
|
export function renderTaskState(taskEvents) {
|
|
73
185
|
if (taskEvents.length === 0)
|
|
@@ -89,7 +201,7 @@ export function renderTaskState(taskEvents) {
|
|
|
89
201
|
if (creates.length === 0)
|
|
90
202
|
return "";
|
|
91
203
|
const DONE = new Set(["completed", "deleted", "failed"]);
|
|
92
|
-
// Match creates to updates positionally (creates[0]
|
|
204
|
+
// Match creates to updates positionally (creates[0] -> lowest taskId)
|
|
93
205
|
const sortedIds = Object.keys(updates).sort((a, b) => Number(a) - Number(b));
|
|
94
206
|
const pending = [];
|
|
95
207
|
for (let i = 0; i < creates.length; i++) {
|
|
@@ -102,147 +214,123 @@ export function renderTaskState(taskEvents) {
|
|
|
102
214
|
// All tasks completed — nothing to render
|
|
103
215
|
if (pending.length === 0)
|
|
104
216
|
return "";
|
|
105
|
-
const lines = [
|
|
217
|
+
const lines = [];
|
|
106
218
|
for (const task of pending) {
|
|
107
|
-
lines.push(`
|
|
219
|
+
lines.push(` [pending] ${escapeXML(task)}`);
|
|
108
220
|
}
|
|
109
|
-
lines.push(" </task_state>");
|
|
110
221
|
return lines.join("\n");
|
|
111
222
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
*/
|
|
116
|
-
export function renderRules(ruleEvents) {
|
|
117
|
-
if (ruleEvents.length === 0)
|
|
223
|
+
function buildTaskSection(taskEvents, searchTool) {
|
|
224
|
+
const taskContent = renderTaskState(taskEvents);
|
|
225
|
+
if (!taskContent)
|
|
118
226
|
return "";
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (ev.type === "rule_content") {
|
|
127
|
-
// Rule content: render as content block (survives compact)
|
|
128
|
-
lines.push(` <rule_content>${escapeXML(truncateString(ev.data, 400))}</rule_content>`);
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
// Rule path
|
|
132
|
-
lines.push(` - ${escapeXML(truncateString(ev.data, 200))}`);
|
|
227
|
+
const queryTerms = [];
|
|
228
|
+
for (const ev of taskEvents) {
|
|
229
|
+
try {
|
|
230
|
+
const parsed = JSON.parse(ev.data);
|
|
231
|
+
if (typeof parsed.subject === "string") {
|
|
232
|
+
queryTerms.push(parsed.subject);
|
|
233
|
+
}
|
|
133
234
|
}
|
|
235
|
+
catch { /* not JSON */ }
|
|
134
236
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return "";
|
|
144
|
-
const seen = new Set();
|
|
145
|
-
const lines = [" <decisions>"];
|
|
146
|
-
for (const ev of decisionEvents) {
|
|
147
|
-
const key = ev.data;
|
|
148
|
-
if (seen.has(key))
|
|
149
|
-
continue;
|
|
150
|
-
seen.add(key);
|
|
151
|
-
lines.push(` - ${escapeXML(truncateString(ev.data, 200))}`);
|
|
152
|
-
}
|
|
153
|
-
lines.push(" </decisions>");
|
|
237
|
+
const queries = buildQueries(queryTerms);
|
|
238
|
+
const pendingCount = taskContent.split("\n").length;
|
|
239
|
+
const lines = [
|
|
240
|
+
` <task_state count="${pendingCount}">`,
|
|
241
|
+
taskContent,
|
|
242
|
+
toolCall(searchTool, queries),
|
|
243
|
+
` </task_state>`,
|
|
244
|
+
];
|
|
154
245
|
return lines.join("\n");
|
|
155
246
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
*/
|
|
159
|
-
export function renderEnvironment(cwdEvent, envEvents, gitEvent) {
|
|
160
|
-
const parts = [];
|
|
161
|
-
if (!cwdEvent && envEvents.length === 0 && !gitEvent)
|
|
247
|
+
function buildEnvironmentSection(cwdEvents, envEvents, searchTool) {
|
|
248
|
+
if (cwdEvents.length === 0 && envEvents.length === 0)
|
|
162
249
|
return "";
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
parts.push(` <git op="${escapeXML(gitEvent.data)}" />`);
|
|
250
|
+
const summaryLines = [];
|
|
251
|
+
const queryTerms = [];
|
|
252
|
+
if (cwdEvents.length > 0) {
|
|
253
|
+
const lastCwd = cwdEvents[cwdEvents.length - 1];
|
|
254
|
+
summaryLines.push(` cwd: ${escapeXML(lastCwd.data)}`);
|
|
255
|
+
queryTerms.push("working directory");
|
|
170
256
|
}
|
|
171
257
|
for (const env of envEvents) {
|
|
172
|
-
|
|
258
|
+
summaryLines.push(` ${escapeXML(env.data)}`);
|
|
259
|
+
queryTerms.push(env.data);
|
|
173
260
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (errorEvents.length === 0)
|
|
182
|
-
return "";
|
|
183
|
-
const lines = [" <errors_encountered>"];
|
|
184
|
-
for (const ev of errorEvents) {
|
|
185
|
-
lines.push(` - ${escapeXML(truncateString(ev.data, 150))}`);
|
|
186
|
-
}
|
|
187
|
-
lines.push(" </errors_encountered>");
|
|
261
|
+
const queries = buildQueries(queryTerms);
|
|
262
|
+
const lines = [
|
|
263
|
+
` <environment>`,
|
|
264
|
+
...summaryLines,
|
|
265
|
+
toolCall(searchTool, queries),
|
|
266
|
+
` </environment>`,
|
|
267
|
+
];
|
|
188
268
|
return lines.join("\n");
|
|
189
269
|
}
|
|
190
|
-
|
|
191
|
-
* Render <intent> from the most recent intent event.
|
|
192
|
-
*/
|
|
193
|
-
export function renderIntent(intentEvent) {
|
|
194
|
-
return ` <intent mode="${escapeXML(intentEvent.data)}">${escapeXML(truncateString(intentEvent.data, 100))}</intent>`;
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Render <subagents> from subagent events.
|
|
198
|
-
* Shows agent dispatch status (launched/completed) and result summaries.
|
|
199
|
-
*/
|
|
200
|
-
export function renderSubagents(subagentEvents) {
|
|
270
|
+
function buildSubagentsSection(subagentEvents, searchTool) {
|
|
201
271
|
if (subagentEvents.length === 0)
|
|
202
272
|
return "";
|
|
203
|
-
const
|
|
273
|
+
const summaryLines = [];
|
|
274
|
+
const queryTerms = [];
|
|
204
275
|
for (const ev of subagentEvents) {
|
|
205
276
|
const status = ev.type === "subagent_completed" ? "completed"
|
|
206
277
|
: ev.type === "subagent_launched" ? "launched"
|
|
207
278
|
: "unknown";
|
|
208
|
-
|
|
279
|
+
summaryLines.push(` [${status}] ${escapeXML(ev.data)}`);
|
|
280
|
+
queryTerms.push(`subagent ${ev.data}`);
|
|
209
281
|
}
|
|
210
|
-
|
|
282
|
+
const queries = buildQueries(queryTerms);
|
|
283
|
+
const lines = [
|
|
284
|
+
` <subagents count="${subagentEvents.length}">`,
|
|
285
|
+
...summaryLines,
|
|
286
|
+
toolCall(searchTool, queries),
|
|
287
|
+
` </subagents>`,
|
|
288
|
+
];
|
|
211
289
|
return lines.join("\n");
|
|
212
290
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
* Deduplicates by tool name, shows usage count.
|
|
216
|
-
*/
|
|
217
|
-
export function renderMcpTools(mcpEvents) {
|
|
218
|
-
if (mcpEvents.length === 0)
|
|
291
|
+
function buildSkillsSection(skillEvents, searchTool) {
|
|
292
|
+
if (skillEvents.length === 0)
|
|
219
293
|
return "";
|
|
220
|
-
// Count
|
|
221
|
-
const
|
|
222
|
-
for (const ev of
|
|
223
|
-
const
|
|
224
|
-
|
|
294
|
+
// Count invocations per skill name
|
|
295
|
+
const skillCounts = new Map();
|
|
296
|
+
for (const ev of skillEvents) {
|
|
297
|
+
const name = ev.data.split(":")[0].trim();
|
|
298
|
+
skillCounts.set(name, (skillCounts.get(name) ?? 0) + 1);
|
|
225
299
|
}
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
300
|
+
const summaryLines = [];
|
|
301
|
+
const queryTerms = [];
|
|
302
|
+
for (const [name, count] of skillCounts) {
|
|
303
|
+
summaryLines.push(` ${escapeXML(name)} (${count}×)`);
|
|
304
|
+
queryTerms.push(`skill ${name} invocation`);
|
|
229
305
|
}
|
|
230
|
-
|
|
306
|
+
const queries = buildQueries(queryTerms);
|
|
307
|
+
const lines = [
|
|
308
|
+
` <skills count="${skillEvents.length}">`,
|
|
309
|
+
...summaryLines,
|
|
310
|
+
toolCall(searchTool, queries),
|
|
311
|
+
` </skills>`,
|
|
312
|
+
];
|
|
231
313
|
return lines.join("\n");
|
|
232
314
|
}
|
|
315
|
+
function buildIntentSection(intentEvents) {
|
|
316
|
+
if (intentEvents.length === 0)
|
|
317
|
+
return "";
|
|
318
|
+
const lastIntent = intentEvents[intentEvents.length - 1];
|
|
319
|
+
return ` <intent mode="${escapeXML(lastIntent.data)}"/>`;
|
|
320
|
+
}
|
|
233
321
|
// ── Main builder ─────────────────────────────────────────────────────────────
|
|
234
322
|
/**
|
|
235
|
-
* Build a resume snapshot XML string from stored session events.
|
|
323
|
+
* Build a reference-based resume snapshot XML string from stored session events.
|
|
236
324
|
*
|
|
237
325
|
* Algorithm:
|
|
238
326
|
* 1. Group events by category
|
|
239
|
-
* 2.
|
|
240
|
-
*
|
|
241
|
-
*
|
|
327
|
+
* 2. For each non-empty category, build a summary section with a runnable
|
|
328
|
+
* search tool call containing exact queries for full details
|
|
329
|
+
* 3. Assemble ALL non-empty sections — no priority dropping, no byte budget
|
|
242
330
|
*/
|
|
243
331
|
export function buildResumeSnapshot(events, opts) {
|
|
244
|
-
const maxBytes = opts?.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
245
332
|
const compactCount = opts?.compactCount ?? 1;
|
|
333
|
+
const searchTool = opts?.searchTool ?? "ctx_search";
|
|
246
334
|
const now = new Date().toISOString();
|
|
247
335
|
// ── Group events by category ──
|
|
248
336
|
const fileEvents = [];
|
|
@@ -255,8 +343,7 @@ export function buildResumeSnapshot(events, opts) {
|
|
|
255
343
|
const gitEvents = [];
|
|
256
344
|
const subagentEvents = [];
|
|
257
345
|
const intentEvents = [];
|
|
258
|
-
const
|
|
259
|
-
const planEvents = [];
|
|
346
|
+
const skillEvents = [];
|
|
260
347
|
for (const ev of events) {
|
|
261
348
|
switch (ev.category) {
|
|
262
349
|
case "file":
|
|
@@ -289,84 +376,56 @@ export function buildResumeSnapshot(events, opts) {
|
|
|
289
376
|
case "intent":
|
|
290
377
|
intentEvents.push(ev);
|
|
291
378
|
break;
|
|
292
|
-
case "
|
|
293
|
-
|
|
294
|
-
break;
|
|
295
|
-
case "plan":
|
|
296
|
-
planEvents.push(ev);
|
|
379
|
+
case "skill":
|
|
380
|
+
skillEvents.push(ev);
|
|
297
381
|
break;
|
|
298
382
|
}
|
|
299
383
|
}
|
|
300
|
-
// ──
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
384
|
+
// ── Build all sections ──
|
|
385
|
+
const sections = [];
|
|
386
|
+
// How-to-search instruction block (always present)
|
|
387
|
+
sections.push(` <how_to_search>
|
|
388
|
+
Each section below contains a summary of prior work.
|
|
389
|
+
For FULL DETAILS, run the exact tool call shown under each section.
|
|
390
|
+
Do NOT ask the user to re-explain prior work. Search first.
|
|
391
|
+
Do NOT invent your own queries — use the ones provided.
|
|
392
|
+
</how_to_search>`);
|
|
393
|
+
const files = buildFilesSection(fileEvents, searchTool);
|
|
394
|
+
if (files)
|
|
395
|
+
sections.push(files);
|
|
396
|
+
const errors = buildErrorsSection(errorEvents, searchTool);
|
|
397
|
+
if (errors)
|
|
398
|
+
sections.push(errors);
|
|
399
|
+
const decisions = buildDecisionsSection(decisionEvents, searchTool);
|
|
315
400
|
if (decisions)
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
401
|
+
sections.push(decisions);
|
|
402
|
+
const rules = buildRulesSection(ruleEvents, searchTool);
|
|
403
|
+
if (rules)
|
|
404
|
+
sections.push(rules);
|
|
405
|
+
const git = buildGitSection(gitEvents, searchTool);
|
|
406
|
+
if (git)
|
|
407
|
+
sections.push(git);
|
|
408
|
+
const tasks = buildTaskSection(taskEvents, searchTool);
|
|
409
|
+
if (tasks)
|
|
410
|
+
sections.push(tasks);
|
|
411
|
+
const environment = buildEnvironmentSection(cwdEvents, envEvents, searchTool);
|
|
320
412
|
if (environment)
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (lastPlan.type === "plan_enter") {
|
|
334
|
-
p2Sections.push(` <plan_mode status="active" />`);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
// P3-P4 sections (15% budget): intent, mcp_tools, launched subagents
|
|
338
|
-
const p3Sections = [];
|
|
339
|
-
if (intentEvents.length > 0) {
|
|
340
|
-
const lastIntent = intentEvents[intentEvents.length - 1];
|
|
341
|
-
p3Sections.push(renderIntent(lastIntent));
|
|
342
|
-
}
|
|
343
|
-
const mcpTools = renderMcpTools(mcpEvents);
|
|
344
|
-
if (mcpTools)
|
|
345
|
-
p3Sections.push(mcpTools);
|
|
346
|
-
const launchedSubagents = subagentEvents.filter(e => e.type === "subagent_launched");
|
|
347
|
-
const subagentsP3 = renderSubagents(launchedSubagents);
|
|
348
|
-
if (subagentsP3)
|
|
349
|
-
p3Sections.push(subagentsP3);
|
|
350
|
-
// ── Assemble with budget trimming ──
|
|
351
|
-
const header = `<session_resume compact_count="${compactCount}" events_captured="${events.length}" generated_at="${now}">`;
|
|
413
|
+
sections.push(environment);
|
|
414
|
+
const subagents = buildSubagentsSection(subagentEvents, searchTool);
|
|
415
|
+
if (subagents)
|
|
416
|
+
sections.push(subagents);
|
|
417
|
+
const skills = buildSkillsSection(skillEvents, searchTool);
|
|
418
|
+
if (skills)
|
|
419
|
+
sections.push(skills);
|
|
420
|
+
const intent = buildIntentSection(intentEvents);
|
|
421
|
+
if (intent)
|
|
422
|
+
sections.push(intent);
|
|
423
|
+
// ── Assemble ──
|
|
424
|
+
const header = `<session_resume events="${events.length}" compact_count="${compactCount}" generated_at="${now}">`;
|
|
352
425
|
const footer = `</session_resume>`;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
for (let dropFrom = tiers.length; dropFrom >= 0; dropFrom--) {
|
|
357
|
-
const activeTiers = tiers.slice(0, dropFrom);
|
|
358
|
-
const body = activeTiers.flat().join("\n");
|
|
359
|
-
let xml;
|
|
360
|
-
if (body) {
|
|
361
|
-
xml = `${header}\n${body}\n${footer}`;
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
xml = `${header}\n${footer}`;
|
|
365
|
-
}
|
|
366
|
-
if (Buffer.byteLength(xml) <= maxBytes) {
|
|
367
|
-
return xml;
|
|
368
|
-
}
|
|
426
|
+
const body = sections.join("\n\n");
|
|
427
|
+
if (body) {
|
|
428
|
+
return `${header}\n\n${body}\n\n${footer}`;
|
|
369
429
|
}
|
|
370
|
-
// If even header+footer is over budget, return the minimal XML
|
|
371
430
|
return `${header}\n${footer}`;
|
|
372
431
|
}
|
package/build/truncate.d.ts
CHANGED
|
@@ -5,16 +5,6 @@
|
|
|
5
5
|
* SessionDB (snapshot building). They are extracted here so any
|
|
6
6
|
* consumer can import them without pulling in the full store or executor.
|
|
7
7
|
*/
|
|
8
|
-
/**
|
|
9
|
-
* Truncate a string to at most `maxChars` characters, appending an ellipsis
|
|
10
|
-
* when truncation occurs.
|
|
11
|
-
*
|
|
12
|
-
* @param str - Input string.
|
|
13
|
-
* @param maxChars - Maximum character count (inclusive). Must be >= 3.
|
|
14
|
-
* @returns The original string if short enough, otherwise a truncated string
|
|
15
|
-
* ending with "...".
|
|
16
|
-
*/
|
|
17
|
-
export declare function truncateString(str: string, maxChars: number): string;
|
|
18
8
|
/**
|
|
19
9
|
* Serialize a value to JSON, then truncate the result to `maxBytes` bytes.
|
|
20
10
|
* If truncation occurs, the string is cut at a UTF-8-safe boundary and
|
package/build/truncate.js
CHANGED
|
@@ -6,23 +6,6 @@
|
|
|
6
6
|
* consumer can import them without pulling in the full store or executor.
|
|
7
7
|
*/
|
|
8
8
|
// ─────────────────────────────────────────────────────────
|
|
9
|
-
// String truncation
|
|
10
|
-
// ─────────────────────────────────────────────────────────
|
|
11
|
-
/**
|
|
12
|
-
* Truncate a string to at most `maxChars` characters, appending an ellipsis
|
|
13
|
-
* when truncation occurs.
|
|
14
|
-
*
|
|
15
|
-
* @param str - Input string.
|
|
16
|
-
* @param maxChars - Maximum character count (inclusive). Must be >= 3.
|
|
17
|
-
* @returns The original string if short enough, otherwise a truncated string
|
|
18
|
-
* ending with "...".
|
|
19
|
-
*/
|
|
20
|
-
export function truncateString(str, maxChars) {
|
|
21
|
-
if (str.length <= maxChars)
|
|
22
|
-
return str;
|
|
23
|
-
return str.slice(0, Math.max(0, maxChars - 3)) + "...";
|
|
24
|
-
}
|
|
25
|
-
// ─────────────────────────────────────────────────────────
|
|
26
9
|
// JSON truncation
|
|
27
10
|
// ─────────────────────────────────────────────────────────
|
|
28
11
|
/**
|