@ulpi/cli 0.1.4 → 0.1.6

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 (112) hide show
  1. package/LICENSE +21 -0
  2. package/dist/{auth-PN7TMQHV-2W4ICG64.js → auth-FWM7MM4Q-VZC3U2XZ.js} +1 -1
  3. package/dist/{auth-ECQ3IB4E.js → auth-HDK7ECJL.js} +2 -1
  4. package/dist/{chunk-3SBPZRB5.js → chunk-3BCW6ABU.js} +402 -142
  5. package/dist/{chunk-JGBXM5NC.js → chunk-3WB5CXH4.js} +180 -5
  6. package/dist/{chunk-2HEE5OKX.js → chunk-4UCJIAOU.js} +2 -2
  7. package/dist/chunk-4XTHZVDS.js +109 -0
  8. package/dist/chunk-4ZPOZULQ.js +6522 -0
  9. package/dist/{chunk-SIAQVRKG.js → chunk-5MI5GIXM.js} +48 -2
  10. package/dist/{chunk-KLEASXUR.js → chunk-6ZL6NXMV.js} +1 -1
  11. package/dist/chunk-76D3BYJD.js +221 -0
  12. package/dist/{chunk-ZLYRPD7I.js → chunk-AWOSRA5F.js} +1 -1
  13. package/dist/{chunk-PDR55ZNW.js → chunk-BFEKZZHM.js} +274 -57
  14. package/dist/chunk-C7CLUQI6.js +1286 -0
  15. package/dist/{chunk-7AL4DOEJ.js → chunk-E3B5NROU.js} +7 -7
  16. package/dist/chunk-EJ7TW77N.js +1418 -0
  17. package/dist/{chunk-5J6NLQUN.js → chunk-IV6MWETF.js} +383 -168
  18. package/dist/chunk-IZPJHSPX.js +1478 -0
  19. package/dist/chunk-JLHNLM3C.js +228 -0
  20. package/dist/{chunk-BZL5H4YQ.js → chunk-KYYI23AQ.js} +2 -2
  21. package/dist/{chunk-2CLNOKPA.js → chunk-RSFJ6QSR.js} +18 -0
  22. package/dist/chunk-S6ANCSYO.js +1271 -0
  23. package/dist/chunk-SEU7WWNQ.js +1251 -0
  24. package/dist/chunk-SNQ7NAIS.js +453 -0
  25. package/dist/{ulpi-RMMCUAGP-JCJ273T6.js → chunk-TSLDGT5O.js} +73 -35
  26. package/dist/{chunk-SPOI23SB.js → chunk-UXHCHOWQ.js} +83 -62
  27. package/dist/chunk-V2H5D6Y3.js +146 -0
  28. package/dist/{chunk-QJ5GSMEC.js → chunk-VVEDXI7E.js} +2 -1
  29. package/dist/chunk-VXH5Y4FO.js +6761 -0
  30. package/dist/chunk-WED4LM5N.js +322 -0
  31. package/dist/{chunk-74WVVWJ4.js → chunk-YOKL7RB5.js} +184 -15
  32. package/dist/chunk-Z53CAR7G.js +298 -0
  33. package/dist/ci-X3U2W4HC.js +854 -0
  34. package/dist/cloud-2F3NLVHN.js +274 -0
  35. package/dist/{codemap-RKSD4MIE.js → codemap-XNGMAF3F.js} +37 -37
  36. package/dist/codex-MB5YTMRT.js +132 -0
  37. package/dist/{config-EGAXXCGL.js → config-OOELBYTH.js} +1 -1
  38. package/dist/dist-2BJYR5EI.js +59 -0
  39. package/dist/dist-2K7IEVTA.js +43 -0
  40. package/dist/dist-3EIQTZHT.js +1380 -0
  41. package/dist/{dist-YA2BWZB2.js → dist-4U5L2X2C.js} +2 -2
  42. package/dist/{dist-UKMCJBB2.js → dist-54KAMNLO.js} +16 -15
  43. package/dist/dist-6M4MZWZW.js +58 -0
  44. package/dist/dist-6X576SU2.js +27 -0
  45. package/dist/dist-7QOEYLFX.js +103 -0
  46. package/dist/dist-AYBGHEDY.js +2541 -0
  47. package/dist/dist-EK45QNEM.js +45 -0
  48. package/dist/{dist-CS2VKNYS.js → dist-FKFEJRPX.js} +16 -15
  49. package/dist/dist-GTEJUBBT.js +66 -0
  50. package/dist/dist-HA74OKJZ.js +40 -0
  51. package/dist/dist-HU5RZAON.js +48 -0
  52. package/dist/dist-IYE3OBRB.js +374 -0
  53. package/dist/{dist-GJYT2OQV.js → dist-JLU26AB6.js} +12 -9
  54. package/dist/{dist-6G7JC2RA.js → dist-KUCI6JFE.js} +49 -9
  55. package/dist/dist-NUEMFZFL.js +33 -0
  56. package/dist/{dist-RKOGLK7R.js → dist-NUXMDXZ3.js} +31 -3
  57. package/dist/{dist-QAU3LGJN.js → dist-YCNWHSLN.js} +15 -5
  58. package/dist/{dist-CB5D5LMO.js → dist-YFFG2ZD6.js} +9 -16
  59. package/dist/dist-ZG4OKCSR.js +15 -0
  60. package/dist/doctor-SI4LLLDZ.js +345 -0
  61. package/dist/{export-import-4A5MWLIA.js → export-import-JFQH4KSJ.js} +1 -1
  62. package/dist/{history-3MOBX4MA.js → history-5NE46ZAH.js} +7 -7
  63. package/dist/hooks-installer-UN5JZLDQ.js +19 -0
  64. package/dist/index.js +395 -619
  65. package/dist/{init-6CH4HV5T.js → init-5FK3VKRT.js} +79 -13
  66. package/dist/job-HIDMAFW2.js +376 -0
  67. package/dist/jobs.memory-PLMMSFHB-VBECCTHN.js +33 -0
  68. package/dist/kiro-VMUHDFGK.js +153 -0
  69. package/dist/{launchd-LF2QMSKZ.js → launchd-6AWT54HR.js} +9 -17
  70. package/dist/mcp-PDUD7SGP.js +249 -0
  71. package/dist/mcp-installer-PQU3XOGO.js +259 -0
  72. package/dist/mcp-setup-OA7IB3H3.js +263 -0
  73. package/dist/{memory-Y6OZTXJ2.js → memory-ZNAEAK3B.js} +17 -17
  74. package/dist/{ollama-3XCUZMZT-FYKHW4TZ.js → ollama-3XCUZMZT-4JMH6B7P.js} +1 -1
  75. package/dist/{openai-E7G2YAHU-UYY4ZWON.js → openai-E7G2YAHU-T3HMBPH7.js} +2 -2
  76. package/dist/portal-JYWVHXDU.js +210 -0
  77. package/dist/prd-Q4J5NVAR.js +408 -0
  78. package/dist/repos-WWZXNN3P.js +271 -0
  79. package/dist/review-integration-5WHEJU2A.js +14 -0
  80. package/dist/{rules-E427DKYJ.js → rules-Y4VSOY5Y.js} +3 -3
  81. package/dist/run-VPNXEIBY.js +687 -0
  82. package/dist/server-COL4AXKU-P7S7NNF6.js +11 -0
  83. package/dist/server-KKSETHDV-XSSLEENT.js +20 -0
  84. package/dist/{skills-CX73O3IV.js → skills-QEYU2N27.js} +4 -2
  85. package/dist/start-JYOEL7AJ.js +303 -0
  86. package/dist/{status-4DFHDJMN.js → status-BHQYYGAL.js} +2 -2
  87. package/dist/{templates-U7T6MARD.js → templates-CBRUJ66V.js} +4 -3
  88. package/dist/tui-DP7736EX.js +61 -0
  89. package/dist/ulpi-5EN6JCAS-LFE3WSL4.js +10 -0
  90. package/dist/{uninstall-6SW35IK4.js → uninstall-ICUV6DDV.js} +3 -3
  91. package/dist/{update-M6IBJNYP.js → update-7ZMAYRBH.js} +3 -3
  92. package/dist/{version-checker-Q6YTYAGP.js → version-checker-4ZFMZA7Y.js} +2 -2
  93. package/package.json +39 -31
  94. package/dist/chunk-2MZER6ND.js +0 -415
  95. package/dist/chunk-2VYFVYJL.js +0 -4273
  96. package/dist/chunk-6OCEY7JY.js +0 -422
  97. package/dist/chunk-7LXY5UVC.js +0 -330
  98. package/dist/chunk-B55DDP24.js +0 -136
  99. package/dist/chunk-JWUUVXIV.js +0 -13694
  100. package/dist/chunk-MIAQVCFW.js +0 -39
  101. package/dist/chunk-YM2HV4IA.js +0 -505
  102. package/dist/ci-STSL2LSP.js +0 -370
  103. package/dist/mcp-installer-NQCGKQ23.js +0 -124
  104. package/dist/projects-ATHDD3D6.js +0 -271
  105. package/dist/review-ADUPV3PN.js +0 -152
  106. package/dist/server-USLHY6GH-AEOJC5ST.js +0 -18
  107. package/dist/server-X5P6WH2M-7K2RY34N.js +0 -11
  108. package/dist/skills/ulpi-generate-guardian/SKILL.md +0 -750
  109. package/dist/skills/ulpi-generate-guardian/references/framework-rules.md +0 -849
  110. package/dist/skills/ulpi-generate-guardian/references/language-rules.md +0 -591
  111. package/dist/ui-OWXZ3YSR.js +0 -167
  112. package/dist/ui.html +0 -698
