volute 0.39.0 → 0.40.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/dist/{activity-events-CR4RIHJT.js → activity-events-OJZ7G5CG.js} +3 -3
- package/dist/{ai-service-PL7GKFQC.js → ai-service-JMV4KZN4.js} +8 -4
- package/dist/{archive-UEWIFRAR.js → archive-BC2MTNPV.js} +3 -3
- package/dist/{auth-OJ2YW3NR.js → auth-2IMZ6N5G.js} +4 -4
- package/dist/{chat-RQ5NV5EA.js → chat-O5QCGCUG.js} +2 -2
- package/dist/{chunk-TCNCRDB5.js → chunk-2AQCEQYN.js} +1 -1
- package/dist/{chunk-OEXOFBW5.js → chunk-3XSFMN3H.js} +1 -1
- package/dist/{chunk-7OS7QFDF.js → chunk-43PVCF4V.js} +3 -3
- package/dist/{chunk-7GZB3ZJP.js → chunk-5VBXKFPO.js} +2 -2
- package/dist/{chunk-IA2SZSLB.js → chunk-76Q6YLJL.js} +1 -1
- package/dist/{chunk-QDWPM73D.js → chunk-B7YJUWMM.js} +3 -3
- package/dist/{chunk-TT4WT6Y7.js → chunk-DNQPHLGM.js} +1 -1
- package/dist/{chunk-6FSSMAXU.js → chunk-FBV22PFY.js} +1 -1
- package/dist/{chunk-RAVQXKAG.js → chunk-FRJ73IWR.js} +1 -1
- package/dist/{chunk-WUH54G5S.js → chunk-HAJHDGBV.js} +1 -1
- package/dist/{chunk-X7GM2OPM.js → chunk-HEZM5GCV.js} +2 -2
- package/dist/{chunk-4BO4I7UF.js → chunk-JSITYTIL.js} +6 -6
- package/dist/{chunk-CJ26DXZL.js → chunk-MHFAWJCC.js} +16 -0
- package/dist/{chunk-OS5MRIEP.js → chunk-PC55DGNX.js} +1 -1
- package/dist/{chunk-QDWP435B.js → chunk-PDSA3ILI.js} +1 -1
- package/dist/{chunk-B7MWXFAH.js → chunk-PGX52NFO.js} +15 -1
- package/dist/{chunk-7W3UWCQD.js → chunk-PHR4XQPL.js} +3 -3
- package/dist/{chunk-EVKGKQ2P.js → chunk-PIRL2HAO.js} +1 -7
- package/dist/{chunk-Y5UC6G3O.js → chunk-PZTVQL3J.js} +2 -2
- package/dist/{chunk-DZ4LYU6J.js → chunk-RMZSICLS.js} +1 -1
- package/dist/{chunk-XOSAY7PY.js → chunk-S4YO5OWV.js} +1 -1
- package/dist/{chunk-6V2U3GFQ.js → chunk-SGC6CDXB.js} +2 -2
- package/dist/{chunk-QJ4GU5XT.js → chunk-SOPMVTTW.js} +2 -2
- package/dist/{chunk-IBOU6Q3Y.js → chunk-T26YLQPU.js} +5 -5
- package/dist/{chunk-Y4W3OS7Q.js → chunk-WGRMLXTH.js} +8 -8
- package/dist/{chunk-CHCISTH7.js → chunk-XUJR6NZW.js} +508 -332
- package/dist/cli.js +15 -15
- package/dist/{cloud-sync-NQCWJLYI.js → cloud-sync-5JF2Z7VM.js} +16 -16
- package/dist/{conversations-EUXHBWJD.js → conversations-LUVHGQSS.js} +4 -4
- package/dist/{create-3QH7DVLC.js → create-TDQM3SHV.js} +1 -1
- package/dist/{daemon-restart-UEB3PLKB.js → daemon-restart-XHDJDOPQ.js} +8 -8
- package/dist/daemon.js +219 -116
- package/dist/{db-32YFKMFL.js → db-2GIS7HSG.js} +2 -2
- package/dist/delivery-manager-L5WFP2ND.js +30 -0
- package/dist/{delivery-router-EUW4RIWG.js → delivery-router-IU4OMVKZ.js} +3 -3
- package/dist/down-KIJVZ3OC.js +17 -0
- package/dist/echo-text-TJWCUBPB.js +31 -0
- package/dist/{exec-2MFHTGOS.js → exec-BGMU7VXR.js} +4 -4
- package/dist/{export-JDAK3THX.js → export-RKUWSQSG.js} +3 -3
- package/dist/{extensions-V7TXKAKM.js → extensions-VN5SEVRP.js} +16 -16
- package/dist/{files-K5XV23H6.js → files-J2CKZ6PK.js} +3 -3
- package/dist/{import-GZ5IYVV7.js → import-KVY6BYXG.js} +6 -6
- package/dist/{isolation-FPF4UTH2.js → isolation-MM7YHK6J.js} +3 -3
- package/dist/{login-BGF5JY3I.js → login-OHM32QVD.js} +2 -2
- package/dist/{logout-GQHI6KH6.js → logout-74WBDMOE.js} +2 -2
- package/dist/{message-delivery-X7MKA3TA.js → message-delivery-VBBAJX3J.js} +16 -16
- package/dist/{mind-QMJIWWWW.js → mind-PUPXDIYQ.js} +5 -5
- package/dist/{mind-activity-tracker-Z3KBQTVU.js → mind-activity-tracker-7GE24YZE.js} +4 -4
- package/dist/mind-manager-4Q6KGZHL.js +30 -0
- package/dist/mind-service-7OD4PRO5.js +36 -0
- package/dist/{package-IR4ZEQA2.js → package-6YPVWGD3.js} +1 -1
- package/dist/{registry-LWEOAO53.js → registry-L272BZJR.js} +2 -2
- package/dist/{sandbox-KUAVC37A.js → sandbox-BQAFYHLE.js} +4 -4
- package/dist/scheduler-7E22Q7IC.js +30 -0
- package/dist/{schema-K575EBPE.js → schema-HHYJDG7B.js} +3 -1
- package/dist/{seed-GPBERPXJ.js → seed-4V2O7FQE.js} +1 -1
- package/dist/{seed-cmd-PWF4FYFG.js → seed-cmd-JKDEW4MP.js} +2 -2
- package/dist/{seed-create-NTIICZVB.js → seed-create-EPAO3H5F.js} +3 -3
- package/dist/{seed-sprout-PRPFKGJF.js → seed-sprout-EONCL4DU.js} +8 -8
- package/dist/{send-IMHDYJHW.js → send-232RAHPX.js} +3 -3
- package/dist/{service-DWBRRFXS.js → service-AG5BKRUV.js} +5 -5
- package/dist/{service-install-KEBDNZVE.js → service-install-HVAEUBAH.js} +6 -6
- package/dist/{setup-IFYS3TLS.js → setup-IL7QJZN4.js} +3 -3
- package/dist/{setup-4CWQ53VV.js → setup-PSA7LJ5U.js} +8 -8
- package/dist/{skills-FJ5W73AV.js → skills-SLEY5X6W.js} +6 -6
- package/dist/sleep-manager-27S7MKJO.js +34 -0
- package/dist/{spirit-U6MDQLYT.js → spirit-7BUHSEO2.js} +8 -8
- package/dist/{sprout-ABX2G76C.js → sprout-JWQ57KF3.js} +1 -1
- package/dist/{status-C6BBWNU3.js → status-6GSOSYOD.js} +6 -6
- package/dist/system-chat-EIINIHFB.js +34 -0
- package/dist/{tailscale-2KKXDMNK.js → tailscale-6MHNLBAX.js} +2 -2
- package/dist/up-4XS3RCPP.js +19 -0
- package/dist/{update-V26PT4VN.js → update-BT2PY73H.js} +6 -6
- package/dist/{update-check-XRERV36Y.js → update-check-Y3HBEMF2.js} +3 -3
- package/dist/{version-notify-ZBMY2TEX.js → version-notify-NFQMI5IY.js} +17 -17
- package/dist/web-assets/assets/index-DqHSW9Fr.css +1 -0
- package/dist/web-assets/assets/index-Jluza4c-.js +73 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0009_mind_notices.sql +14 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/templates/_base/.init/.local/hooks/pre-prompt/notices.ts +46 -0
- package/templates/_base/src/lib/context-breakdown.ts +281 -126
- package/templates/_base/src/lib/daemon-client.ts +5 -2
- package/templates/_base/src/lib/transparency.ts +5 -3
- package/templates/_base/src/lib/volute-server.ts +23 -0
- package/templates/claude/src/agent.ts +54 -16
- package/templates/claude/src/server.ts +1 -0
- package/templates/codex/src/agent.ts +45 -12
- package/templates/codex/src/server.ts +1 -0
- package/templates/pi/src/agent.ts +72 -13
- package/templates/pi/src/server.ts +1 -0
- package/dist/delivery-manager-N34XKUGL.js +0 -30
- package/dist/down-TCEBGTMY.js +0 -17
- package/dist/echo-text-N3BC3WNX.js +0 -31
- package/dist/mind-manager-7GWTTKWD.js +0 -30
- package/dist/mind-service-MO2XJKJN.js +0 -36
- package/dist/scheduler-CGUPAEBA.js +0 -30
- package/dist/sleep-manager-6QRMUNHK.js +0 -34
- package/dist/system-chat-D343HYGR.js +0 -34
- package/dist/up-ZU6ILVMA.js +0 -19
- package/dist/web-assets/assets/index-B1zwqwt8.css +0 -1
- package/dist/web-assets/assets/index-D1guJF1i.js +0 -73
|
@@ -29,6 +29,26 @@ function countTokens(text: string): number {
|
|
|
29
29
|
return _countTokens(text);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// --- Shared JSONL reader ---
|
|
33
|
+
|
|
34
|
+
function readJsonlEntries<T>(filePath: string): T[] | null {
|
|
35
|
+
let data: string;
|
|
36
|
+
try {
|
|
37
|
+
data = readFileSync(filePath, "utf-8");
|
|
38
|
+
} catch (err: any) {
|
|
39
|
+
if (err?.code !== "ENOENT") console.warn(`context-breakdown: ${filePath}:`, err?.message);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const entries: T[] = [];
|
|
44
|
+
for (const line of data.split("\n").filter((l) => l.trim())) {
|
|
45
|
+
try {
|
|
46
|
+
entries.push(JSON.parse(line));
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
return entries;
|
|
50
|
+
}
|
|
51
|
+
|
|
32
52
|
// --- Claude JSONL parser ---
|
|
33
53
|
|
|
34
54
|
type ClaudeContentBlock = {
|
|
@@ -37,6 +57,8 @@ type ClaudeContentBlock = {
|
|
|
37
57
|
thinking?: string;
|
|
38
58
|
input?: unknown;
|
|
39
59
|
content?: unknown;
|
|
60
|
+
name?: string;
|
|
61
|
+
is_error?: boolean;
|
|
40
62
|
};
|
|
41
63
|
|
|
42
64
|
type ClaudeMessage = {
|
|
@@ -57,32 +79,52 @@ export type ParsedContext = {
|
|
|
57
79
|
breakdown: ContextBreakdown;
|
|
58
80
|
};
|
|
59
81
|
|
|
60
|
-
|
|
82
|
+
// --- Message extraction types ---
|
|
83
|
+
|
|
84
|
+
export type ContextBlock =
|
|
85
|
+
| { type: "text"; text: string }
|
|
86
|
+
| { type: "thinking"; text: string }
|
|
87
|
+
| { type: "tool_use"; name: string; input: string }
|
|
88
|
+
| { type: "tool_result"; text: string; isError?: boolean };
|
|
89
|
+
|
|
90
|
+
export type ContextMessage = {
|
|
91
|
+
role: "user" | "assistant" | "system";
|
|
92
|
+
blocks: ContextBlock[];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type ContextMessages = {
|
|
96
|
+
preamble: {
|
|
97
|
+
systemPrompt: string;
|
|
98
|
+
sdkInstructions: string;
|
|
99
|
+
skillDescriptions: Array<{ name: string; description: string }>;
|
|
100
|
+
};
|
|
101
|
+
sessions: Array<{
|
|
102
|
+
name: string;
|
|
103
|
+
messages: ContextMessage[];
|
|
104
|
+
}>;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// --- Claude: combined parse + extract ---
|
|
108
|
+
|
|
109
|
+
type SessionResult = {
|
|
110
|
+
parsed: ParsedContext | null;
|
|
111
|
+
messages: ContextMessage[];
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export function processClaudeSession(
|
|
61
115
|
filePath: string,
|
|
62
116
|
systemPromptTokens: number,
|
|
63
117
|
claudeMdTokens: number,
|
|
64
118
|
skillDescriptionTokens: number,
|
|
65
|
-
):
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
data = readFileSync(filePath, "utf-8");
|
|
69
|
-
} catch (err: any) {
|
|
70
|
-
if (err?.code !== "ENOENT") console.warn(`context-breakdown: ${filePath}:`, err?.message);
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
119
|
+
): SessionResult {
|
|
120
|
+
const entries = readJsonlEntries<ClaudeMessage>(filePath);
|
|
121
|
+
if (!entries) return { parsed: null, messages: [] };
|
|
73
122
|
|
|
74
|
-
const lines = data.split("\n").filter((l) => l.trim());
|
|
75
123
|
let lastContextTokens = 0;
|
|
76
124
|
const conv = { userText: 0, assistantText: 0, thinking: 0, toolUse: 0, toolResult: 0 };
|
|
125
|
+
const messages: ContextMessage[] = [];
|
|
77
126
|
|
|
78
|
-
for (const
|
|
79
|
-
let entry: ClaudeMessage;
|
|
80
|
-
try {
|
|
81
|
-
entry = JSON.parse(line);
|
|
82
|
-
} catch {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
|
|
127
|
+
for (const entry of entries) {
|
|
86
128
|
if (entry.type === "assistant" && entry.message) {
|
|
87
129
|
const usage = entry.message.usage;
|
|
88
130
|
if (usage) {
|
|
@@ -92,49 +134,80 @@ export function parseClaudeSessionJSONL(
|
|
|
92
134
|
(usage.cache_read_input_tokens ?? 0);
|
|
93
135
|
if (ctx > 0) lastContextTokens = ctx;
|
|
94
136
|
}
|
|
137
|
+
const blocks: ContextBlock[] = [];
|
|
95
138
|
for (const block of entry.message.content ?? []) {
|
|
96
139
|
if (block.type === "thinking" && block.thinking) {
|
|
97
140
|
conv.thinking += countTokens(block.thinking);
|
|
141
|
+
blocks.push({ type: "thinking", text: block.thinking });
|
|
98
142
|
} else if (block.type === "text" && block.text) {
|
|
99
143
|
conv.assistantText += countTokens(block.text);
|
|
144
|
+
blocks.push({ type: "text", text: block.text });
|
|
100
145
|
} else if (block.type === "tool_use") {
|
|
101
146
|
const input = block.input;
|
|
147
|
+
const inputStr = typeof input === "string" ? input : JSON.stringify(input ?? {}, null, 2);
|
|
102
148
|
conv.toolUse += countTokens(
|
|
103
149
|
typeof input === "string" ? input : JSON.stringify(input ?? {}),
|
|
104
150
|
);
|
|
151
|
+
blocks.push({ type: "tool_use", name: block.name ?? "unknown", input: inputStr });
|
|
105
152
|
}
|
|
106
153
|
}
|
|
154
|
+
if (blocks.length > 0) messages.push({ role: "assistant", blocks });
|
|
107
155
|
} else if (entry.type === "user" && entry.message) {
|
|
156
|
+
const blocks: ContextBlock[] = [];
|
|
108
157
|
for (const block of entry.message.content ?? []) {
|
|
109
158
|
if (block.type === "tool_result") {
|
|
110
159
|
const content = block.content;
|
|
160
|
+
const isError = block.is_error === true;
|
|
111
161
|
if (typeof content === "string") {
|
|
112
162
|
conv.toolResult += countTokens(content);
|
|
163
|
+
blocks.push({ type: "tool_result", text: content, isError });
|
|
113
164
|
} else if (Array.isArray(content)) {
|
|
114
165
|
for (const c of content) {
|
|
115
166
|
if (c && typeof c === "object" && "text" in c && typeof c.text === "string") {
|
|
116
167
|
conv.toolResult += countTokens(c.text);
|
|
168
|
+
blocks.push({ type: "tool_result", text: c.text, isError });
|
|
117
169
|
}
|
|
118
170
|
}
|
|
119
171
|
}
|
|
120
172
|
} else if (block.type === "text" && block.text) {
|
|
121
173
|
conv.userText += countTokens(block.text);
|
|
174
|
+
blocks.push({ type: "text", text: block.text });
|
|
122
175
|
}
|
|
123
176
|
}
|
|
177
|
+
if (blocks.length > 0) messages.push({ role: "user", blocks });
|
|
124
178
|
}
|
|
125
179
|
}
|
|
126
180
|
|
|
127
|
-
|
|
181
|
+
const parsed =
|
|
182
|
+
lastContextTokens === 0
|
|
183
|
+
? null
|
|
184
|
+
: {
|
|
185
|
+
contextTokens: lastContextTokens,
|
|
186
|
+
breakdown: {
|
|
187
|
+
systemPrompt: systemPromptTokens,
|
|
188
|
+
sdkInstructions: claudeMdTokens,
|
|
189
|
+
skillDescriptions: skillDescriptionTokens,
|
|
190
|
+
conversation: conv,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return { parsed, messages };
|
|
195
|
+
}
|
|
128
196
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
197
|
+
/** @deprecated Use processClaudeSession instead */
|
|
198
|
+
export function parseClaudeSessionJSONL(
|
|
199
|
+
filePath: string,
|
|
200
|
+
systemPromptTokens: number,
|
|
201
|
+
claudeMdTokens: number,
|
|
202
|
+
skillDescriptionTokens: number,
|
|
203
|
+
): ParsedContext | null {
|
|
204
|
+
return processClaudeSession(filePath, systemPromptTokens, claudeMdTokens, skillDescriptionTokens)
|
|
205
|
+
.parsed;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** @deprecated Use processClaudeSession instead */
|
|
209
|
+
export function extractClaudeSessionMessages(filePath: string): ContextMessage[] {
|
|
210
|
+
return processClaudeSession(filePath, 0, 0, 0).messages;
|
|
138
211
|
}
|
|
139
212
|
|
|
140
213
|
// --- Codex JSONL parser ---
|
|
@@ -160,32 +233,22 @@ type CodexEntry = {
|
|
|
160
233
|
};
|
|
161
234
|
};
|
|
162
235
|
|
|
163
|
-
|
|
236
|
+
// --- Codex: combined parse + extract ---
|
|
237
|
+
|
|
238
|
+
export function processCodexSession(
|
|
164
239
|
filePath: string,
|
|
165
240
|
systemPromptTokens: number,
|
|
166
241
|
claudeMdTokens: number,
|
|
167
242
|
skillDescriptionTokens: number,
|
|
168
|
-
):
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
data = readFileSync(filePath, "utf-8");
|
|
172
|
-
} catch (err: any) {
|
|
173
|
-
if (err?.code !== "ENOENT") console.warn(`context-breakdown: ${filePath}:`, err?.message);
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
243
|
+
): SessionResult {
|
|
244
|
+
const entries = readJsonlEntries<CodexEntry>(filePath);
|
|
245
|
+
if (!entries) return { parsed: null, messages: [] };
|
|
176
246
|
|
|
177
|
-
const lines = data.split("\n").filter((l) => l.trim());
|
|
178
247
|
let lastContextTokens = 0;
|
|
179
248
|
const conv = { userText: 0, assistantText: 0, thinking: 0, toolUse: 0, toolResult: 0 };
|
|
249
|
+
const messages: ContextMessage[] = [];
|
|
180
250
|
|
|
181
|
-
for (const
|
|
182
|
-
let entry: CodexEntry;
|
|
183
|
-
try {
|
|
184
|
-
entry = JSON.parse(line);
|
|
185
|
-
} catch {
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
|
|
251
|
+
for (const entry of entries) {
|
|
189
252
|
const payload = entry.payload;
|
|
190
253
|
if (!payload) continue;
|
|
191
254
|
|
|
@@ -196,45 +259,83 @@ export function parseCodexSessionJSONL(
|
|
|
196
259
|
}
|
|
197
260
|
} else if (entry.type === "response_item") {
|
|
198
261
|
if (payload.type === "reasoning") {
|
|
199
|
-
// Reasoning summaries are in the `summary` field (array of {text} objects)
|
|
200
262
|
const summary = payload.summary;
|
|
263
|
+
const blocks: ContextBlock[] = [];
|
|
201
264
|
if (Array.isArray(summary)) {
|
|
202
265
|
for (const s of summary) {
|
|
203
|
-
if (s && typeof s === "object" && s.text)
|
|
266
|
+
if (s && typeof s === "object" && s.text) {
|
|
267
|
+
conv.thinking += countTokens(s.text);
|
|
268
|
+
blocks.push({ type: "thinking", text: s.text });
|
|
269
|
+
}
|
|
204
270
|
}
|
|
205
271
|
} else if (typeof summary === "string" && summary) {
|
|
206
272
|
conv.thinking += countTokens(summary);
|
|
273
|
+
blocks.push({ type: "thinking", text: summary });
|
|
207
274
|
}
|
|
275
|
+
if (blocks.length > 0) messages.push({ role: "assistant", blocks });
|
|
208
276
|
} else if (payload.type === "message") {
|
|
209
277
|
const text = payload.content?.map((c) => c.text ?? "").join("") ?? "";
|
|
210
278
|
if (text) {
|
|
211
|
-
|
|
279
|
+
const role: ContextMessage["role"] =
|
|
280
|
+
payload.role === "assistant"
|
|
281
|
+
? "assistant"
|
|
282
|
+
: payload.role === "developer"
|
|
283
|
+
? "system"
|
|
284
|
+
: "user";
|
|
285
|
+
if (role === "assistant") {
|
|
212
286
|
conv.assistantText += countTokens(text);
|
|
213
287
|
} else {
|
|
214
288
|
conv.userText += countTokens(text);
|
|
215
289
|
}
|
|
290
|
+
messages.push({ role, blocks: [{ type: "text", text }] });
|
|
216
291
|
}
|
|
217
292
|
} else if (payload.type === "function_call") {
|
|
218
293
|
const args = payload.arguments ?? "";
|
|
219
294
|
if (args) conv.toolUse += countTokens(args);
|
|
295
|
+
messages.push({
|
|
296
|
+
role: "assistant",
|
|
297
|
+
blocks: [{ type: "tool_use", name: payload.name ?? "unknown", input: args }],
|
|
298
|
+
});
|
|
220
299
|
} else if (payload.type === "function_call_output") {
|
|
221
300
|
const output = payload.output ?? "";
|
|
222
|
-
if (output)
|
|
301
|
+
if (output) {
|
|
302
|
+
conv.toolResult += countTokens(output);
|
|
303
|
+
messages.push({ role: "user", blocks: [{ type: "tool_result", text: output }] });
|
|
304
|
+
}
|
|
223
305
|
}
|
|
224
306
|
}
|
|
225
307
|
}
|
|
226
308
|
|
|
227
|
-
|
|
309
|
+
const parsed =
|
|
310
|
+
lastContextTokens === 0
|
|
311
|
+
? null
|
|
312
|
+
: {
|
|
313
|
+
contextTokens: lastContextTokens,
|
|
314
|
+
breakdown: {
|
|
315
|
+
systemPrompt: systemPromptTokens,
|
|
316
|
+
sdkInstructions: claudeMdTokens,
|
|
317
|
+
skillDescriptions: skillDescriptionTokens,
|
|
318
|
+
conversation: conv,
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
return { parsed, messages };
|
|
323
|
+
}
|
|
228
324
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
325
|
+
/** @deprecated Use processCodexSession instead */
|
|
326
|
+
export function parseCodexSessionJSONL(
|
|
327
|
+
filePath: string,
|
|
328
|
+
systemPromptTokens: number,
|
|
329
|
+
claudeMdTokens: number,
|
|
330
|
+
skillDescriptionTokens: number,
|
|
331
|
+
): ParsedContext | null {
|
|
332
|
+
return processCodexSession(filePath, systemPromptTokens, claudeMdTokens, skillDescriptionTokens)
|
|
333
|
+
.parsed;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** @deprecated Use processCodexSession instead */
|
|
337
|
+
export function extractCodexSessionMessages(filePath: string): ContextMessage[] {
|
|
338
|
+
return processCodexSession(filePath, 0, 0, 0).messages;
|
|
238
339
|
}
|
|
239
340
|
|
|
240
341
|
// --- Pi JSONL parser ---
|
|
@@ -245,41 +346,38 @@ type PiEntry = {
|
|
|
245
346
|
message?: {
|
|
246
347
|
role?: string;
|
|
247
348
|
usage?: { input?: number; cacheRead?: number; cacheWrite?: number };
|
|
248
|
-
content?: Array<{
|
|
349
|
+
content?: Array<{
|
|
350
|
+
type?: string;
|
|
351
|
+
text?: string;
|
|
352
|
+
thinking?: string;
|
|
353
|
+
arguments?: unknown;
|
|
354
|
+
name?: string;
|
|
355
|
+
}>;
|
|
249
356
|
};
|
|
250
357
|
// custom_message entries (hook-injected context: reply-instructions, startup-context, etc.)
|
|
251
358
|
content?: string;
|
|
252
359
|
};
|
|
253
360
|
|
|
254
|
-
|
|
361
|
+
// --- Pi: combined parse + extract ---
|
|
362
|
+
|
|
363
|
+
export function processPiSession(
|
|
255
364
|
filePath: string,
|
|
256
365
|
systemPromptTokens: number,
|
|
257
366
|
claudeMdTokens: number,
|
|
258
367
|
skillDescriptionTokens: number,
|
|
259
|
-
):
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
data = readFileSync(filePath, "utf-8");
|
|
263
|
-
} catch (err: any) {
|
|
264
|
-
if (err?.code !== "ENOENT") console.warn(`context-breakdown: ${filePath}:`, err?.message);
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
368
|
+
): SessionResult {
|
|
369
|
+
const entries = readJsonlEntries<PiEntry>(filePath);
|
|
370
|
+
if (!entries) return { parsed: null, messages: [] };
|
|
267
371
|
|
|
268
|
-
const lines = data.split("\n").filter((l) => l.trim());
|
|
269
372
|
let lastContextTokens = 0;
|
|
270
373
|
const conv = { userText: 0, assistantText: 0, thinking: 0, toolUse: 0, toolResult: 0 };
|
|
374
|
+
const messages: ContextMessage[] = [];
|
|
271
375
|
|
|
272
|
-
for (const
|
|
273
|
-
let entry: PiEntry;
|
|
274
|
-
try {
|
|
275
|
-
entry = JSON.parse(line);
|
|
276
|
-
} catch {
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
|
|
376
|
+
for (const entry of entries) {
|
|
280
377
|
// custom_message entries are hook-injected context (reply-instructions, startup-context)
|
|
281
378
|
if (entry.type === "custom_message" && entry.content) {
|
|
282
379
|
conv.userText += countTokens(entry.content);
|
|
380
|
+
messages.push({ role: "user", blocks: [{ type: "text", text: entry.content }] });
|
|
283
381
|
continue;
|
|
284
382
|
}
|
|
285
383
|
|
|
@@ -292,75 +390,103 @@ export function parsePiSessionJSONL(
|
|
|
292
390
|
const ctx = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
293
391
|
if (ctx > 0) lastContextTokens = ctx;
|
|
294
392
|
}
|
|
393
|
+
const blocks: ContextBlock[] = [];
|
|
295
394
|
for (const block of msg.content ?? []) {
|
|
296
395
|
if (block.type === "thinking" && block.thinking) {
|
|
297
396
|
conv.thinking += countTokens(block.thinking);
|
|
397
|
+
blocks.push({ type: "thinking", text: block.thinking });
|
|
298
398
|
} else if (block.type === "text" && block.text) {
|
|
299
399
|
conv.assistantText += countTokens(block.text);
|
|
400
|
+
blocks.push({ type: "text", text: block.text });
|
|
300
401
|
} else if (block.type === "toolCall") {
|
|
301
402
|
const args = block.arguments;
|
|
403
|
+
const argsStr = typeof args === "string" ? args : JSON.stringify(args ?? {}, null, 2);
|
|
302
404
|
conv.toolUse += countTokens(typeof args === "string" ? args : JSON.stringify(args ?? {}));
|
|
405
|
+
blocks.push({ type: "tool_use", name: block.name ?? "unknown", input: argsStr });
|
|
303
406
|
}
|
|
304
407
|
}
|
|
408
|
+
if (blocks.length > 0) messages.push({ role: "assistant", blocks });
|
|
305
409
|
} else if (msg.role === "toolResult") {
|
|
410
|
+
const blocks: ContextBlock[] = [];
|
|
306
411
|
for (const block of msg.content ?? []) {
|
|
307
|
-
if (block.text)
|
|
412
|
+
if (block.text) {
|
|
413
|
+
conv.toolResult += countTokens(block.text);
|
|
414
|
+
blocks.push({ type: "tool_result", text: block.text });
|
|
415
|
+
}
|
|
308
416
|
}
|
|
417
|
+
if (blocks.length > 0) messages.push({ role: "user", blocks });
|
|
309
418
|
} else if (msg.role === "user") {
|
|
419
|
+
const blocks: ContextBlock[] = [];
|
|
310
420
|
for (const block of msg.content ?? []) {
|
|
311
421
|
if (block.type === "text" && block.text) {
|
|
312
422
|
conv.userText += countTokens(block.text);
|
|
423
|
+
blocks.push({ type: "text", text: block.text });
|
|
313
424
|
}
|
|
314
425
|
}
|
|
426
|
+
if (blocks.length > 0) messages.push({ role: "user", blocks });
|
|
315
427
|
}
|
|
316
428
|
}
|
|
317
429
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
430
|
+
const parsed =
|
|
431
|
+
lastContextTokens === 0
|
|
432
|
+
? null
|
|
433
|
+
: {
|
|
434
|
+
contextTokens: lastContextTokens,
|
|
435
|
+
breakdown: {
|
|
436
|
+
systemPrompt: systemPromptTokens,
|
|
437
|
+
sdkInstructions: claudeMdTokens,
|
|
438
|
+
skillDescriptions: skillDescriptionTokens,
|
|
439
|
+
conversation: conv,
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
return { parsed, messages };
|
|
329
444
|
}
|
|
330
445
|
|
|
331
|
-
|
|
446
|
+
/** @deprecated Use processPiSession instead */
|
|
447
|
+
export function parsePiSessionJSONL(
|
|
448
|
+
filePath: string,
|
|
449
|
+
systemPromptTokens: number,
|
|
450
|
+
claudeMdTokens: number,
|
|
451
|
+
skillDescriptionTokens: number,
|
|
452
|
+
): ParsedContext | null {
|
|
453
|
+
return processPiSession(filePath, systemPromptTokens, claudeMdTokens, skillDescriptionTokens)
|
|
454
|
+
.parsed;
|
|
455
|
+
}
|
|
332
456
|
|
|
333
|
-
/**
|
|
334
|
-
export function
|
|
335
|
-
return
|
|
457
|
+
/** @deprecated Use processPiSession instead */
|
|
458
|
+
export function extractPiSessionMessages(filePath: string): ContextMessage[] {
|
|
459
|
+
return processPiSession(filePath, 0, 0, 0).messages;
|
|
336
460
|
}
|
|
337
461
|
|
|
338
|
-
|
|
339
|
-
|
|
462
|
+
// --- Preamble text readers ---
|
|
463
|
+
|
|
464
|
+
/** Read the SDK instruction file content (CLAUDE.md, MINDS.md, or AGENTS.md). */
|
|
465
|
+
export function readSdkInstructions(cwd: string): string {
|
|
340
466
|
for (const name of ["CLAUDE.md", "MINDS.md", "AGENTS.md"]) {
|
|
341
467
|
try {
|
|
342
|
-
|
|
343
|
-
return countTokens(content);
|
|
468
|
+
return readFileSync(resolve(cwd, name), "utf-8");
|
|
344
469
|
} catch (err: any) {
|
|
345
470
|
if (err?.code !== "ENOENT")
|
|
346
471
|
console.warn(`context-breakdown: ${resolve(cwd, name)}:`, err?.message);
|
|
347
472
|
}
|
|
348
473
|
}
|
|
349
|
-
return
|
|
474
|
+
return "";
|
|
350
475
|
}
|
|
351
476
|
|
|
352
|
-
/**
|
|
353
|
-
export function
|
|
354
|
-
|
|
477
|
+
/** Read skill names and descriptions from SKILL.md frontmatter. */
|
|
478
|
+
export function readSkillDescriptions(
|
|
479
|
+
skillsDirs: string[],
|
|
480
|
+
): Array<{ name: string; description: string }> {
|
|
481
|
+
const results: Array<{ name: string; description: string }> = [];
|
|
355
482
|
for (const dir of skillsDirs) {
|
|
356
483
|
try {
|
|
357
484
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
358
485
|
if (!entry.isDirectory()) continue;
|
|
359
486
|
try {
|
|
360
487
|
const content = readFileSync(resolve(dir, entry.name, "SKILL.md"), "utf-8");
|
|
361
|
-
// Extract just the frontmatter description — that's what's always in context
|
|
362
488
|
const match = content.match(/^description:\s*(.+?)$/m);
|
|
363
|
-
if (match)
|
|
489
|
+
if (match) results.push({ name: entry.name, description: match[1] });
|
|
364
490
|
} catch (err: any) {
|
|
365
491
|
if (err?.code !== "ENOENT")
|
|
366
492
|
console.warn(`context-breakdown: SKILL.md in ${entry.name}:`, err?.message);
|
|
@@ -371,6 +497,29 @@ export function countSkillDescriptionTokens(skillsDirs: string[]): number {
|
|
|
371
497
|
console.warn(`context-breakdown: skills dir ${dir}:`, err?.message);
|
|
372
498
|
}
|
|
373
499
|
}
|
|
500
|
+
return results;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// --- Measure known SDK overhead ---
|
|
504
|
+
|
|
505
|
+
/** Count tokens in the actual composed system prompt string. */
|
|
506
|
+
export function countSystemPromptTokens(systemPrompt: string): number {
|
|
507
|
+
return countTokens(systemPrompt);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/** Count tokens in the SDK instruction file (CLAUDE.md, MINDS.md, or AGENTS.md). */
|
|
511
|
+
export function countSdkInstructionTokens(cwd: string): number {
|
|
512
|
+
const content = readSdkInstructions(cwd);
|
|
513
|
+
return content ? countTokens(content) : 0;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** Count tokens in skill description frontmatter (always in context). */
|
|
517
|
+
export function countSkillDescriptionTokens(skillsDirs: string[]): number {
|
|
518
|
+
const skills = readSkillDescriptions(skillsDirs);
|
|
519
|
+
let total = 0;
|
|
520
|
+
for (const skill of skills) {
|
|
521
|
+
total += countTokens(skill.description);
|
|
522
|
+
}
|
|
374
523
|
return total;
|
|
375
524
|
}
|
|
376
525
|
|
|
@@ -401,40 +550,46 @@ export function findClaudeSessionFile(cwd: string, sessionId: string): string |
|
|
|
401
550
|
}
|
|
402
551
|
|
|
403
552
|
/** Find the Codex JSONL file for a thread ID. */
|
|
404
|
-
export function findCodexSessionFile(threadId: string): string | null {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
for (const month of readdirSync(yearDir)) {
|
|
415
|
-
const monthDir = resolve(yearDir, month);
|
|
553
|
+
export function findCodexSessionFile(threadId: string, mindDir?: string): string | null {
|
|
554
|
+
// Check mind-local Codex sessions first, then global ~/.codex/sessions
|
|
555
|
+
const searchDirs: string[] = [];
|
|
556
|
+
if (mindDir) searchDirs.push(resolve(mindDir, ".mind/codex/sessions"));
|
|
557
|
+
searchDirs.push(resolve(process.env.HOME ?? "", ".codex/sessions"));
|
|
558
|
+
|
|
559
|
+
for (const codexDir of searchDirs) {
|
|
560
|
+
try {
|
|
561
|
+
for (const year of readdirSync(codexDir)) {
|
|
562
|
+
const yearDir = resolve(codexDir, year);
|
|
416
563
|
try {
|
|
417
|
-
if (!statSync(
|
|
564
|
+
if (!statSync(yearDir).isDirectory()) continue;
|
|
418
565
|
} catch {
|
|
419
566
|
continue;
|
|
420
567
|
}
|
|
421
|
-
for (const
|
|
422
|
-
const
|
|
568
|
+
for (const month of readdirSync(yearDir)) {
|
|
569
|
+
const monthDir = resolve(yearDir, month);
|
|
423
570
|
try {
|
|
424
|
-
if (!statSync(
|
|
571
|
+
if (!statSync(monthDir).isDirectory()) continue;
|
|
425
572
|
} catch {
|
|
426
573
|
continue;
|
|
427
574
|
}
|
|
428
|
-
for (const
|
|
429
|
-
|
|
430
|
-
|
|
575
|
+
for (const day of readdirSync(monthDir)) {
|
|
576
|
+
const dayDir = resolve(monthDir, day);
|
|
577
|
+
try {
|
|
578
|
+
if (!statSync(dayDir).isDirectory()) continue;
|
|
579
|
+
} catch {
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
for (const file of readdirSync(dayDir)) {
|
|
583
|
+
if (file.endsWith(".jsonl") && file.includes(threadId)) {
|
|
584
|
+
return resolve(dayDir, file);
|
|
585
|
+
}
|
|
431
586
|
}
|
|
432
587
|
}
|
|
433
588
|
}
|
|
434
589
|
}
|
|
590
|
+
} catch (err: any) {
|
|
591
|
+
if (err?.code !== "ENOENT") console.warn("context-breakdown: codex sessions:", err?.message);
|
|
435
592
|
}
|
|
436
|
-
} catch (err: any) {
|
|
437
|
-
if (err?.code !== "ENOENT") console.warn("context-breakdown: codex sessions:", err?.message);
|
|
438
593
|
}
|
|
439
594
|
return null;
|
|
440
595
|
}
|
|
@@ -57,6 +57,7 @@ export type EventType =
|
|
|
57
57
|
| "usage"
|
|
58
58
|
| "session_start"
|
|
59
59
|
| "done"
|
|
60
|
+
| "error"
|
|
60
61
|
| "inbound"
|
|
61
62
|
| "outbound"
|
|
62
63
|
| "context";
|
|
@@ -79,8 +80,10 @@ export async function daemonEmit(event: DaemonEvent): Promise<void> {
|
|
|
79
80
|
}
|
|
80
81
|
const url = `http://127.0.0.1:${port}/api/minds/${encodeURIComponent(mind)}/events`;
|
|
81
82
|
const body = JSON.stringify(event);
|
|
82
|
-
// Critical events
|
|
83
|
-
|
|
83
|
+
// Critical events get retries: `done` (else turns stay stuck) and `error` (else the
|
|
84
|
+
// mind never learns a failure happened). The daemon is local, so retrying against it
|
|
85
|
+
// is worthwhile even when the model provider is the thing that's failing.
|
|
86
|
+
const maxAttempts = event.type === "done" || event.type === "error" ? 3 : 1;
|
|
84
87
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
85
88
|
try {
|
|
86
89
|
const res = await fetch(url, { method: "POST", headers: headers(), body });
|
|
@@ -4,7 +4,9 @@ import type { DaemonEvent, EventType } from "./daemon-client.js";
|
|
|
4
4
|
|
|
5
5
|
export type TransparencyPreset = "transparent" | "standard" | "private" | "silent";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// `error` is an internal daemon signal (emitted directly, never channel-facing), so it
|
|
8
|
+
// isn't subject to transparency filtering — exclude it alongside the communication records.
|
|
9
|
+
type FilterableEventType = Exclude<EventType, "inbound" | "outbound" | "context" | "error">;
|
|
8
10
|
|
|
9
11
|
const PRESET_RULES: Record<
|
|
10
12
|
TransparencyPreset,
|
|
@@ -52,8 +54,8 @@ const PRESET_RULES: Record<
|
|
|
52
54
|
},
|
|
53
55
|
};
|
|
54
56
|
|
|
55
|
-
// Communication records
|
|
56
|
-
const ALWAYS_ALLOWED: ReadonlySet<string> = new Set(["inbound", "outbound", "context"]);
|
|
57
|
+
// Communication records and the internal error signal bypass transparency filtering
|
|
58
|
+
const ALWAYS_ALLOWED: ReadonlySet<string> = new Set(["inbound", "outbound", "context", "error"]);
|
|
57
59
|
|
|
58
60
|
export function loadTransparencyPreset(): TransparencyPreset {
|
|
59
61
|
for (const file of ["home/.config/config.json", "home/.config/volute.json"]) {
|