volute 0.31.0 → 0.32.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.
Files changed (178) hide show
  1. package/README.md +15 -22
  2. package/dist/{accept-GAKQ3MEH.js → accept-74M7I4RZ.js} +3 -2
  3. package/dist/{activity-events-T5ZRCVAL.js → activity-events-HETAODOK.js} +3 -2
  4. package/dist/{ai-service-UWUPM4T6.js → ai-service-ZIPCV3MX.js} +18 -5
  5. package/dist/api.d.ts +98 -281
  6. package/dist/{archive-YBNSJYZZ.js → archive-INXYFVCW.js} +3 -2
  7. package/dist/{auth-T5AW2USD.js → auth-6DMGES3I.js} +4 -3
  8. package/dist/{bridge-4AJ3EY26.js → bridge-BVCBTGPF.js} +3 -2
  9. package/dist/{chat-7YLT7FI3.js → chat-XT4OBJBU.js} +8 -8
  10. package/dist/{chunk-BNC43CSY.js → chunk-2FLJ63GU.js} +2 -2
  11. package/dist/{chunk-NV3TYNWX.js → chunk-2NGTS5UU.js} +1 -1
  12. package/dist/{chunk-LX6T3GKQ.js → chunk-ALEF47VT.js} +1 -1
  13. package/dist/{chunk-S2TZLSDH.js → chunk-D5G5YOPL.js} +163 -15
  14. package/dist/{chunk-VGWJSNHS.js → chunk-G53F3JA4.js} +1 -35
  15. package/dist/{chunk-A6TUJJ3L.js → chunk-G6BSYHPK.js} +2 -2
  16. package/dist/{chunk-BC3P3QCK.js → chunk-I5KY25PQ.js} +1 -9
  17. package/dist/{chunk-PNQCXLSV.js → chunk-IYDIE3HG.js} +58 -1
  18. package/dist/{chunk-HDKY4TWU.js → chunk-JJ7W6WSB.js} +3 -3
  19. package/dist/{chunk-57OKQMP3.js → chunk-LGB6JBHI.js} +1 -1
  20. package/dist/chunk-LRCG2JLP.js +251 -0
  21. package/dist/{chunk-SNVPRRT7.js → chunk-LSGWR54X.js} +2 -2
  22. package/dist/{chunk-EMPFLFTG.js → chunk-M7UL5S3Q.js} +1 -1
  23. package/dist/chunk-PB65JZK2.js +85 -0
  24. package/dist/chunk-PVY5W6QN.js +41 -0
  25. package/dist/{chunk-BWKIHH7B.js → chunk-QBQ424EM.js} +318 -418
  26. package/dist/{chunk-EKDWA7E4.js → chunk-QZANELPX.js} +4 -2
  27. package/dist/{chunk-AAO77TZX.js → chunk-R7E6CRVQ.js} +1 -1
  28. package/dist/{chunk-X62AXPR7.js → chunk-RPZZSXV3.js} +8 -196
  29. package/dist/{chunk-WRS3B556.js → chunk-RSX4OPZY.js} +5 -5
  30. package/dist/{chunk-FAHDKPEH.js → chunk-S6NFERDC.js} +5 -3
  31. package/dist/chunk-SKLSMHXO.js +208 -0
  32. package/dist/{chunk-DAXJKPHZ.js → chunk-SX5TKJBZ.js} +2 -2
  33. package/dist/{chunk-R5QJBZZG.js → chunk-TDRYEPH4.js} +20 -10
  34. package/dist/{chunk-6QIUN46C.js → chunk-TSXLLQZW.js} +11 -3
  35. package/dist/{chunk-4OUOFS23.js → chunk-UKVWJRKN.js} +1 -1
  36. package/dist/{chunk-NOWVQ7AL.js → chunk-WKF5FEFK.js} +318 -167
  37. package/dist/cli.js +38 -20
  38. package/dist/{clock-LJCG426D.js → clock-2UOZ6JPU.js} +5 -4
  39. package/dist/{cloud-sync-O3LXIRN6.js → cloud-sync-JN3NWKEM.js} +16 -14
  40. package/dist/config-H2H4UIF7.js +72 -0
  41. package/dist/connectors/discord-bridge.js +1 -1
  42. package/dist/connectors/slack-bridge.js +1 -1
  43. package/dist/connectors/telegram-bridge.js +1 -1
  44. package/dist/{conversations-RKKGP5IA.js → conversations-3O5O6AS3.js} +4 -3
  45. package/dist/{create-WUTIIRI2.js → create-RNLNCORE.js} +3 -2
  46. package/dist/{create-TL623TFC.js → create-WBBYI6V7.js} +6 -2
  47. package/dist/{daemon-client-CVGM25DM.js → daemon-client-6QXHZ7US.js} +3 -2
  48. package/dist/{daemon-restart-EZP7XH3V.js → daemon-restart-NGFHFAUF.js} +7 -6
  49. package/dist/daemon.js +907 -612
  50. package/dist/{db-SW5PL6QA.js → db-F34YLV7D.js} +2 -1
  51. package/dist/db-RA45JBFG.js +16 -0
  52. package/dist/{delete-Z6HAG35F.js → delete-QTGWEDBI.js} +1 -1
  53. package/dist/delivery-manager-SDVXFD4W.js +28 -0
  54. package/dist/delivery-router-FL45JL7N.js +21 -0
  55. package/dist/down-TB3ESMNP.js +14 -0
  56. package/dist/{env-NHESNNSP.js → env-RLYQBOOP.js} +3 -2
  57. package/dist/{export-EVMP7GWY.js → export-SUYRLI5Q.js} +4 -3
  58. package/dist/{extension-LR7EW3JF.js → extension-FQ5D3NCC.js} +4 -3
  59. package/dist/{extensions-NGEJI7JH.js → extensions-GDYWQXC4.js} +9 -7
  60. package/dist/{files-3SM7V33S.js → files-EAMPO2SJ.js} +4 -3
  61. package/dist/{history-PQD3LXEP.js → history-FO5PHBQ5.js} +7 -2
  62. package/dist/{import-PR2OCGQJ.js → import-DDUFE7AY.js} +4 -3
  63. package/dist/{join-R4EN5CWQ.js → join-I5QEE3LG.js} +1 -1
  64. package/dist/{list-B4XNUOFO.js → list-DW2VRTOZ.js} +3 -2
  65. package/dist/{login-62JVY6A2.js → login-7CHPW2PN.js} +3 -2
  66. package/dist/{login-URWP6S2N.js → login-RIJF2F4G.js} +3 -2
  67. package/dist/{logout-NXJQJDLI.js → logout-5MLHZALK.js} +3 -2
  68. package/dist/{logout-ZK2N62T3.js → logout-UZJRGY4Z.js} +3 -2
  69. package/dist/message-delivery-2FIM7QKO.js +32 -0
  70. package/dist/{mind-E2ZV2WRX.js → mind-2B6M7Y25.js} +18 -18
  71. package/dist/{mind-activity-tracker-ASNZBMLC.js → mind-activity-tracker-NZZT2NTT.js} +4 -3
  72. package/dist/{mind-list-BEI7E5WY.js → mind-list-WUPMQDYQ.js} +3 -2
  73. package/dist/mind-manager-BNCMGYXW.js +28 -0
  74. package/dist/mind-service-AV273WT4.js +34 -0
  75. package/dist/{mind-sleep-CANABWJI.js → mind-sleep-B7BHJLH7.js} +3 -2
  76. package/dist/{mind-status-6WKZVUOP.js → mind-status-L3EFFRPR.js} +3 -2
  77. package/dist/{mind-wake-RZKLH2IN.js → mind-wake-GY3RFX7Y.js} +3 -2
  78. package/dist/{package-NU4CA7OU.js → package-PK6JUFL3.js} +1 -1
  79. package/dist/{read-THL362EI.js → read-5AMJRO3D.js} +3 -2
  80. package/dist/{register-QAQELAS6.js → register-V2JZZKFK.js} +3 -2
  81. package/dist/{registry-ASXCQCNH.js → registry-PJ4S5PHQ.js} +8 -1
  82. package/dist/{reject-AYPBNPNL.js → reject-33HEZMZ4.js} +3 -2
  83. package/dist/{restart-6SKPV3T2.js → restart-3UCMRUVC.js} +3 -2
  84. package/dist/{sandbox-6ZEWQDVU.js → sandbox-JANNTX6U.js} +4 -3
  85. package/dist/schema-PA3M5ZKH.js +32 -0
  86. package/dist/{seed-OWX2AW75.js → seed-ALUQ55FF.js} +26 -9
  87. package/dist/{send-ZO4BTWXK.js → send-3MI36LEF.js} +56 -67
  88. package/dist/{setup-7CFITEQN.js → setup-SZIARWI6.js} +5 -2
  89. package/dist/{setup-ZXBXG7E4.js → setup-WENLVPVP.js} +8 -6
  90. package/dist/{skill-YFXP67A2.js → skill-TUVOTW4Z.js} +3 -2
  91. package/dist/skills/dreaming/SKILL.md +6 -4
  92. package/dist/skills/dreaming/references/INSTALL.md +2 -2
  93. package/dist/skills/dreaming/scripts/dream.ts +2 -2
  94. package/dist/skills/dreaming/scripts/wake-context-dreams.sh +1 -1
  95. package/dist/skills/imagegen/SKILL.md +6 -5
  96. package/dist/skills/imagegen/references/INSTALL.md +1 -1
  97. package/dist/skills/resonance/SKILL.md +4 -1
  98. package/dist/skills/resonance/references/INSTALL.md +2 -2
  99. package/dist/skills/resonance/scripts/resonance-hook.sh +2 -0
  100. package/dist/skills/resonance/scripts/resonance.ts +35 -5
  101. package/dist/skills/volute-admin/SKILL.md +83 -0
  102. package/dist/skills/volute-mind/SKILL.md +11 -11
  103. package/dist/skills-XNZK6P4K.js +61 -0
  104. package/dist/sleep-manager-53DZOWW7.js +32 -0
  105. package/dist/spirit-N4W4UQRH.js +217 -0
  106. package/dist/{split-MI62KJUU.js → split-STOROBYJ.js} +1 -1
  107. package/dist/{sprout-FDVI2CGN.js → sprout-L2GFOVF7.js} +9 -7
  108. package/dist/{start-D64BRKPH.js → start-K2NCUUCG.js} +3 -2
  109. package/dist/{status-ZZWBYFGE.js → status-TCUMUO6M.js} +5 -4
  110. package/dist/{stop-OP2CTXCO.js → stop-H26JZDXF.js} +3 -2
  111. package/dist/system-chat-NPYFYZVI.js +32 -0
  112. package/dist/{systems-EQPPT4B7.js → systems-DHBKVYEY.js} +6 -5
  113. package/dist/{tailscale-6DJKUMNF.js → tailscale-XHQBZROW.js} +2 -1
  114. package/dist/{template-hash-3HOR4UAJ.js → template-hash-A6VVKOXJ.js} +2 -1
  115. package/dist/up-6I6BHRTO.js +17 -0
  116. package/dist/{update-KUJXATRS.js → update-QVPRF6GR.js} +5 -4
  117. package/dist/{update-check-5WVSU37T.js → update-check-ZD6OOIYQ.js} +3 -2
  118. package/dist/{upgrade-KBHCWX6T.js → upgrade-O4Q7WJM3.js} +12 -14
  119. package/dist/{version-notify-75ELVKPV.js → version-notify-TCKWBZZG.js} +21 -18
  120. package/dist/web-assets/assets/index-Bui7U9Uu.css +1 -0
  121. package/dist/web-assets/assets/index-e36DIo1b.js +73 -0
  122. package/dist/web-assets/ext-theme.css +93 -0
  123. package/dist/web-assets/index.html +2 -2
  124. package/drizzle/0004_spirits.sql +5 -0
  125. package/drizzle/meta/0004_snapshot.json +7 -0
  126. package/drizzle/meta/_journal.json +7 -0
  127. package/package.json +1 -1
  128. package/packages/extensions/notes/dist/ui/assets/index-8jWEv9SA.js +61 -0
  129. package/packages/extensions/notes/dist/ui/assets/index-DkaB7Ytd.css +1 -0
  130. package/packages/extensions/notes/dist/ui/index.html +2 -2
  131. package/packages/extensions/pages/skills/pages/SKILL.md +16 -46
  132. package/templates/_base/.init/.config/hooks/pre-prompt/session-activity.ts +40 -0
  133. package/templates/_base/.init/{.config → .local}/bin/volute +1 -1
  134. package/templates/_base/.init/.local/hooks/pre-prompt/session-activity.ts +40 -0
  135. package/templates/_base/.init/.local/hooks/startup-context.ts +58 -0
  136. package/templates/_base/home/.config/routes.json +1 -1
  137. package/templates/_base/src/lib/daemon-client.ts +21 -13
  138. package/templates/_base/src/lib/format-prefix.ts +1 -0
  139. package/templates/_base/src/lib/hook-loader.ts +155 -0
  140. package/templates/_base/src/lib/startup.ts +11 -4
  141. package/templates/_base/src/lib/transparency.ts +2 -2
  142. package/templates/claude/.init/.claude/settings.json +1 -1
  143. package/templates/claude/.init/.config/routes.json +2 -2
  144. package/templates/claude/src/agent.ts +95 -13
  145. package/templates/claude/src/lib/message-channel.ts +7 -2
  146. package/templates/codex/.init/.config/routes.json +11 -0
  147. package/templates/codex/.init/AGENTS.md +29 -0
  148. package/templates/codex/home/.config/config.json.tmpl +7 -0
  149. package/templates/codex/package.json.tmpl +20 -0
  150. package/templates/codex/src/agent.ts +553 -0
  151. package/templates/codex/src/lib/content.ts +16 -0
  152. package/templates/codex/src/lib/session-store.ts +56 -0
  153. package/templates/codex/src/server.ts +59 -0
  154. package/templates/codex/volute-template.json +8 -0
  155. package/templates/pi/.init/.config/routes.json +2 -2
  156. package/templates/pi/src/agent.ts +62 -8
  157. package/templates/pi/src/lib/event-handler.ts +1 -1
  158. package/templates/pi/src/lib/reply-instructions-extension.ts +32 -11
  159. package/dist/chunk-HR5JKIDG.js +0 -222
  160. package/dist/down-TS4XQBA4.js +0 -13
  161. package/dist/message-delivery-UJHCLVU4.js +0 -30
  162. package/dist/mind-manager-IPA6DZXD.js +0 -26
  163. package/dist/pages-watcher-72OVPRMH.js +0 -22
  164. package/dist/skills/sessions/SKILL.md +0 -49
  165. package/dist/sleep-manager-TPS6OGCA.js +0 -30
  166. package/dist/system-chat-B43GIXQU.js +0 -30
  167. package/dist/up-TDXEP3VA.js +0 -16
  168. package/dist/web-assets/assets/index-BM1cTzBg.js +0 -72
  169. package/dist/web-assets/assets/index-BfJkKTPF.css +0 -1
  170. package/packages/extensions/notes/dist/ui/assets/index-B8GdTnXs.css +0 -1
  171. package/packages/extensions/notes/dist/ui/assets/index-CDpGTCWb.js +0 -2
  172. package/packages/extensions/pages/skills/pages/scripts/pages.mjs +0 -58
  173. package/templates/_base/.init/.config/hooks/startup-context.sh +0 -46
  174. package/templates/_base/.init/.config/scripts/session-reader.ts +0 -59
  175. package/templates/_base/src/lib/session-monitor.ts +0 -400
  176. package/templates/claude/src/lib/hooks/session-context.ts +0 -32
  177. package/templates/pi/src/lib/session-context-extension.ts +0 -35
  178. /package/templates/_base/.init/{.config → .local}/hooks/wake-context.sh +0 -0