@@ -0,0 +1,1251 @@
1
+ import {
2
+ SESSIONS_DIR
3
+ } from "./chunk-C7CLUQI6.js";
4
+
5
+ // ../../packages/session-engine/dist/index.js
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ import * as path2 from "path";
9
+ import * as fs2 from "fs";
10
+ import * as path3 from "path";
11
+ import { execFileSync } from "child_process";
12
+ import * as fs3 from "fs";
13
+ import * as path4 from "path";
14
+ function projectDirToSlug(dir) {
15
+ const slug = dir.replace(/^\//, "").replace(/\//g, "-").replace(/\./g, "_").replace(/-{2,}/g, "-").replace(/^-|-$/g, "").toLowerCase();
16
+ return slug || "unknown-project";
17
+ }
18
+ function validateSessionId(sessionId) {
19
+ if (!sessionId || sessionId.includes("..") || sessionId.includes("/") || sessionId.includes("\\")) {
20
+ throw new Error("Invalid session ID");
21
+ }
22
+ if (sessionId.length > 200) {
23
+ throw new Error("Session ID too long");
24
+ }
25
+ if (!/^[a-zA-Z0-9_-]+$/.test(sessionId)) {
26
+ throw new Error("Invalid session ID characters");
27
+ }
28
+ }
29
+ var JsonSessionStore = class {
30
+ baseDir;
31
+ projectSlug;
32
+ constructor(baseDir, projectDir) {
33
+ this.baseDir = baseDir ?? path.dirname(SESSIONS_DIR);
34
+ this.projectSlug = projectDir ? projectDirToSlug(projectDir) : null;
35
+ }
36
+ /**
37
+ * Load session state from disk.
38
+ * Returns `null` if the file does not exist or contains invalid JSON.
39
+ */
40
+ load(sessionId) {
41
+ validateSessionId(sessionId);
42
+ try {
43
+ const raw = fs.readFileSync(this.sessionPath(sessionId), "utf-8");
44
+ return JSON.parse(raw);
45
+ } catch {
46
+ }
47
+ if (this.projectSlug) {
48
+ try {
49
+ const flatPath = path.join(this.baseDir, "sessions", `${sessionId}.json`);
50
+ const raw = fs.readFileSync(flatPath, "utf-8");
51
+ return JSON.parse(raw);
52
+ } catch {
53
+ }
54
+ }
55
+ if (!this.projectSlug) {
56
+ try {
57
+ const sessionsDir = path.join(this.baseDir, "sessions");
58
+ const entries = fs.readdirSync(sessionsDir, { withFileTypes: true });
59
+ for (const entry of entries) {
60
+ if (!entry.isDirectory()) continue;
61
+ try {
62
+ const subPath = path.join(sessionsDir, entry.name, `${sessionId}.json`);
63
+ const raw = fs.readFileSync(subPath, "utf-8");
64
+ return JSON.parse(raw);
65
+ } catch {
66
+ }
67
+ }
68
+ } catch {
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ /**
74
+ * Persist session state atomically.
75
+ *
76
+ * The directory tree is created on demand. Data is first written to a
77
+ * temporary file adjacent to the target, then renamed so the operation
78
+ * is atomic on POSIX file-systems.
79
+ */
80
+ save(state) {
81
+ validateSessionId(state.sessionId);
82
+ const target = this.sessionPath(state.sessionId);
83
+ const dir = path.dirname(target);
84
+ fs.mkdirSync(dir, { recursive: true });
85
+ const tmp = `${target}.tmp`;
86
+ fs.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
87
+ fs.renameSync(tmp, target);
88
+ }
89
+ /**
90
+ * Delete a session's state file. Silently ignores missing files.
91
+ */
92
+ delete(sessionId) {
93
+ validateSessionId(sessionId);
94
+ try {
95
+ fs.unlinkSync(this.sessionPath(sessionId));
96
+ return;
97
+ } catch {
98
+ }
99
+ if (this.projectSlug) {
100
+ try {
101
+ fs.unlinkSync(path.join(this.baseDir, "sessions", `${sessionId}.json`));
102
+ } catch (err) {
103
+ if (err.code !== "ENOENT") throw err;
104
+ }
105
+ }
106
+ }
107
+ /**
108
+ * List all persisted session IDs by scanning the sessions directory.
109
+ */
110
+ list() {
111
+ if (this.projectSlug) {
112
+ return this.scanJsonFiles(path.join(this.baseDir, "sessions", this.projectSlug));
113
+ }
114
+ const base = path.join(this.baseDir, "sessions");
115
+ return [.../* @__PURE__ */ new Set([...this.scanJsonFiles(base), ...this.scanSubdirs(base)])];
116
+ }
117
+ /** Resolve the on-disk path for a given session ID. */
118
+ sessionPath(sessionId) {
119
+ if (this.projectSlug) {
120
+ return path.join(this.baseDir, "sessions", this.projectSlug, `${sessionId}.json`);
121
+ }
122
+ return path.join(this.baseDir, "sessions", `${sessionId}.json`);
123
+ }
124
+ /**
125
+ * List session IDs for a specific project directory.
126
+ * Returns sessions where state.projectDir matches the given path.
127
+ */
128
+ listByProject(projectDir) {
129
+ const slug = projectDirToSlug(projectDir);
130
+ const results = this.scanJsonFiles(path.join(this.baseDir, "sessions", slug));
131
+ const flatDir = path.join(this.baseDir, "sessions");
132
+ const normalizedTarget = path.resolve(projectDir);
133
+ for (const id of this.scanJsonFiles(flatDir)) {
134
+ if (results.includes(id)) continue;
135
+ try {
136
+ const raw = fs.readFileSync(path.join(flatDir, `${id}.json`), "utf-8");
137
+ const state = JSON.parse(raw);
138
+ if (path.resolve(state.projectDir) === normalizedTarget) {
139
+ results.push(id);
140
+ }
141
+ } catch {
142
+ }
143
+ }
144
+ return results;
145
+ }
146
+ /**
147
+ * Get the most recent session for a specific project directory.
148
+ * Prefers sessions with actual activity over empty sessions.
149
+ * Returns null if no sessions found for the project.
150
+ */
151
+ getLatestForProject(projectDir) {
152
+ const sessionIds = this.listByProject(projectDir);
153
+ if (sessionIds.length === 0) {
154
+ return null;
155
+ }
156
+ let latest = null;
157
+ let latestTime = -Infinity;
158
+ let latestActive = null;
159
+ let latestActiveTime = -Infinity;
160
+ for (const id of sessionIds) {
161
+ const s = this.load(id);
162
+ if (!s) continue;
163
+ const t = new Date(s.startedAt).getTime();
164
+ if (t > latestTime) {
165
+ latestTime = t;
166
+ latest = s;
167
+ }
168
+ const hasActivity = s.filesRead.length > 0 || s.filesWritten.length > 0 || s.commandsRun.length > 0 || s.rulesEnforced > 0;
169
+ if (hasActivity && t > latestActiveTime) {
170
+ latestActiveTime = t;
171
+ latestActive = s;
172
+ }
173
+ }
174
+ return latestActive ?? latest;
175
+ }
176
+ // --- Iteration tracking (v2) ---
177
+ /**
178
+ * Add an iteration to a session's state.
179
+ * Creates the iterations array if it doesn't exist.
180
+ */
181
+ addIteration(sessionId, iteration) {
182
+ const state = this.load(sessionId);
183
+ if (!state) {
184
+ throw new Error(`Session ${sessionId} not found`);
185
+ }
186
+ if (!state.iterations) {
187
+ state.iterations = [];
188
+ }
189
+ state.iterations.push(iteration);
190
+ state.activeIterationId = iteration.id;
191
+ this.save(state);
192
+ }
193
+ /**
194
+ * Update an existing iteration within a session.
195
+ * Merges the provided updates into the existing iteration.
196
+ */
197
+ updateIteration(sessionId, iterationId, updates) {
198
+ const state = this.load(sessionId);
199
+ if (!state || !state.iterations) {
200
+ throw new Error(`Session ${sessionId} not found or has no iterations`);
201
+ }
202
+ const index = state.iterations.findIndex((it) => it.id === iterationId);
203
+ if (index === -1) {
204
+ throw new Error(`Iteration ${iterationId} not found in session ${sessionId}`);
205
+ }
206
+ state.iterations[index] = { ...state.iterations[index], ...updates };
207
+ if (updates.status && updates.status !== "running" && updates.status !== "pending" && state.activeIterationId === iterationId) {
208
+ state.activeIterationId = void 0;
209
+ }
210
+ this.save(state);
211
+ }
212
+ /**
213
+ * Get all iterations for a specific task within a session.
214
+ */
215
+ getIterationsForTask(sessionId, taskId) {
216
+ const state = this.load(sessionId);
217
+ if (!state || !state.iterations) {
218
+ return [];
219
+ }
220
+ return state.iterations.filter((it) => it.taskId === taskId);
221
+ }
222
+ /**
223
+ * Get all iterations for a session.
224
+ */
225
+ getIterations(sessionId) {
226
+ const state = this.load(sessionId);
227
+ return state?.iterations ?? [];
228
+ }
229
+ scanJsonFiles(dir) {
230
+ try {
231
+ return fs.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.endsWith(".tmp")).map((f) => f.replace(/\.json$/, ""));
232
+ } catch {
233
+ return [];
234
+ }
235
+ }
236
+ scanSubdirs(base) {
237
+ try {
238
+ const ids = [];
239
+ for (const entry of fs.readdirSync(base, { withFileTypes: true })) {
240
+ if (entry.isDirectory()) {
241
+ ids.push(...this.scanJsonFiles(path.join(base, entry.name)));
242
+ }
243
+ }
244
+ return ids;
245
+ } catch {
246
+ return [];
247
+ }
248
+ }
249
+ };
250
+ function createEmptyState(sessionId, projectDir) {
251
+ validateSessionId(sessionId);
252
+ if (!projectDir || typeof projectDir !== "string") {
253
+ throw new Error("Invalid project directory");
254
+ }
255
+ return {
256
+ sessionId,
257
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
258
+ projectDir,
259
+ branch: void 0,
260
+ filesRead: [],
261
+ filesWritten: [],
262
+ filesDeleted: [],
263
+ commandsRun: [],
264
+ testsRun: false,
265
+ testsPassed: null,
266
+ lintRun: false,
267
+ lintPassed: null,
268
+ buildRun: false,
269
+ buildPassed: null,
270
+ formatterRun: false,
271
+ branchCreated: false,
272
+ planApproved: false,
273
+ rulesEnforced: 0,
274
+ actionsBlocked: 0,
275
+ autoActionsRun: 0,
276
+ consecutiveBlocks: 0,
277
+ lastNotifications: {},
278
+ stack: void 0,
279
+ phase: void 0,
280
+ transcriptPath: void 0,
281
+ untrackedFilesAtStart: void 0,
282
+ workingTreeDirtyAtStart: void 0
283
+ };
284
+ }
285
+ function createInitialState(sessionId, projectDir) {
286
+ return createEmptyState(sessionId, projectDir);
287
+ }
288
+ function generateIterationId() {
289
+ return `iter_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
290
+ }
291
+ function startIteration(taskId, agentType, agentModel) {
292
+ return {
293
+ id: generateIterationId(),
294
+ taskId,
295
+ agentType,
296
+ agentModel,
297
+ status: "running",
298
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
299
+ };
300
+ }
301
+ function completeIteration(iteration, result) {
302
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
303
+ const duration = iteration.startedAt ? new Date(completedAt).getTime() - new Date(iteration.startedAt).getTime() : void 0;
304
+ return {
305
+ ...iteration,
306
+ status: result.status ?? (result.error ? "failed" : "completed"),
307
+ completedAt,
308
+ duration,
309
+ exitCode: result.exitCode,
310
+ tokenUsage: result.tokenUsage,
311
+ filesChanged: result.filesChanged,
312
+ commitSha: result.commitSha,
313
+ outputSummary: result.outputSummary,
314
+ error: result.error
315
+ };
316
+ }
317
+ function updateStateFromInput(state, input, config) {
318
+ const next = {
319
+ ...state,
320
+ filesRead: [...state.filesRead],
321
+ filesWritten: [...state.filesWritten],
322
+ filesDeleted: [...state.filesDeleted],
323
+ commandsRun: [...state.commandsRun],
324
+ lastNotifications: { ...state.lastNotifications }
325
+ };
326
+ const toolName = input.tool_name;
327
+ const toolInput = input.tool_input;
328
+ const toolResponse = input.tool_response;
329
+ if (toolName === "Read" && toolInput?.file_path) {
330
+ const norm = normalizePath(toolInput.file_path, next.projectDir);
331
+ if (!next.filesRead.includes(norm)) {
332
+ next.filesRead.push(norm);
333
+ }
334
+ }
335
+ if ((toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") && toolInput?.file_path) {
336
+ const norm = normalizePath(toolInput.file_path, next.projectDir);
337
+ if (!next.filesWritten.includes(norm)) {
338
+ next.filesWritten.push(norm);
339
+ }
340
+ }
341
+ if (toolName === "Bash" && toolInput?.command) {
342
+ const cmd = toolInput.command;
343
+ next.commandsRun.push(cmd);
344
+ trackDeletedFiles(cmd, next);
345
+ if (isBranchCreationCommand(cmd)) {
346
+ next.branchCreated = true;
347
+ next.branch = extractBranchName(cmd) ?? next.branch;
348
+ }
349
+ const passed = didCommandPass(toolResponse);
350
+ if (isTestCommand(cmd, config)) {
351
+ next.testsRun = true;
352
+ if (passed !== null) {
353
+ next.testsPassed = passed;
354
+ }
355
+ }
356
+ if (isLintCommand(cmd, config)) {
357
+ next.lintRun = true;
358
+ if (passed !== null) {
359
+ next.lintPassed = passed;
360
+ }
361
+ }
362
+ if (isBuildCommand(cmd, config)) {
363
+ next.buildRun = true;
364
+ if (passed !== null) {
365
+ next.buildPassed = passed;
366
+ }
367
+ }
368
+ if (isFormatCommand(cmd, config)) {
369
+ next.formatterRun = true;
370
+ }
371
+ }
372
+ return next;
373
+ }
374
+ var TEST_PATTERNS = [
375
+ /\bjest\b/,
376
+ /\bvitest\b/,
377
+ /\bpytest\b/,
378
+ /\bgo\s+test\b/,
379
+ /\bcargo\s+test\b/,
380
+ /\bphpunit\b/,
381
+ /\bpest\b/,
382
+ /\brspec\b/,
383
+ /\bmocha\b/,
384
+ /\bava\b/,
385
+ /\bnpm\s+run\s+test\b/,
386
+ /\bpnpm\s+(run\s+)?test\b/,
387
+ /\byarn\s+(run\s+)?test\b/,
388
+ /\bbun\s+test\b/,
389
+ /\bmake\s+test\b/
390
+ ];
391
+ var LINT_PATTERNS = [
392
+ /\beslint\b/,
393
+ /\btsc\s+--noEmit\b/,
394
+ /\btsc\b.*--noEmit\b/,
395
+ /\bruff\s+check\b/,
396
+ /\bruff\s+lint\b/,
397
+ /\bcargo\s+clippy\b/,
398
+ /\bphpstan\b/,
399
+ /\bpylint\b/,
400
+ /\bflake8\b/,
401
+ /\bmypy\b/,
402
+ /\bgolangci-lint\b/,
403
+ /\brubocop\b/,
404
+ /\bnpm\s+run\s+lint\b/,
405
+ /\bpnpm\s+(run\s+)?lint\b/,
406
+ /\byarn\s+(run\s+)?lint\b/
407
+ ];
408
+ var BUILD_PATTERNS = [
409
+ /\bnpm\s+run\s+build\b/,
410
+ /\bpnpm\s+(run\s+)?build\b/,
411
+ /\byarn\s+(run\s+)?build\b/,
412
+ /\bcargo\s+build\b/,
413
+ /\bgo\s+build\b/,
414
+ /\bmake\s+build\b/,
415
+ /\bmake\b(?!.*test|.*lint|.*format)/,
416
+ /\btsc\b(?!.*--noEmit)/,
417
+ /\bvite\s+build\b/,
418
+ /\bnext\s+build\b/
419
+ ];
420
+ var FORMAT_PATTERNS = [
421
+ /\bprettier\b/,
422
+ /\bblack\b/,
423
+ /\bruff\s+format\b/,
424
+ /\bgofmt\b/,
425
+ /\bgoimports\b/,
426
+ /\brustfmt\b/,
427
+ /\bphp-cs-fixer\b/,
428
+ /\bpint\b/,
429
+ /\bnpm\s+run\s+format\b/,
430
+ /\bpnpm\s+(run\s+)?format\b/,
431
+ /\byarn\s+(run\s+)?format\b/
432
+ ];
433
+ function matchesPatterns(command, patterns, configCommand) {
434
+ if (configCommand && command.includes(configCommand)) {
435
+ return true;
436
+ }
437
+ return patterns.some((p) => p.test(command));
438
+ }
439
+ function isTestCommand(command, config) {
440
+ return matchesPatterns(command, TEST_PATTERNS, config?.test_command);
441
+ }
442
+ function isLintCommand(command, config) {
443
+ return matchesPatterns(command, LINT_PATTERNS, config?.lint_command);
444
+ }
445
+ function isBuildCommand(command, config) {
446
+ return matchesPatterns(command, BUILD_PATTERNS, config?.build_command);
447
+ }
448
+ function isFormatCommand(command, config) {
449
+ return matchesPatterns(command, FORMAT_PATTERNS, config?.format_command);
450
+ }
451
+ var BRANCH_CREATION_RE = /\bgit\s+(?:checkout\s+-b|switch\s+-c|branch)\s+/;
452
+ function isBranchCreationCommand(command) {
453
+ return BRANCH_CREATION_RE.test(command);
454
+ }
455
+ function extractBranchName(command) {
456
+ const cbMatch = command.match(
457
+ /\bgit\s+(?:checkout\s+-b|switch\s+-c)\s+(\S+)/
458
+ );
459
+ if (cbMatch) return cbMatch[1];
460
+ const brMatch = command.match(/\bgit\s+branch\s+(?!-\w)(\S+)/);
461
+ if (brMatch) return brMatch[1];
462
+ return void 0;
463
+ }
464
+ function trackDeletedFiles(command, state) {
465
+ const rmMatch = command.match(/\brm\s+(.*)/);
466
+ if (!rmMatch) return;
467
+ const args = rmMatch[1].split(/\s+/).filter((a) => {
468
+ return a.length > 0 && !a.startsWith("-");
469
+ });
470
+ for (const arg of args) {
471
+ const norm = normalizePath(arg, state.projectDir);
472
+ if (!state.filesDeleted.includes(norm)) {
473
+ state.filesDeleted.push(norm);
474
+ }
475
+ }
476
+ }
477
+ function normalizePath(filePath, projectDir) {
478
+ let normalized = filePath;
479
+ if (path2.isAbsolute(normalized)) {
480
+ const prefix = projectDir.endsWith(path2.sep) ? projectDir : projectDir + path2.sep;
481
+ if (normalized.startsWith(prefix)) {
482
+ normalized = normalized.slice(prefix.length);
483
+ }
484
+ }
485
+ normalized = normalized.replace(/^\.\//, "");
486
+ return normalized;
487
+ }
488
+ function didCommandPass(response) {
489
+ if (!response) return null;
490
+ if (typeof response.exit_code === "number") {
491
+ return response.exit_code === 0;
492
+ }
493
+ if (typeof response.success === "boolean") {
494
+ return response.success;
495
+ }
496
+ return null;
497
+ }
498
+ var MAX_EVENTS_PER_SESSION = 1e4;
499
+ function eventLogPath(sessionId, baseDir, projectDir) {
500
+ if (projectDir) {
501
+ const slug = projectDirToSlug(projectDir);
502
+ return path3.join(baseDir, "sessions", slug, `${sessionId}.events.jsonl`);
503
+ }
504
+ return path3.join(baseDir, "sessions", `${sessionId}.events.jsonl`);
505
+ }
506
+ function countFilePath(logPath) {
507
+ return logPath + ".count";
508
+ }
509
+ function readEventCount(logPath) {
510
+ const countPath = countFilePath(logPath);
511
+ try {
512
+ const raw = fs2.readFileSync(countPath, "utf-8").trim();
513
+ const count2 = parseInt(raw, 10);
514
+ if (!Number.isNaN(count2) && count2 >= 0) {
515
+ return count2;
516
+ }
517
+ } catch {
518
+ }
519
+ let count = 0;
520
+ try {
521
+ const existing = fs2.readFileSync(logPath, "utf-8");
522
+ count = existing.split("\n").filter((l) => l.trim().length > 0).length;
523
+ } catch {
524
+ }
525
+ try {
526
+ fs2.writeFileSync(countPath, String(count), "utf-8");
527
+ } catch {
528
+ }
529
+ return count;
530
+ }
531
+ function writeEventCount(logPath, count) {
532
+ try {
533
+ fs2.writeFileSync(countFilePath(logPath), String(count), "utf-8");
534
+ } catch {
535
+ }
536
+ }
537
+ function appendEvent(sessionId, event, projectDir, baseDir) {
538
+ validateSessionId(sessionId);
539
+ const dir = baseDir ?? path3.dirname(SESSIONS_DIR);
540
+ const logPath = eventLogPath(sessionId, dir, projectDir);
541
+ const currentCount = readEventCount(logPath);
542
+ if (currentCount >= MAX_EVENTS_PER_SESSION) {
543
+ console.error(
544
+ `[ulpi] Session ${sessionId}: event log at capacity (${MAX_EVENTS_PER_SESSION}), dropping new events`
545
+ );
546
+ return;
547
+ }
548
+ fs2.mkdirSync(path3.dirname(logPath), { recursive: true });
549
+ fs2.appendFileSync(logPath, JSON.stringify(event) + "\n", "utf-8");
550
+ writeEventCount(logPath, currentCount + 1);
551
+ }
552
+ function readEvents(sessionId, projectDir, baseDir) {
553
+ validateSessionId(sessionId);
554
+ const dir = baseDir ?? path3.dirname(SESSIONS_DIR);
555
+ const logPath = eventLogPath(sessionId, dir, projectDir);
556
+ let raw;
557
+ try {
558
+ raw = fs2.readFileSync(logPath, "utf-8");
559
+ } catch {
560
+ if (projectDir) {
561
+ const flatPath = eventLogPath(sessionId, dir);
562
+ try {
563
+ raw = fs2.readFileSync(flatPath, "utf-8");
564
+ } catch {
565
+ return [];
566
+ }
567
+ } else {
568
+ return [];
569
+ }
570
+ }
571
+ const events = [];
572
+ for (const line of raw.split("\n")) {
573
+ const trimmed = line.trim();
574
+ if (trimmed.length === 0) continue;
575
+ try {
576
+ events.push(JSON.parse(trimmed));
577
+ } catch {
578
+ }
579
+ }
580
+ return events;
581
+ }
582
+ function nextPhase(current, hookEvent, context) {
583
+ switch (current) {
584
+ case "idle":
585
+ if (hookEvent === "SessionStart") {
586
+ return { phase: "active" };
587
+ }
588
+ break;
589
+ case "active":
590
+ if (hookEvent === "PostToolUse" && context?.hasNewCommit) {
591
+ return { phase: "active_committed", sideEffect: "capture" };
592
+ }
593
+ if (hookEvent === "SessionEnd") {
594
+ return { phase: "ended" };
595
+ }
596
+ break;
597
+ case "active_committed":
598
+ if (hookEvent === "PostToolUse" && context?.hasNewCommit) {
599
+ return { phase: "active_committed", sideEffect: "capture" };
600
+ }
601
+ if (hookEvent === "SessionEnd") {
602
+ return { phase: "ended" };
603
+ }
604
+ break;
605
+ case "ended":
606
+ break;
607
+ }
608
+ return { phase: current };
609
+ }
610
+ function detectNewCommit(projectDir, lastKnownHead) {
611
+ try {
612
+ const currentHead = execFileSync("git", ["rev-parse", "HEAD"], {
613
+ cwd: projectDir,
614
+ encoding: "utf-8",
615
+ timeout: 3e3
616
+ }).toString().trim();
617
+ if (currentHead && currentHead !== lastKnownHead) {
618
+ return { hasNewCommit: true, newHead: currentHead };
619
+ }
620
+ return { hasNewCommit: false, newHead: null };
621
+ } catch {
622
+ return { hasNewCommit: false, newHead: null };
623
+ }
624
+ }
625
+ var SessionEventEmitter = class {
626
+ listeners = /* @__PURE__ */ new Map();
627
+ maxListeners = 50;
628
+ /**
629
+ * Subscribe to all events or a specific event type.
630
+ *
631
+ * @param eventType - Event type to listen for, or "*" for all events
632
+ * @param listener - Callback function
633
+ * @returns Unsubscribe function
634
+ */
635
+ on(eventType, listener) {
636
+ const key = eventType;
637
+ let set = this.listeners.get(key);
638
+ if (!set) {
639
+ set = /* @__PURE__ */ new Set();
640
+ this.listeners.set(key, set);
641
+ }
642
+ if (set.size >= this.maxListeners) {
643
+ console.error(
644
+ `[ulpi] Warning: SessionEventEmitter has ${set.size} listeners for "${key}". Possible memory leak.`
645
+ );
646
+ }
647
+ set.add(listener);
648
+ return () => {
649
+ set?.delete(listener);
650
+ if (set?.size === 0) {
651
+ this.listeners.delete(key);
652
+ }
653
+ };
654
+ }
655
+ /**
656
+ * Subscribe to an event type, but only fire once.
657
+ */
658
+ once(eventType, listener) {
659
+ const unsubscribe = this.on(eventType, (event) => {
660
+ unsubscribe();
661
+ listener(event);
662
+ });
663
+ return unsubscribe;
664
+ }
665
+ /**
666
+ * Emit an event to all matching listeners.
667
+ * Each listener is wrapped in try/catch so one failure doesn't affect others.
668
+ *
669
+ * @param event - The session event to emit
670
+ */
671
+ emit(event) {
672
+ const specificListeners = this.listeners.get(event.event);
673
+ if (specificListeners) {
674
+ for (const listener of specificListeners) {
675
+ try {
676
+ listener(event);
677
+ } catch (err) {
678
+ console.error(
679
+ `[ulpi] SessionEventEmitter listener error for "${event.event}":`,
680
+ err instanceof Error ? err.message : String(err)
681
+ );
682
+ }
683
+ }
684
+ }
685
+ const wildcardListeners = this.listeners.get("*");
686
+ if (wildcardListeners) {
687
+ for (const listener of wildcardListeners) {
688
+ try {
689
+ listener(event);
690
+ } catch (err) {
691
+ console.error(
692
+ `[ulpi] SessionEventEmitter wildcard listener error:`,
693
+ err instanceof Error ? err.message : String(err)
694
+ );
695
+ }
696
+ }
697
+ }
698
+ }
699
+ /**
700
+ * Remove all listeners for a specific event type, or all listeners.
701
+ */
702
+ removeAllListeners(eventType) {
703
+ if (eventType) {
704
+ this.listeners.delete(eventType);
705
+ } else {
706
+ this.listeners.clear();
707
+ }
708
+ }
709
+ /**
710
+ * Get the number of listeners for a specific event type.
711
+ */
712
+ listenerCount(eventType) {
713
+ return this.listeners.get(eventType)?.size ?? 0;
714
+ }
715
+ /**
716
+ * Set the maximum number of listeners per event type.
717
+ */
718
+ setMaxListeners(n) {
719
+ this.maxListeners = n;
720
+ }
721
+ };
722
+ var MAX_LIVE_OUTPUT = 25e4;
723
+ var MAX_HISTORY_OUTPUT = 1e5;
724
+ var TRUNCATED_PREFIX = "[...output truncated";
725
+ function appendWithCharLimit(current, chunk, maxChars) {
726
+ const combined = current + chunk;
727
+ if (combined.length <= maxChars) {
728
+ return combined;
729
+ }
730
+ const omitted = combined.length - maxChars;
731
+ const prefix = `${TRUNCATED_PREFIX} (${omitted} chars omitted)...]
732
+ `;
733
+ const tail = combined.slice(combined.length - maxChars + prefix.length);
734
+ return prefix + tail;
735
+ }
736
+ var OutputBuffer = class {
737
+ liveBuffer = "";
738
+ historyBuffer = "";
739
+ liveLimit;
740
+ historyLimit;
741
+ totalCharsReceived = 0;
742
+ constructor(liveLimit = MAX_LIVE_OUTPUT, historyLimit = MAX_HISTORY_OUTPUT) {
743
+ this.liveLimit = liveLimit;
744
+ this.historyLimit = historyLimit;
745
+ }
746
+ /**
747
+ * Append text to both buffers.
748
+ */
749
+ append(text) {
750
+ this.totalCharsReceived += text.length;
751
+ this.liveBuffer = appendWithCharLimit(this.liveBuffer, text, this.liveLimit);
752
+ this.historyBuffer = appendWithCharLimit(this.historyBuffer, text, this.historyLimit);
753
+ }
754
+ /**
755
+ * Get the live output buffer (larger, for display).
756
+ */
757
+ getLiveOutput() {
758
+ return this.liveBuffer;
759
+ }
760
+ /**
761
+ * Get the history output buffer (smaller, for storage).
762
+ */
763
+ getHistoryOutput() {
764
+ return this.historyBuffer;
765
+ }
766
+ /**
767
+ * Get total characters received (before truncation).
768
+ */
769
+ getTotalReceived() {
770
+ return this.totalCharsReceived;
771
+ }
772
+ /**
773
+ * Check if any output has been truncated.
774
+ */
775
+ isTruncated() {
776
+ return this.totalCharsReceived > this.liveLimit;
777
+ }
778
+ /**
779
+ * Clear both buffers.
780
+ */
781
+ clear() {
782
+ this.liveBuffer = "";
783
+ this.historyBuffer = "";
784
+ this.totalCharsReceived = 0;
785
+ }
786
+ };
787
+ function toMemorySafeOutput(output, maxChars = MAX_HISTORY_OUTPUT) {
788
+ if (output.length <= maxChars) {
789
+ return output;
790
+ }
791
+ return appendWithCharLimit("", output, maxChars);
792
+ }
793
+ var SESSION_STATE_FILE = "session-state.json";
794
+ var MAX_SESSION_AGE_MS = 24 * 60 * 60 * 1e3;
795
+ function saveSessionSnapshot(snapshotDir, state) {
796
+ const snapshot = {
797
+ state,
798
+ pid: process.pid,
799
+ snapshotAt: (/* @__PURE__ */ new Date()).toISOString(),
800
+ gracefulEnd: false
801
+ };
802
+ const filePath = path4.join(snapshotDir, SESSION_STATE_FILE);
803
+ fs3.mkdirSync(snapshotDir, { recursive: true });
804
+ const tmp = `${filePath}.tmp`;
805
+ fs3.writeFileSync(tmp, JSON.stringify(snapshot, null, 2), "utf-8");
806
+ fs3.renameSync(tmp, filePath);
807
+ }
808
+ function loadSessionSnapshot(snapshotDir) {
809
+ const filePath = path4.join(snapshotDir, SESSION_STATE_FILE);
810
+ try {
811
+ const raw = fs3.readFileSync(filePath, "utf-8");
812
+ return JSON.parse(raw);
813
+ } catch {
814
+ return null;
815
+ }
816
+ }
817
+ function removeSessionSnapshot(snapshotDir) {
818
+ const filePath = path4.join(snapshotDir, SESSION_STATE_FILE);
819
+ try {
820
+ fs3.unlinkSync(filePath);
821
+ } catch {
822
+ }
823
+ }
824
+ function markSessionGracefulEnd(snapshotDir) {
825
+ const snapshot = loadSessionSnapshot(snapshotDir);
826
+ if (!snapshot) return;
827
+ snapshot.gracefulEnd = true;
828
+ snapshot.snapshotAt = (/* @__PURE__ */ new Date()).toISOString();
829
+ const filePath = path4.join(snapshotDir, SESSION_STATE_FILE);
830
+ const tmp = `${filePath}.tmp`;
831
+ fs3.writeFileSync(tmp, JSON.stringify(snapshot, null, 2), "utf-8");
832
+ fs3.renameSync(tmp, filePath);
833
+ }
834
+ function detectOrphanedSessions(baseDir) {
835
+ const dir = baseDir ?? path4.dirname(SESSIONS_DIR);
836
+ const sessionsDir = path4.join(dir, "sessions");
837
+ const orphans = [];
838
+ let projectDirs;
839
+ try {
840
+ projectDirs = fs3.readdirSync(sessionsDir, { withFileTypes: true });
841
+ } catch {
842
+ return orphans;
843
+ }
844
+ for (const entry of projectDirs) {
845
+ if (!entry.isDirectory()) continue;
846
+ const projectSlug = entry.name;
847
+ const projectSessionsDir = path4.join(sessionsDir, projectSlug);
848
+ let files;
849
+ try {
850
+ files = fs3.readdirSync(projectSessionsDir);
851
+ } catch {
852
+ continue;
853
+ }
854
+ for (const file of files) {
855
+ if (!file.endsWith(".json") || file.endsWith(".tmp") || file.endsWith(".count")) continue;
856
+ if (file === SESSION_STATE_FILE) continue;
857
+ const sessionId = file.replace(/\.json$/, "");
858
+ try {
859
+ validateSessionId(sessionId);
860
+ } catch {
861
+ continue;
862
+ }
863
+ const raw = fs3.readFileSync(path4.join(projectSessionsDir, file), "utf-8");
864
+ let state;
865
+ try {
866
+ state = JSON.parse(raw);
867
+ } catch {
868
+ continue;
869
+ }
870
+ if (state.phase === "ended") continue;
871
+ const startedAt = new Date(state.startedAt).getTime();
872
+ const now = Date.now();
873
+ if (now - startedAt > MAX_SESSION_AGE_MS) {
874
+ orphans.push({
875
+ sessionId,
876
+ projectSlug,
877
+ snapshotAt: state.startedAt,
878
+ pid: 0,
879
+ // Unknown PID for sessions without snapshots
880
+ reason: "too_old"
881
+ });
882
+ }
883
+ }
884
+ }
885
+ return orphans;
886
+ }
887
+ function cleanupOrphanedSession(sessionId, baseDir, projectDir) {
888
+ validateSessionId(sessionId);
889
+ const dir = baseDir ?? path4.dirname(SESSIONS_DIR);
890
+ const slug = projectDir ? projectDirToSlug(projectDir) : null;
891
+ const paths = [];
892
+ if (slug) {
893
+ paths.push(path4.join(dir, "sessions", slug, `${sessionId}.json`));
894
+ }
895
+ paths.push(path4.join(dir, "sessions", `${sessionId}.json`));
896
+ for (const filePath of paths) {
897
+ try {
898
+ const raw = fs3.readFileSync(filePath, "utf-8");
899
+ const state = JSON.parse(raw);
900
+ state.phase = "ended";
901
+ const tmp = `${filePath}.tmp`;
902
+ fs3.writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
903
+ fs3.renameSync(tmp, filePath);
904
+ return;
905
+ } catch {
906
+ continue;
907
+ }
908
+ }
909
+ }
910
+ var idCounter = 0;
911
+ function generateSubagentId() {
912
+ idCounter++;
913
+ return `subagent_${Date.now()}_${idCounter}_${Math.random().toString(36).slice(2, 7)}`;
914
+ }
915
+ var SubagentTraceParser = class {
916
+ subagents = /* @__PURE__ */ new Map();
917
+ activeStack = [];
918
+ onEvent;
919
+ trackHierarchy;
920
+ maxResultLength;
921
+ toolUseIdToSubagentId = /* @__PURE__ */ new Map();
922
+ constructor(options = {}) {
923
+ this.onEvent = options.onEvent;
924
+ this.trackHierarchy = options.trackHierarchy ?? true;
925
+ this.maxResultLength = options.maxResultLength ?? 2e3;
926
+ }
927
+ /**
928
+ * Process a single JSONL line. Parses as JSON and detects subagent events.
929
+ *
930
+ * @param line - Raw JSONL line string
931
+ * @returns Array of detected events (empty if line is not subagent-related)
932
+ */
933
+ processLine(line) {
934
+ const trimmed = line.trim();
935
+ if (!trimmed) return [];
936
+ let message;
937
+ try {
938
+ const parsed = JSON.parse(trimmed);
939
+ message = {
940
+ type: parsed.type,
941
+ tool: parsed.tool,
942
+ raw: parsed
943
+ };
944
+ } catch {
945
+ return [];
946
+ }
947
+ return this.processMessage(message);
948
+ }
949
+ /**
950
+ * Process a parsed JSONL message.
951
+ */
952
+ processMessage(message) {
953
+ const events = [];
954
+ if (this.isTaskToolInvocation(message)) {
955
+ const event = this.handleSpawn(message);
956
+ if (event) events.push(event);
957
+ }
958
+ if (this.isToolResult(message)) {
959
+ const event = this.handleResult(message);
960
+ if (event) events.push(event);
961
+ }
962
+ if (this.isErrorMessage(message)) {
963
+ const event = this.handleError(message);
964
+ if (event) events.push(event);
965
+ }
966
+ return events;
967
+ }
968
+ /**
969
+ * Get all currently active (running) subagents.
970
+ */
971
+ getActiveSubagents() {
972
+ return Array.from(this.subagents.values()).filter(
973
+ (s) => s.status === "running"
974
+ );
975
+ }
976
+ /**
977
+ * Get the full subagent tree rooted at top-level nodes.
978
+ * Returns top-level nodes only (children are accessible via the Map).
979
+ */
980
+ getTree() {
981
+ return Array.from(this.subagents.values()).filter(
982
+ (s) => s.parentId === void 0
983
+ );
984
+ }
985
+ /**
986
+ * Get all tracked subagents as a flat list.
987
+ */
988
+ getAllSubagents() {
989
+ return Array.from(this.subagents.values());
990
+ }
991
+ /**
992
+ * Get a specific subagent by ID.
993
+ */
994
+ getSubagent(id) {
995
+ return this.subagents.get(id);
996
+ }
997
+ /**
998
+ * Get children of a specific subagent.
999
+ */
1000
+ getChildren(parentId) {
1001
+ const parent = this.subagents.get(parentId);
1002
+ if (!parent) return [];
1003
+ return parent.children.map((id) => this.subagents.get(id)).filter((n) => n !== void 0);
1004
+ }
1005
+ /**
1006
+ * Get the current nesting depth.
1007
+ */
1008
+ getCurrentDepth() {
1009
+ return this.activeStack.length;
1010
+ }
1011
+ /**
1012
+ * Get a summary of subagent activity.
1013
+ */
1014
+ getSummary() {
1015
+ const nodes = Array.from(this.subagents.values());
1016
+ const byAgentType = {};
1017
+ let totalDurationMs = 0;
1018
+ let maxDepth = 0;
1019
+ for (const node of nodes) {
1020
+ const type = node.agentType ?? "unknown";
1021
+ byAgentType[type] = (byAgentType[type] ?? 0) + 1;
1022
+ if (node.durationMs !== void 0) {
1023
+ totalDurationMs += node.durationMs;
1024
+ }
1025
+ if (node.depth > maxDepth) {
1026
+ maxDepth = node.depth;
1027
+ }
1028
+ }
1029
+ return {
1030
+ totalSpawned: nodes.length,
1031
+ completed: nodes.filter((n) => n.status === "completed").length,
1032
+ errored: nodes.filter((n) => n.status === "error").length,
1033
+ running: nodes.filter((n) => n.status === "running").length,
1034
+ maxDepth,
1035
+ totalDurationMs,
1036
+ byAgentType
1037
+ };
1038
+ }
1039
+ /**
1040
+ * Reset the parser state.
1041
+ */
1042
+ reset() {
1043
+ this.subagents.clear();
1044
+ this.activeStack = [];
1045
+ this.toolUseIdToSubagentId.clear();
1046
+ }
1047
+ // ─── Private Methods ───
1048
+ isTaskToolInvocation(message) {
1049
+ if (message.tool?.name?.toLowerCase() === "task") {
1050
+ return true;
1051
+ }
1052
+ const raw = message.raw;
1053
+ if (raw.type === "assistant" && Array.isArray(raw.content)) {
1054
+ for (const block of raw.content) {
1055
+ if (typeof block === "object" && block !== null && "type" in block && block.type === "tool_use" && "name" in block && typeof block.name === "string" && block.name.toLowerCase() === "task") {
1056
+ return true;
1057
+ }
1058
+ }
1059
+ }
1060
+ return false;
1061
+ }
1062
+ isToolResult(message) {
1063
+ return message.raw.type === "tool_result" || message.type === "result";
1064
+ }
1065
+ isErrorMessage(message) {
1066
+ return message.raw.type === "error" || message.type === "error" || typeof message.raw.error === "object" && message.raw.error !== null;
1067
+ }
1068
+ handleSpawn(message) {
1069
+ const raw = message.raw;
1070
+ let toolInput;
1071
+ let toolUseId;
1072
+ if (message.tool?.input) {
1073
+ toolInput = message.tool.input;
1074
+ } else if (raw.type === "assistant" && Array.isArray(raw.content)) {
1075
+ for (const block of raw.content) {
1076
+ if (typeof block === "object" && block !== null && "type" in block && block.type === "tool_use" && "name" in block && block.name.toLowerCase() === "task") {
1077
+ toolInput = block.input;
1078
+ toolUseId = block.id;
1079
+ break;
1080
+ }
1081
+ }
1082
+ }
1083
+ if (!toolInput) return null;
1084
+ const agentType = toolInput.subagent_type || "unknown";
1085
+ const description = toolInput.description || "";
1086
+ const id = generateSubagentId();
1087
+ const parentId = this.trackHierarchy && this.activeStack.length > 0 ? this.activeStack[this.activeStack.length - 1] : void 0;
1088
+ const node = {
1089
+ id,
1090
+ parentId,
1091
+ toolName: "Task",
1092
+ status: "running",
1093
+ children: [],
1094
+ startTime: (/* @__PURE__ */ new Date()).toISOString(),
1095
+ description,
1096
+ agentType,
1097
+ depth: this.activeStack.length
1098
+ };
1099
+ if (parentId) {
1100
+ const parent = this.subagents.get(parentId);
1101
+ if (parent) {
1102
+ parent.children.push(id);
1103
+ }
1104
+ }
1105
+ this.subagents.set(id, node);
1106
+ this.activeStack.push(id);
1107
+ if (toolUseId) {
1108
+ this.toolUseIdToSubagentId.set(toolUseId, id);
1109
+ }
1110
+ const event = {
1111
+ type: "spawn",
1112
+ subagentId: id,
1113
+ timestamp: node.startTime,
1114
+ agentType,
1115
+ description,
1116
+ parentId
1117
+ };
1118
+ this.emitEvent(event);
1119
+ return event;
1120
+ }
1121
+ handleResult(message) {
1122
+ const raw = message.raw;
1123
+ let subagentId;
1124
+ const toolUseId = raw.tool_use_id;
1125
+ if (toolUseId) {
1126
+ subagentId = this.toolUseIdToSubagentId.get(toolUseId);
1127
+ }
1128
+ if (!subagentId && this.activeStack.length > 0) {
1129
+ subagentId = this.activeStack[this.activeStack.length - 1];
1130
+ }
1131
+ if (!subagentId) return null;
1132
+ const node = this.subagents.get(subagentId);
1133
+ if (!node || node.status !== "running") return null;
1134
+ const isError = raw.is_error === true || typeof raw.content === "string" && raw.content.toLowerCase().includes("error");
1135
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1136
+ const durationMs = new Date(now).getTime() - new Date(node.startTime).getTime();
1137
+ node.status = isError ? "error" : "completed";
1138
+ node.endTime = now;
1139
+ node.durationMs = durationMs;
1140
+ let resultContent;
1141
+ if (typeof raw.content === "string") {
1142
+ resultContent = raw.content;
1143
+ } else if (Array.isArray(raw.content)) {
1144
+ resultContent = raw.content.map((block) => {
1145
+ if (typeof block === "string") return block;
1146
+ if (typeof block === "object" && block !== null && "text" in block) {
1147
+ return block.text;
1148
+ }
1149
+ return "";
1150
+ }).join("\n");
1151
+ }
1152
+ if (resultContent && resultContent.length > this.maxResultLength) {
1153
+ resultContent = resultContent.slice(0, this.maxResultLength) + "...";
1154
+ }
1155
+ if (isError) {
1156
+ node.error = resultContent;
1157
+ } else {
1158
+ node.result = resultContent;
1159
+ }
1160
+ const stackIndex = this.activeStack.indexOf(subagentId);
1161
+ if (stackIndex !== -1) {
1162
+ this.activeStack.splice(stackIndex, 1);
1163
+ }
1164
+ if (toolUseId) {
1165
+ this.toolUseIdToSubagentId.delete(toolUseId);
1166
+ }
1167
+ const event = {
1168
+ type: isError ? "error" : "complete",
1169
+ subagentId,
1170
+ timestamp: now,
1171
+ agentType: node.agentType,
1172
+ description: node.description,
1173
+ parentId: node.parentId,
1174
+ durationMs,
1175
+ result: isError ? void 0 : resultContent,
1176
+ error: isError ? resultContent : void 0
1177
+ };
1178
+ this.emitEvent(event);
1179
+ return event;
1180
+ }
1181
+ handleError(message) {
1182
+ if (this.activeStack.length === 0) return null;
1183
+ const subagentId = this.activeStack[this.activeStack.length - 1];
1184
+ const node = this.subagents.get(subagentId);
1185
+ if (!node || node.status !== "running") return null;
1186
+ const raw = message.raw;
1187
+ let errorMessage = "Unknown error";
1188
+ if (typeof raw.error === "object" && raw.error !== null) {
1189
+ const errorObj = raw.error;
1190
+ errorMessage = errorObj.message || errorMessage;
1191
+ } else if (typeof raw.message === "string") {
1192
+ errorMessage = raw.message;
1193
+ }
1194
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1195
+ const durationMs = new Date(now).getTime() - new Date(node.startTime).getTime();
1196
+ node.status = "error";
1197
+ node.endTime = now;
1198
+ node.durationMs = durationMs;
1199
+ node.error = errorMessage;
1200
+ this.activeStack.pop();
1201
+ const event = {
1202
+ type: "error",
1203
+ subagentId,
1204
+ timestamp: now,
1205
+ agentType: node.agentType,
1206
+ description: node.description,
1207
+ parentId: node.parentId,
1208
+ error: errorMessage,
1209
+ durationMs
1210
+ };
1211
+ this.emitEvent(event);
1212
+ return event;
1213
+ }
1214
+ emitEvent(event) {
1215
+ if (this.onEvent) {
1216
+ try {
1217
+ this.onEvent(event);
1218
+ } catch {
1219
+ }
1220
+ }
1221
+ }
1222
+ };
1223
+
1224
+ export {
1225
+ projectDirToSlug,
1226
+ validateSessionId,
1227
+ JsonSessionStore,
1228
+ createEmptyState,
1229
+ createInitialState,
1230
+ generateIterationId,
1231
+ startIteration,
1232
+ completeIteration,
1233
+ updateStateFromInput,
1234
+ appendEvent,
1235
+ readEvents,
1236
+ nextPhase,
1237
+ detectNewCommit,
1238
+ SessionEventEmitter,
1239
+ MAX_LIVE_OUTPUT,
1240
+ MAX_HISTORY_OUTPUT,
1241
+ appendWithCharLimit,
1242
+ OutputBuffer,
1243
+ toMemorySafeOutput,
1244
+ saveSessionSnapshot,
1245
+ loadSessionSnapshot,
1246
+ removeSessionSnapshot,
1247
+ markSessionGracefulEnd,
1248
+ detectOrphanedSessions,
1249
+ cleanupOrphanedSession,
1250
+ SubagentTraceParser
1251
+ };