@@ -1,400 +0,0 @@
1
- import {
2
- closeSync,
3
- existsSync,
4
- mkdirSync,
5
- openSync,
6
- readdirSync,
7
- readFileSync,
8
- readSync,
9
- statSync,
10
- writeFileSync,
11
- } from "node:fs";
12
- import { dirname, resolve } from "node:path";
13
-
14
- // --- Types ---
15
-
16
- type CursorState = Record<string, Record<string, { offset: number }>>;
17
-
18
- type ParsedEntry = {
19
- role: "user" | "assistant";
20
- timestamp?: string;
21
- text?: string;
22
- toolUses?: { name: string; primaryArg?: string }[];
23
- };
24
-
25
- type SessionSummary = {
26
- firstUserText: string;
27
- toolCounts: { edits: number; reads: number; commands: number; other: number };
28
- messageCount: number;
29
- timeSpan: { first?: string; last?: string };
30
- lastAssistantText?: string;
31
- };
32
-
33
- type Format = "agent-sdk" | "pi";
34
-
35
- // --- Public API ---
36
-
37
- export function getSessionUpdates(options: {
38
- currentSession: string;
39
- sessionsDir: string;
40
- cursorFile: string;
41
- jsonlResolver: (sessionName: string) => string | null;
42
- format: Format;
43
- }): string | null {
44
- const sessionNames = listSessionNames(options.sessionsDir, options.format);
45
- const others = sessionNames.filter((n) => n !== options.currentSession && !n.startsWith("new-"));
46
- if (others.length === 0) return null;
47
-
48
- const cursors = loadCursors(options.cursorFile);
49
- const currentCursors = cursors[options.currentSession] ?? {};
50
- const summaries: string[] = [];
51
-
52
- for (const name of others) {
53
- try {
54
- const jsonlPath = options.jsonlResolver(name);
55
- if (!jsonlPath || !existsSync(jsonlPath)) continue;
56
-
57
- const stat = statSync(jsonlPath);
58
- const prevOffset = currentCursors[name]?.offset ?? 0;
59
- const fileSize = stat.size;
60
-
61
- // Reset if offset past EOF (file was truncated/recreated)
62
- const offset = prevOffset > fileSize ? 0 : prevOffset;
63
- if (offset >= fileSize) {
64
- currentCursors[name] = { offset: fileSize };
65
- continue;
66
- }
67
-
68
- const newBytes = readBytesFrom(jsonlPath, offset, fileSize - offset);
69
- const lines = newBytes.split("\n").filter((l) => l.trim());
70
- const entries = parseJsonlEntries(lines, options.format);
71
- const summary = summarizeEntries(entries);
72
-
73
- currentCursors[name] = { offset: fileSize };
74
-
75
- if (!summary) continue;
76
-
77
- const ago = summary.timeSpan.last ? formatTimeAgo(summary.timeSpan.last) : "recently";
78
- const parts = [`- ${name} (${ago}, ${summary.messageCount} messages)`];
79
-
80
- if (summary.firstUserText) {
81
- parts[0] += `: "${truncate(summary.firstUserText, 100)}"`;
82
- }
83
-
84
- const actions: string[] = [];
85
- if (summary.toolCounts.edits > 0) actions.push(`edited ${summary.toolCounts.edits} files`);
86
- if (summary.toolCounts.commands > 0)
87
- actions.push(`ran ${summary.toolCounts.commands} commands`);
88
- if (summary.toolCounts.reads > 0) actions.push(`read ${summary.toolCounts.reads} files`);
89
- if (summary.toolCounts.other > 0) actions.push(`${summary.toolCounts.other} other tool uses`);
90
- if (actions.length > 0) {
91
- parts[0] += ` -> ${actions.join(", ")}`;
92
- }
93
-
94
- summaries.push(parts[0]);
95
- } catch {}
96
- }
97
-
98
- cursors[options.currentSession] = currentCursors;
99
- try {
100
- saveCursors(options.cursorFile, cursors);
101
- } catch {
102
- // Non-fatal: worst case is duplicate summaries on next check
103
- }
104
-
105
- if (summaries.length === 0) return null;
106
-
107
- // Cap total output at ~500 chars
108
- let output = "[Session Activity]\n" + summaries.join("\n");
109
- if (output.length > 500) {
110
- output = output.slice(0, 497) + "...";
111
- }
112
- return output;
113
- }
114
-
115
- export function readSessionLog(options: {
116
- jsonlPath: string;
117
- format: Format;
118
- lines?: number;
119
- }): string {
120
- const maxLines = options.lines ?? 50;
121
- if (!existsSync(options.jsonlPath)) return "No session log found.";
122
-
123
- const content = readFileSync(options.jsonlPath, "utf-8");
124
- const allLines = content.split("\n").filter((l) => l.trim());
125
- const lines = allLines.slice(-maxLines);
126
- const entries = parseJsonlEntries(lines, options.format);
127
-
128
- const output: string[] = [];
129
- for (const entry of entries) {
130
- const ts = entry.timestamp ? `[${formatTimestamp(entry.timestamp)}]` : "";
131
- if (entry.role === "user" && entry.text) {
132
- output.push(`${ts} User: ${entry.text}`);
133
- } else if (entry.role === "assistant") {
134
- if (entry.text) {
135
- output.push(`${ts} Assistant: ${entry.text}`);
136
- }
137
- if (entry.toolUses) {
138
- for (const tool of entry.toolUses) {
139
- const arg = tool.primaryArg ? ` ${tool.primaryArg}` : "";
140
- output.push(`${ts} [${tool.name}${arg}]`);
141
- }
142
- }
143
- }
144
- }
145
-
146
- return output.length > 0 ? output.join("\n") : "No activity found.";
147
- }
148
-
149
- // --- JSONL Path Resolvers ---
150
-
151
- export function resolveAgentSdkJsonl(
152
- sessionsDir: string,
153
- sessionName: string,
154
- cwd: string,
155
- ): string | null {
156
- const sessionFile = resolve(sessionsDir, `${sessionName}.json`);
157
- if (!existsSync(sessionFile)) return null;
158
-
159
- try {
160
- const data = JSON.parse(readFileSync(sessionFile, "utf-8"));
161
- const sessionId = data.sessionId;
162
- if (!sessionId) return null;
163
-
164
- const encoded = encodeCwd(cwd);
165
- const home = process.env.HOME || process.env.USERPROFILE || "";
166
- return resolve(home, ".claude", "projects", encoded, `${sessionId}.jsonl`);
167
- } catch {
168
- return null;
169
- }
170
- }
171
-
172
- export function encodeCwd(cwd: string): string {
173
- return cwd.replace(/\//g, "-").replace(/\./g, "-");
174
- }
175
-
176
- export function resolvePiJsonl(sessionsDir: string, sessionName: string): string | null {
177
- const sessionDir = resolve(sessionsDir, sessionName);
178
- if (!existsSync(sessionDir)) return null;
179
-
180
- try {
181
- const files = readdirSync(sessionDir)
182
- .filter((f) => f.endsWith(".jsonl"))
183
- .map((f) => ({
184
- name: f,
185
- mtime: statSync(resolve(sessionDir, f)).mtimeMs,
186
- }))
187
- .sort((a, b) => b.mtime - a.mtime);
188
-
189
- if (files.length === 0) return null;
190
- return resolve(sessionDir, files[0].name);
191
- } catch {
192
- return null;
193
- }
194
- }
195
-
196
- // --- Parsing ---
197
-
198
- export function parseJsonlEntries(lines: string[], format: Format): ParsedEntry[] {
199
- const entries: ParsedEntry[] = [];
200
-
201
- for (const line of lines) {
202
- let parsed: any;
203
- try {
204
- parsed = JSON.parse(line);
205
- } catch {
206
- continue;
207
- }
208
-
209
- if (format === "agent-sdk") {
210
- if (parsed.type === "user" && parsed.message?.role === "user") {
211
- const text = extractTextFromContent(parsed.message.content);
212
- if (text) entries.push({ role: "user", timestamp: parsed.timestamp, text });
213
- } else if (parsed.type === "assistant" && parsed.message?.role === "assistant") {
214
- const text = extractTextFromContent(parsed.message.content);
215
- const toolUses = extractToolUses(parsed.message.content, format);
216
- if (text || toolUses.length > 0) {
217
- entries.push({
218
- role: "assistant",
219
- timestamp: parsed.timestamp,
220
- text: text || undefined,
221
- toolUses,
222
- });
223
- }
224
- }
225
- } else {
226
- // pi format
227
- if (parsed.type === "message" && parsed.message?.role === "user") {
228
- const text = extractTextFromContent(parsed.message.content);
229
- if (text) entries.push({ role: "user", timestamp: parsed.timestamp, text });
230
- } else if (parsed.type === "message" && parsed.message?.role === "assistant") {
231
- const text = extractTextFromContent(parsed.message.content);
232
- const toolUses = extractToolUses(parsed.message.content, format);
233
- if (text || toolUses.length > 0) {
234
- entries.push({
235
- role: "assistant",
236
- timestamp: parsed.timestamp,
237
- text: text || undefined,
238
- toolUses,
239
- });
240
- }
241
- }
242
- }
243
- }
244
-
245
- return entries;
246
- }
247
-
248
- export function summarizeEntries(entries: ParsedEntry[]): SessionSummary | null {
249
- if (entries.length === 0) return null;
250
-
251
- let firstUserText = "";
252
- let lastAssistantText: string | undefined;
253
- const toolCounts = { edits: 0, reads: 0, commands: 0, other: 0 };
254
- let messageCount = 0;
255
- const timestamps: string[] = [];
256
-
257
- for (const entry of entries) {
258
- messageCount++;
259
- if (entry.timestamp) timestamps.push(entry.timestamp);
260
-
261
- if (entry.role === "user" && entry.text && !firstUserText) {
262
- firstUserText = entry.text;
263
- }
264
-
265
- if (entry.role === "assistant") {
266
- if (entry.text) lastAssistantText = entry.text;
267
- if (entry.toolUses) {
268
- for (const tool of entry.toolUses) {
269
- const cat = categorizeTool(tool.name);
270
- toolCounts[cat]++;
271
- }
272
- }
273
- }
274
- }
275
-
276
- return {
277
- firstUserText,
278
- toolCounts,
279
- messageCount,
280
- timeSpan: {
281
- first: timestamps[0],
282
- last: timestamps[timestamps.length - 1],
283
- },
284
- lastAssistantText,
285
- };
286
- }
287
-
288
- // --- Helpers ---
289
-
290
- function extractTextFromContent(content: any[]): string | null {
291
- if (!Array.isArray(content)) return null;
292
- const texts: string[] = [];
293
- for (const part of content) {
294
- if (part.type === "text" && part.text) {
295
- texts.push(part.text);
296
- }
297
- }
298
- return texts.length > 0 ? texts.join("\n") : null;
299
- }
300
-
301
- function extractToolUses(content: any[], format: Format): { name: string; primaryArg?: string }[] {
302
- if (!Array.isArray(content)) return [];
303
- const tools: { name: string; primaryArg?: string }[] = [];
304
-
305
- for (const part of content) {
306
- const isToolUse = format === "agent-sdk" ? part.type === "tool_use" : part.type === "toolCall";
307
-
308
- if (isToolUse) {
309
- const name = part.name || "unknown";
310
- const input = format === "agent-sdk" ? part.input : part.arguments;
311
- const primaryArg = extractPrimaryArg(name, input);
312
- tools.push({ name, primaryArg });
313
- }
314
- }
315
-
316
- return tools;
317
- }
318
-
319
- function extractPrimaryArg(_name: string, input: any): string | undefined {
320
- if (!input || typeof input !== "object") return undefined;
321
- // Common patterns for primary argument
322
- return (
323
- input.file_path || input.path || input.command || input.pattern || input.query || input.url
324
- );
325
- }
326
-
327
- function categorizeTool(name: string): "edits" | "reads" | "commands" | "other" {
328
- const lowerName = name.toLowerCase();
329
- if (["edit", "write", "notebookedit"].includes(lowerName)) return "edits";
330
- if (["read", "glob", "grep", "ls"].includes(lowerName)) return "reads";
331
- if (["bash", "exec", "execute_shell_command"].includes(lowerName)) return "commands";
332
- return "other";
333
- }
334
-
335
- function listSessionNames(sessionsDir: string, format: Format): string[] {
336
- if (!existsSync(sessionsDir)) return [];
337
- try {
338
- const entries = readdirSync(sessionsDir);
339
- if (format === "agent-sdk") {
340
- return entries.filter((e) => e.endsWith(".json")).map((e) => e.replace(/\.json$/, ""));
341
- }
342
- // pi: subdirectories
343
- return entries.filter((e) => {
344
- try {
345
- return statSync(resolve(sessionsDir, e)).isDirectory();
346
- } catch {
347
- return false;
348
- }
349
- });
350
- } catch {
351
- return [];
352
- }
353
- }
354
-
355
- function loadCursors(cursorFile: string): CursorState {
356
- try {
357
- return JSON.parse(readFileSync(cursorFile, "utf-8"));
358
- } catch {
359
- return {};
360
- }
361
- }
362
-
363
- function saveCursors(cursorFile: string, cursors: CursorState): void {
364
- mkdirSync(dirname(cursorFile), { recursive: true });
365
- writeFileSync(cursorFile, JSON.stringify(cursors, null, 2));
366
- }
367
-
368
- function readBytesFrom(filePath: string, offset: number, length: number): string {
369
- const buf = Buffer.alloc(length);
370
- const fd = openSync(filePath, "r");
371
- try {
372
- readSync(fd, buf, 0, length, offset);
373
- } finally {
374
- closeSync(fd);
375
- }
376
- return buf.toString("utf-8");
377
- }
378
-
379
- function truncate(s: string, max: number): string {
380
- if (s.length <= max) return s;
381
- return s.slice(0, max - 3) + "...";
382
- }
383
-
384
- function formatTimeAgo(timestamp: string): string {
385
- const diff = Date.now() - new Date(timestamp).getTime();
386
- if (isNaN(diff) || diff < 0) return "just now";
387
- const minutes = Math.floor(diff / 60000);
388
- if (minutes < 1) return "just now";
389
- if (minutes < 60) return `${minutes}m ago`;
390
- const hours = Math.floor(minutes / 60);
391
- if (hours < 24) return `${hours}h ago`;
392
- const days = Math.floor(hours / 24);
393
- return `${days}d ago`;
394
- }
395
-
396
- function formatTimestamp(timestamp: string): string {
397
- const d = new Date(timestamp);
398
- if (isNaN(d.getTime())) return timestamp;
399
- return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
400
- }
@@ -1,32 +0,0 @@
1
- import { resolve } from "node:path";
2
- import type { HookCallback } from "@anthropic-ai/claude-agent-sdk";
3
- import { getSessionUpdates, resolveAgentSdkJsonl } from "../session-monitor.js";
4
-
5
- export function createSessionContextHook(options: {
6
- currentSession: string;
7
- sessionsDir: string;
8
- cwd: string;
9
- }) {
10
- const hook: HookCallback = async () => {
11
- try {
12
- const summary = getSessionUpdates({
13
- currentSession: options.currentSession,
14
- sessionsDir: options.sessionsDir,
15
- cursorFile: resolve(options.sessionsDir, "../session-cursors.json"),
16
- jsonlResolver: (name) => resolveAgentSdkJsonl(options.sessionsDir, name, options.cwd),
17
- format: "agent-sdk",
18
- });
19
- if (!summary) return {};
20
- return {
21
- hookSpecificOutput: {
22
- hookEventName: "UserPromptSubmit" as const,
23
- additionalContext: summary,
24
- },
25
- };
26
- } catch {
27
- return {};
28
- }
29
- };
30
-
31
- return { hook };
32
- }
@@ -1,35 +0,0 @@
1
- import { resolve } from "node:path";
2
- import type { ExtensionFactory } from "@mariozechner/pi-coding-agent";
3
- import { log } from "./logger.js";
4
- import { getSessionUpdates, resolvePiJsonl } from "./session-monitor.js";
5
-
6
- export function createSessionContextExtension(options: {
7
- currentSession: string;
8
- mindDir: string;
9
- }): ExtensionFactory {
10
- return (pi) => {
11
- pi.on("before_agent_start", () => {
12
- try {
13
- const sessionsDir = resolve(options.mindDir, ".mind/pi-sessions");
14
- const summary = getSessionUpdates({
15
- currentSession: options.currentSession,
16
- sessionsDir,
17
- cursorFile: resolve(options.mindDir, ".mind/session-cursors.json"),
18
- jsonlResolver: (name) => resolvePiJsonl(sessionsDir, name),
19
- format: "pi",
20
- });
21
- if (!summary) return {};
22
- return {
23
- message: {
24
- customType: "session-update",
25
- content: summary,
26
- display: true,
27
- },
28
- };
29
- } catch (err) {
30
- log("mind", "session context extension failed:", err);
31
- return {};
32
- }
33
- });
34
- };
35
- }