agent-flow-app 0.6.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/app.js ADDED
@@ -0,0 +1,2072 @@
1
+ #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+
28
+ // scripts/setup.js
29
+ var require_setup = __commonJS({
30
+ "scripts/setup.js"(exports2, module2) {
31
+ "use strict";
32
+ var fs5 = require("fs");
33
+ var path4 = require("path");
34
+ var os2 = require("os");
35
+ var { execFileSync } = require("child_process");
36
+ var DISCOVERY_DIR2 = path4.join(os2.homedir(), ".claude", "agent-flow");
37
+ var HOOK_SCRIPT_PATH = path4.join(DISCOVERY_DIR2, "hook.js");
38
+ var SETTINGS_PATH = path4.join(os2.homedir(), ".claude", "settings.json");
39
+ var HOOK_TIMEOUT_S = 2;
40
+ var HOOK_SAFETY_MARGIN_MS = 500;
41
+ var HOOK_FORWARD_TIMEOUT_MS = 1e3;
42
+ var HOOK_COMMAND_MARKER = "agent-flow/hook.js";
43
+ function resolveNodePath() {
44
+ try {
45
+ const cmd = process.platform === "win32" ? "where" : "command";
46
+ const args2 = process.platform === "win32" ? ["node"] : ["-v", "node"];
47
+ const result = execFileSync(cmd, args2, { encoding: "utf8", timeout: 3e3 }).trim();
48
+ const firstLine = result.split(/\r?\n/)[0].trim();
49
+ if (firstLine)
50
+ return firstLine;
51
+ } catch {
52
+ }
53
+ return "node";
54
+ }
55
+ function getHookScriptContent() {
56
+ return `#!/usr/bin/env node
57
+ // Agent Flow hook forwarder v3 \u2014 installed by the Agent Flow setup script.
58
+ // Claude Code invokes this as a command hook. It reads a discovery directory to
59
+ // find live extension instances, checks their PIDs, and forwards the event via
60
+ // HTTP POST. Dead instances are cleaned up automatically.
61
+ 'use strict';
62
+ const fs = require('fs');
63
+ const path = require('path');
64
+ const http = require('http');
65
+ const os = require('os');
66
+
67
+ setTimeout(() => process.exit(0), ${HOOK_TIMEOUT_S * 1e3 - HOOK_SAFETY_MARGIN_MS});
68
+
69
+ const DIR = path.join(os.homedir(), '.claude', 'agent-flow');
70
+ const IS_WIN = process.platform === 'win32';
71
+
72
+ function normPath(p) {
73
+ let r = path.resolve(p);
74
+ try { r = fs.realpathSync(r); } catch {}
75
+ return r;
76
+ }
77
+
78
+ function isAlive(pid) {
79
+ if (IS_WIN) return true;
80
+ try { process.kill(pid, 0); return true; } catch { return false; }
81
+ }
82
+
83
+ let input = '';
84
+ process.stdin.setEncoding('utf8');
85
+ process.stdin.on('data', c => { input += c; });
86
+ process.stdin.on('end', () => {
87
+ let cwd;
88
+ try { cwd = JSON.parse(input).cwd; } catch { process.exit(0); }
89
+ if (!cwd) process.exit(0);
90
+
91
+ const resolvedCwd = normPath(cwd);
92
+
93
+ let allFiles;
94
+ try {
95
+ allFiles = fs.readdirSync(DIR).filter(f => f.endsWith('.json') && f !== 'workspaces.json');
96
+ } catch { process.exit(0); }
97
+ if (!allFiles.length) process.exit(0);
98
+
99
+ const matches = [];
100
+ for (const file of allFiles) {
101
+ let d;
102
+ try { d = JSON.parse(fs.readFileSync(path.join(DIR, file), 'utf8')); } catch { continue; }
103
+ if (!d.workspace || !d.pid || !d.port) continue;
104
+
105
+ if (!isAlive(d.pid)) {
106
+ try { fs.unlinkSync(path.join(DIR, file)); } catch {}
107
+ continue;
108
+ }
109
+
110
+ const ws = normPath(d.workspace);
111
+ if (resolvedCwd === ws || resolvedCwd.startsWith(ws + path.sep)) {
112
+ matches.push({ d, file, wsLen: ws.length });
113
+ }
114
+ }
115
+
116
+ if (!matches.length) process.exit(0);
117
+
118
+ matches.sort((a, b) => b.wsLen - a.wsLen);
119
+ const bestLen = matches[0].wsLen;
120
+ const targets = matches.filter(m => m.wsLen === bestLen);
121
+
122
+ let pending = targets.length;
123
+ for (const { d } of targets) {
124
+ let settled = false;
125
+ const finish = () => { if (settled) return; settled = true; done(); };
126
+ const req = http.request({
127
+ hostname: '127.0.0.1', port: d.port, method: 'POST',
128
+ headers: { 'Content-Type': 'application/json' },
129
+ timeout: ${HOOK_FORWARD_TIMEOUT_MS},
130
+ }, res => { res.resume(); res.on('end', finish); });
131
+ req.on('error', finish);
132
+ req.on('timeout', () => { req.destroy(); });
133
+ req.write(input);
134
+ req.end();
135
+ }
136
+
137
+ function done() { if (--pending <= 0) process.exit(0); }
138
+ });
139
+ `;
140
+ }
141
+ function ensureHookScript() {
142
+ if (!fs5.existsSync(DISCOVERY_DIR2)) {
143
+ fs5.mkdirSync(DISCOVERY_DIR2, { recursive: true });
144
+ }
145
+ const script = getHookScriptContent();
146
+ try {
147
+ if (fs5.existsSync(HOOK_SCRIPT_PATH) && fs5.readFileSync(HOOK_SCRIPT_PATH, "utf8") === script) {
148
+ console.log("Hook script already up to date:", HOOK_SCRIPT_PATH);
149
+ return;
150
+ }
151
+ } catch {
152
+ }
153
+ const tmpPath = HOOK_SCRIPT_PATH + `.${process.pid}.tmp`;
154
+ fs5.writeFileSync(tmpPath, script, { mode: 493 });
155
+ fs5.renameSync(tmpPath, HOOK_SCRIPT_PATH);
156
+ console.log("Installed hook script:", HOOK_SCRIPT_PATH);
157
+ }
158
+ function isAgentFlowHook(entry) {
159
+ return entry.hooks?.some(
160
+ (h) => h.command?.includes(HOOK_COMMAND_MARKER) || h.url?.startsWith("http://127.0.0.1:")
161
+ );
162
+ }
163
+ function configureHooks() {
164
+ const nodePath = resolveNodePath();
165
+ const hookCommand = `"${nodePath}" "${HOOK_SCRIPT_PATH}"`;
166
+ const hookEntry = { hooks: [{ type: "command", command: hookCommand, timeout: HOOK_TIMEOUT_S }] };
167
+ const events = [
168
+ "SessionStart",
169
+ "PreToolUse",
170
+ "PostToolUse",
171
+ "PostToolUseFailure",
172
+ "SubagentStart",
173
+ "SubagentStop",
174
+ "Notification",
175
+ "Stop",
176
+ "SessionEnd"
177
+ ];
178
+ let settings = {};
179
+ try {
180
+ if (fs5.existsSync(SETTINGS_PATH)) {
181
+ settings = JSON.parse(fs5.readFileSync(SETTINGS_PATH, "utf-8"));
182
+ }
183
+ } catch {
184
+ console.log("Could not read existing settings, starting fresh");
185
+ }
186
+ const existingHooks = settings.hooks || {};
187
+ for (const event of events) {
188
+ const existing = existingHooks[event] || [];
189
+ const filtered = existing.filter((entry) => !isAgentFlowHook(entry));
190
+ existingHooks[event] = [...filtered, hookEntry];
191
+ }
192
+ settings.hooks = existingHooks;
193
+ const dir = path4.dirname(SETTINGS_PATH);
194
+ if (!fs5.existsSync(dir)) {
195
+ fs5.mkdirSync(dir, { recursive: true });
196
+ }
197
+ fs5.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
198
+ console.log("Configured Claude Code hooks in:", SETTINGS_PATH);
199
+ }
200
+ function isAlreadySetup() {
201
+ if (!fs5.existsSync(HOOK_SCRIPT_PATH))
202
+ return false;
203
+ try {
204
+ if (!fs5.existsSync(SETTINGS_PATH))
205
+ return false;
206
+ const settings = JSON.parse(fs5.readFileSync(SETTINGS_PATH, "utf-8"));
207
+ const hooks = settings.hooks;
208
+ if (!hooks || typeof hooks !== "object")
209
+ return false;
210
+ return Object.values(hooks).some((entries) => {
211
+ if (!Array.isArray(entries))
212
+ return false;
213
+ return entries.some((entry) => isAgentFlowHook(entry));
214
+ });
215
+ } catch {
216
+ return false;
217
+ }
218
+ }
219
+ function ensureSetup2() {
220
+ if (isAlreadySetup())
221
+ return;
222
+ console.log("Setting up agent hooks...");
223
+ ensureHookScript();
224
+ configureHooks();
225
+ console.log("");
226
+ }
227
+ module2.exports = { ensureSetup: ensureSetup2 };
228
+ if (require.main === module2) {
229
+ const force = process.argv.includes("--force");
230
+ if (!force && isAlreadySetup()) {
231
+ console.log("Agent Flow is already set up. Run with --force to reconfigure.");
232
+ process.exit(0);
233
+ }
234
+ console.log("Setting up Agent Flow...\n");
235
+ ensureHookScript();
236
+ configureHooks();
237
+ console.log("\nDone! New sessions will stream events to Agent Flow.");
238
+ }
239
+ }
240
+ });
241
+
242
+ // scripts/vscode-shim.js
243
+ var require_vscode_shim = __commonJS({
244
+ "scripts/vscode-shim.js"(exports2, module2) {
245
+ "use strict";
246
+ var EventEmitter2 = class {
247
+ constructor() {
248
+ this._listeners = [];
249
+ }
250
+ get event() {
251
+ return (listener) => {
252
+ this._listeners.push(listener);
253
+ return { dispose: () => {
254
+ const i = this._listeners.indexOf(listener);
255
+ if (i >= 0)
256
+ this._listeners.splice(i, 1);
257
+ } };
258
+ };
259
+ }
260
+ fire(data) {
261
+ for (const l of this._listeners)
262
+ l(data);
263
+ }
264
+ dispose() {
265
+ this._listeners = [];
266
+ }
267
+ };
268
+ module2.exports = {
269
+ EventEmitter: EventEmitter2,
270
+ workspace: {
271
+ workspaceFolders: [{ uri: { fsPath: process.cwd() } }],
272
+ getConfiguration: () => ({ get: () => void 0 })
273
+ },
274
+ window: {
275
+ showInformationMessage: () => {
276
+ }
277
+ }
278
+ };
279
+ }
280
+ });
281
+
282
+ // app/src/args.ts
283
+ function parseArgs(argv) {
284
+ let port = 3001;
285
+ let open = true;
286
+ for (let i = 0; i < argv.length; i++) {
287
+ const arg = argv[i];
288
+ if ((arg === "--port" || arg === "-p") && argv[i + 1]) {
289
+ const n = parseInt(argv[i + 1], 10);
290
+ if (!isNaN(n) && n > 0 && n < 65536)
291
+ port = n;
292
+ i++;
293
+ } else if (arg === "--no-open") {
294
+ open = false;
295
+ } else if (arg === "--help" || arg === "-h") {
296
+ console.log(`
297
+ Usage: agent-flow [options]
298
+
299
+ Options:
300
+ -p, --port <number> Port for the server (default: 3001)
301
+ --no-open Don't open the browser automatically
302
+ -h, --help Show this help message
303
+ `);
304
+ process.exit(0);
305
+ }
306
+ }
307
+ return { port, open };
308
+ }
309
+
310
+ // app/src/app.ts
311
+ var import_setup = __toESM(require_setup());
312
+
313
+ // app/src/server.ts
314
+ var http2 = __toESM(require("http"));
315
+ var import_child_process = require("child_process");
316
+
317
+ // scripts/relay.ts
318
+ var crypto = __toESM(require("crypto"));
319
+ var fs3 = __toESM(require("fs"));
320
+ var path2 = __toESM(require("path"));
321
+ var os = __toESM(require("os"));
322
+
323
+ // extension/src/hook-server.ts
324
+ var http = __toESM(require("http"));
325
+ var vscode = __toESM(require_vscode_shim());
326
+
327
+ // extension/src/protocol.ts
328
+ function emitSubagentSpawn(emitter, parent, child, task, sessionId) {
329
+ emitter.emit({
330
+ time: emitter.elapsed(sessionId),
331
+ type: "subagent_dispatch",
332
+ payload: { parent, child, task }
333
+ }, sessionId);
334
+ emitter.emit({
335
+ time: emitter.elapsed(sessionId),
336
+ type: "agent_spawn",
337
+ payload: { name: child, parent, task }
338
+ }, sessionId);
339
+ }
340
+
341
+ // extension/src/constants.ts
342
+ var INACTIVITY_TIMEOUT_MS = 5 * 60 * 1e3;
343
+ var SCAN_INTERVAL_MS = 1e3;
344
+ var POLL_FALLBACK_MS = 3e3;
345
+ var PERMISSION_DETECT_MS = 5e3;
346
+ var ACTIVE_SESSION_AGE_S = 10 * 60;
347
+ var HOOK_SERVER_NOT_STARTED = -1;
348
+ var HOOK_MAX_BODY_SIZE = 1024 * 1024;
349
+ var WORKSPACE_HASH_LENGTH = 16;
350
+ var PREVIEW_MAX = 60;
351
+ var ARGS_MAX = 80;
352
+ var RESULT_MAX = 200;
353
+ var MESSAGE_MAX = 2e3;
354
+ var SESSION_LABEL_MAX = 14;
355
+ var SESSION_LABEL_TRUNCATED = SESSION_LABEL_MAX - 2;
356
+ var DISCOVERY_LABEL_MAX = 40;
357
+ var DISCOVERY_LABEL_TAIL = DISCOVERY_LABEL_MAX - 3;
358
+ var DISCOVERY_CONTENT_MAX = 100;
359
+ var TASK_MAX = 60;
360
+ var EDIT_CONTENT_MAX = 500;
361
+ var WEB_FETCH_PROMPT_MAX = 200;
362
+ var CHILD_NAME_MAX = 30;
363
+ var SKILL_NAME_MAX = 40;
364
+ var URL_PATH_MAX = 40;
365
+ var SESSION_ID_DISPLAY = 8;
366
+ var FAILED_RESULT_MAX = 100;
367
+ var CHARS_PER_TOKEN = 4;
368
+ var MIN_TOKEN_ESTIMATE = 10;
369
+ var FALLBACK_TOKEN_ESTIMATE = 200;
370
+ var GREP_TOKEN_MULTIPLIER = 0.5;
371
+ var DEFAULT_TOKEN_MULTIPLIER = 0.3;
372
+ var SYSTEM_PROMPT_BASE_TOKENS = 5e3;
373
+ var HASH_PREFIX_MAX = 200;
374
+ var HOOK_SERVER_HOST = "127.0.0.1";
375
+ var HOOK_URL_PREFIX = `http://${HOOK_SERVER_HOST}:`;
376
+ var ORCHESTRATOR_NAME = "orchestrator";
377
+ var FILE_TOOLS = ["Read", "Edit", "Write", "Glob", "Grep"];
378
+ var PATTERN_TOOLS = ["Glob", "Grep"];
379
+ var SUBAGENT_ID_SUFFIX_LENGTH = 6;
380
+ function generateSubagentFallbackName(id, index) {
381
+ return `subagent-${id.length > SUBAGENT_ID_SUFFIX_LENGTH ? id.slice(-SUBAGENT_ID_SUFFIX_LENGTH) : index}`;
382
+ }
383
+ function resolveSubagentChildName(input) {
384
+ return String(input.description || input.subagent_type || "subagent").slice(0, CHILD_NAME_MAX);
385
+ }
386
+ var SYSTEM_CONTENT_PREFIXES = [
387
+ "This session is being continued",
388
+ "<ide_",
389
+ "<system-reminder",
390
+ "<available-deferred-tools",
391
+ "<command-name"
392
+ ];
393
+
394
+ // extension/src/tool-summarizer.ts
395
+ function tailPath(filePath, segments = 2) {
396
+ return String(filePath).split("/").slice(-segments).join("/");
397
+ }
398
+ function summarizeInput(toolName, input) {
399
+ if (!input) {
400
+ return "";
401
+ }
402
+ switch (toolName) {
403
+ case "Bash":
404
+ return String(input.command || "").slice(0, ARGS_MAX);
405
+ case "Read":
406
+ return tailPath(String(input.file_path || input.path || ""));
407
+ case "Edit":
408
+ return tailPath(String(input.file_path || "")) + " \u2014 edit";
409
+ case "Write":
410
+ return tailPath(String(input.file_path || "")) + " \u2014 write";
411
+ case "Glob":
412
+ return String(input.pattern || "");
413
+ case "Grep":
414
+ return String(input.pattern || "");
415
+ case "Task":
416
+ case "Agent":
417
+ return String(input.description || input.prompt || "").slice(0, TASK_MAX);
418
+ case "TodoWrite": {
419
+ const todos = input.todos;
420
+ if (Array.isArray(todos) && todos.length > 0) {
421
+ const active = todos.find((t) => t.status === "in_progress");
422
+ const label = active?.activeForm || active?.content || todos[0]?.content || "todos";
423
+ const done = todos.filter((t) => t.status === "completed").length;
424
+ return `${label} (${done}/${todos.length})`.slice(0, ARGS_MAX);
425
+ }
426
+ return "updating todos";
427
+ }
428
+ case "WebSearch":
429
+ return String(input.query || "").slice(0, ARGS_MAX);
430
+ case "WebFetch": {
431
+ const url = String(input.url || "");
432
+ try {
433
+ const u = new URL(url);
434
+ return u.hostname + u.pathname.slice(0, URL_PATH_MAX);
435
+ } catch {
436
+ return url.slice(0, ARGS_MAX);
437
+ }
438
+ }
439
+ case "AskUserQuestion": {
440
+ const questions = input.questions;
441
+ if (Array.isArray(questions) && questions[0]?.question) {
442
+ return String(questions[0].question).slice(0, ARGS_MAX);
443
+ }
444
+ return "asking user...";
445
+ }
446
+ case "Skill":
447
+ return String(input.skill || "").slice(0, SKILL_NAME_MAX);
448
+ case "NotebookEdit":
449
+ return tailPath(String(input.notebook_path || "")) + ` cell ${input.cell_number ?? "?"}`;
450
+ default:
451
+ return JSON.stringify(input).slice(0, ARGS_MAX);
452
+ }
453
+ }
454
+ function summarizeResult(content) {
455
+ if (typeof content === "string") {
456
+ return content.slice(0, RESULT_MAX);
457
+ }
458
+ if (Array.isArray(content)) {
459
+ return content.map((c) => {
460
+ if (typeof c === "string") {
461
+ return c;
462
+ }
463
+ if (c && typeof c === "object" && "text" in c) {
464
+ return String(c.text);
465
+ }
466
+ return "";
467
+ }).join("\n").slice(0, RESULT_MAX);
468
+ }
469
+ if (content && typeof content === "object" && !Array.isArray(content)) {
470
+ const obj = content;
471
+ if (typeof obj.content === "string") {
472
+ return obj.content.slice(0, RESULT_MAX);
473
+ }
474
+ if (typeof obj.text === "string") {
475
+ return obj.text.slice(0, RESULT_MAX);
476
+ }
477
+ try {
478
+ return JSON.stringify(content).slice(0, RESULT_MAX);
479
+ } catch {
480
+ }
481
+ }
482
+ return String(content || "").slice(0, RESULT_MAX);
483
+ }
484
+ function extractInputData(toolName, input) {
485
+ if (!input) {
486
+ return void 0;
487
+ }
488
+ try {
489
+ switch (toolName) {
490
+ case "Edit":
491
+ return {
492
+ file_path: String(input.file_path || ""),
493
+ old_string: String(input.old_string || "").slice(0, EDIT_CONTENT_MAX),
494
+ new_string: String(input.new_string || "").slice(0, EDIT_CONTENT_MAX)
495
+ };
496
+ case "TodoWrite":
497
+ return { todos: input.todos };
498
+ case "Write":
499
+ return {
500
+ file_path: String(input.file_path || ""),
501
+ content: String(input.content || "").slice(0, EDIT_CONTENT_MAX)
502
+ };
503
+ case "Bash":
504
+ return {
505
+ command: String(input.command || ""),
506
+ description: String(input.description || "")
507
+ };
508
+ case "Read":
509
+ return {
510
+ file_path: String(input.file_path || input.path || ""),
511
+ offset: typeof input.offset === "number" ? input.offset : void 0,
512
+ limit: typeof input.limit === "number" ? input.limit : void 0
513
+ };
514
+ case "Grep":
515
+ return {
516
+ pattern: String(input.pattern || ""),
517
+ path: String(input.path || ""),
518
+ glob: typeof input.glob === "string" ? input.glob : void 0
519
+ };
520
+ case "Glob":
521
+ return {
522
+ pattern: String(input.pattern || ""),
523
+ path: String(input.path || "")
524
+ };
525
+ case "WebSearch":
526
+ return {
527
+ query: String(input.query || "")
528
+ };
529
+ case "WebFetch":
530
+ return {
531
+ url: String(input.url || ""),
532
+ prompt: String(input.prompt || "").slice(0, WEB_FETCH_PROMPT_MAX)
533
+ };
534
+ case "AskUserQuestion": {
535
+ const qs = input.questions;
536
+ return {
537
+ questions: Array.isArray(qs) ? qs.map((q) => ({
538
+ question: String(q.question || ""),
539
+ options: Array.isArray(q.options) ? q.options.map((o) => String(o.label || "")) : []
540
+ })) : []
541
+ };
542
+ }
543
+ default:
544
+ return void 0;
545
+ }
546
+ } catch {
547
+ return void 0;
548
+ }
549
+ }
550
+ function extractFilePath(input) {
551
+ if (!input) {
552
+ return void 0;
553
+ }
554
+ const raw = input.file_path || input.path;
555
+ return typeof raw === "string" ? raw : void 0;
556
+ }
557
+ function buildDiscovery(toolName, filePath, result) {
558
+ if (!FILE_TOOLS.includes(toolName) || !filePath) {
559
+ return void 0;
560
+ }
561
+ return {
562
+ type: PATTERN_TOOLS.includes(toolName) ? "pattern" : "file",
563
+ label: filePath.length > DISCOVERY_LABEL_MAX ? "..." + filePath.slice(-DISCOVERY_LABEL_TAIL) : filePath,
564
+ content: result.slice(0, DISCOVERY_CONTENT_MAX)
565
+ };
566
+ }
567
+ function detectError(content) {
568
+ const lower = content.toLowerCase();
569
+ const patterns = [
570
+ "error:",
571
+ "error[",
572
+ "exception:",
573
+ "failed",
574
+ "permission denied",
575
+ "command failed",
576
+ "cannot find",
577
+ "not found",
578
+ "enoent",
579
+ "fatal:",
580
+ "panic:",
581
+ "segfault",
582
+ "syntax error"
583
+ ];
584
+ return patterns.some((p) => lower.includes(p));
585
+ }
586
+
587
+ // extension/src/token-estimator.ts
588
+ function estimateTokensFromContent(content) {
589
+ if (typeof content === "string") {
590
+ return Math.max(Math.ceil(content.length / CHARS_PER_TOKEN), MIN_TOKEN_ESTIMATE);
591
+ }
592
+ if (Array.isArray(content)) {
593
+ let total = 0;
594
+ for (const item of content) {
595
+ if (typeof item === "string") {
596
+ total += item.length;
597
+ } else if (item && typeof item === "object" && "text" in item) {
598
+ total += String(item.text || "").length;
599
+ }
600
+ }
601
+ return Math.max(Math.ceil(total / CHARS_PER_TOKEN), MIN_TOKEN_ESTIMATE);
602
+ }
603
+ return FALLBACK_TOKEN_ESTIMATE;
604
+ }
605
+ function estimateTokensFromText(text) {
606
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
607
+ }
608
+ function estimateTokenCost(toolName, result) {
609
+ const baseTokens = Math.ceil(result.length / CHARS_PER_TOKEN);
610
+ if (toolName === "Read") {
611
+ return baseTokens;
612
+ }
613
+ if (toolName === "Grep" || toolName === "Glob") {
614
+ return Math.ceil(baseTokens * GREP_TOKEN_MULTIPLIER);
615
+ }
616
+ return Math.ceil(baseTokens * DEFAULT_TOKEN_MULTIPLIER);
617
+ }
618
+
619
+ // extension/src/logger.ts
620
+ var LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
621
+ var minLevel = "info";
622
+ function shouldLog(level) {
623
+ return (LOG_LEVELS[level] ?? 0) >= (LOG_LEVELS[minLevel] ?? 0);
624
+ }
625
+ function createLogger(tag) {
626
+ const prefix = `[${tag}]`;
627
+ return {
628
+ debug(...args2) {
629
+ if (shouldLog("debug"))
630
+ console.log(prefix, ...args2);
631
+ },
632
+ info(...args2) {
633
+ if (shouldLog("info"))
634
+ console.log(prefix, ...args2);
635
+ },
636
+ warn(...args2) {
637
+ if (shouldLog("warn"))
638
+ console.warn(prefix, ...args2);
639
+ },
640
+ error(...args2) {
641
+ if (shouldLog("error"))
642
+ console.error(prefix, ...args2);
643
+ }
644
+ };
645
+ }
646
+
647
+ // extension/src/hook-server.ts
648
+ var log = createLogger("HookServer");
649
+ var HookServer = class {
650
+ server = null;
651
+ port;
652
+ /** Per-session state — cleaned up on SessionEnd/Stop to prevent unbounded growth */
653
+ sessionState = /* @__PURE__ */ new Map();
654
+ _onEvent = new vscode.EventEmitter();
655
+ onEvent = this._onEvent.event;
656
+ constructor(port) {
657
+ this.port = port ?? 0;
658
+ }
659
+ async start() {
660
+ return new Promise((resolve2, reject) => {
661
+ this.server = http.createServer((req, res) => {
662
+ if (req.method === "POST") {
663
+ let body = "";
664
+ let oversized = false;
665
+ req.on("data", (chunk) => {
666
+ if (oversized)
667
+ return;
668
+ body += chunk.toString();
669
+ if (body.length > HOOK_MAX_BODY_SIZE) {
670
+ oversized = true;
671
+ body = "";
672
+ log.warn("Request body exceeded size limit, discarding");
673
+ }
674
+ });
675
+ req.on("end", () => {
676
+ if (!oversized) {
677
+ try {
678
+ const parsed = JSON.parse(body);
679
+ if (!parsed || typeof parsed !== "object" || !("session_id" in parsed) || !("hook_event_name" in parsed) || typeof parsed.session_id !== "string" || typeof parsed.hook_event_name !== "string") {
680
+ log.warn("Invalid hook payload: missing session_id or hook_event_name");
681
+ } else {
682
+ this.handleHook(parsed);
683
+ }
684
+ } catch (e) {
685
+ log.error("Failed to parse payload:", e);
686
+ }
687
+ }
688
+ res.writeHead(200);
689
+ res.end();
690
+ });
691
+ } else {
692
+ res.writeHead(200, { "Content-Type": "text/plain" });
693
+ res.end("Agent Visualizer Hook Server");
694
+ }
695
+ });
696
+ this.server.on("error", (err) => {
697
+ if (err.code === "EADDRINUSE") {
698
+ log.info(`Port ${this.port} in use (another instance owns it) \u2014 skipping hook server`);
699
+ this.server?.close();
700
+ this.server = null;
701
+ resolve2(HOOK_SERVER_NOT_STARTED);
702
+ } else {
703
+ reject(err);
704
+ }
705
+ });
706
+ this.server.listen(this.port, HOOK_SERVER_HOST, () => {
707
+ const addr = this.server.address();
708
+ this.port = addr.port;
709
+ log.info(`Listening on http://127.0.0.1:${this.port}`);
710
+ resolve2(this.port);
711
+ });
712
+ });
713
+ }
714
+ getPort() {
715
+ return this.port;
716
+ }
717
+ getOrCreateSession(sessionId) {
718
+ let state = this.sessionState.get(sessionId);
719
+ if (!state) {
720
+ state = { startTime: Date.now(), agentNames: /* @__PURE__ */ new Map() };
721
+ this.sessionState.set(sessionId, state);
722
+ }
723
+ return state;
724
+ }
725
+ elapsedSeconds(sessionId) {
726
+ const startTime = sessionId ? this.sessionState.get(sessionId)?.startTime ?? Date.now() : Date.now();
727
+ return (Date.now() - startTime) / 1e3;
728
+ }
729
+ handleHook(payload) {
730
+ const eventName = payload.hook_event_name;
731
+ log.debug(eventName, payload.tool_name || payload.agent_type || "");
732
+ switch (eventName) {
733
+ case "SessionStart":
734
+ this.handleSessionStart(payload);
735
+ break;
736
+ case "PreToolUse":
737
+ this.handlePreToolUse(payload);
738
+ break;
739
+ case "PostToolUse":
740
+ this.handlePostToolUse(payload);
741
+ break;
742
+ case "PostToolUseFailure":
743
+ this.handlePostToolUseFailure(payload);
744
+ break;
745
+ case "SubagentStart":
746
+ this.handleSubagentStart(payload);
747
+ break;
748
+ case "SubagentStop":
749
+ this.handleSubagentStop(payload);
750
+ break;
751
+ case "Notification":
752
+ this.handleNotification(payload);
753
+ break;
754
+ case "Stop":
755
+ this.handleStop(payload);
756
+ break;
757
+ case "SessionEnd":
758
+ this.handleSessionEnd(payload);
759
+ break;
760
+ }
761
+ }
762
+ handleSessionStart(payload) {
763
+ this.getOrCreateSession(payload.session_id);
764
+ this.emit({
765
+ time: 0,
766
+ type: "agent_spawn",
767
+ payload: {
768
+ name: ORCHESTRATOR_NAME,
769
+ isMain: true,
770
+ task: `Session ${payload.session_id.slice(0, SESSION_ID_DISPLAY)}`
771
+ }
772
+ }, payload.session_id);
773
+ }
774
+ handlePreToolUse(payload) {
775
+ const agentName = this.resolveAgentName(payload);
776
+ const toolName = payload.tool_name || "unknown";
777
+ const args2 = summarizeInput(toolName, payload.tool_input);
778
+ if (!this.sessionState.has(payload.session_id)) {
779
+ this.handleSessionStart(payload);
780
+ }
781
+ this.emit({
782
+ time: this.elapsedSeconds(payload.session_id),
783
+ type: "tool_call_start",
784
+ payload: {
785
+ agent: agentName,
786
+ tool: toolName,
787
+ args: args2,
788
+ preview: `${toolName}: ${args2}`.slice(0, PREVIEW_MAX)
789
+ }
790
+ }, payload.session_id);
791
+ }
792
+ handlePostToolUse(payload) {
793
+ const agentName = this.resolveAgentName(payload);
794
+ const toolName = payload.tool_name || "unknown";
795
+ const result = payload.tool_response ? summarizeResult(payload.tool_response) : "";
796
+ const tokenCost = estimateTokenCost(toolName, result);
797
+ const discovery = buildDiscovery(toolName, extractFilePath(payload.tool_input), result);
798
+ this.emit({
799
+ time: this.elapsedSeconds(payload.session_id),
800
+ type: "tool_call_end",
801
+ payload: {
802
+ agent: agentName,
803
+ tool: toolName,
804
+ result: result.slice(0, RESULT_MAX),
805
+ tokenCost,
806
+ ...discovery ? { discovery } : {}
807
+ }
808
+ }, payload.session_id);
809
+ }
810
+ handlePostToolUseFailure(payload) {
811
+ const agentName = this.resolveAgentName(payload);
812
+ const toolName = payload.tool_name || "unknown";
813
+ this.emit({
814
+ time: this.elapsedSeconds(payload.session_id),
815
+ type: "tool_call_end",
816
+ payload: {
817
+ agent: agentName,
818
+ tool: toolName,
819
+ result: `[FAILED] ${(payload.tool_response ? summarizeResult(payload.tool_response) : "").slice(0, FAILED_RESULT_MAX)}`,
820
+ tokenCost: 0
821
+ }
822
+ }, payload.session_id);
823
+ }
824
+ handleSubagentStart(payload) {
825
+ const parentName = this.resolveAgentName(payload);
826
+ const agentType = payload.agent_type || "subagent";
827
+ const agentId = payload.agent_id || "";
828
+ const sessionAgents = this.getOrCreateSession(payload.session_id).agentNames;
829
+ const childName = agentId ? `${agentType}-${agentId.slice(-SUBAGENT_ID_SUFFIX_LENGTH)}` : generateSubagentFallbackName(String(Date.now()), sessionAgents.size + 1);
830
+ sessionAgents.set(agentId, childName);
831
+ emitSubagentSpawn(
832
+ { emit: (e, s) => this.emit(e, s), elapsed: (s) => this.elapsedSeconds(s) },
833
+ parentName,
834
+ childName,
835
+ agentType,
836
+ payload.session_id
837
+ );
838
+ }
839
+ handleSubagentStop(payload) {
840
+ const agentId = payload.agent_id || "";
841
+ const sessionAgents = this.sessionState.get(payload.session_id)?.agentNames;
842
+ const childName = sessionAgents?.get(agentId) || "subagent";
843
+ const parentName = this.resolveAgentName(payload);
844
+ this.emit({
845
+ time: this.elapsedSeconds(payload.session_id),
846
+ type: "subagent_return",
847
+ payload: { child: childName, parent: parentName, summary: `${payload.agent_type} complete` }
848
+ }, payload.session_id);
849
+ this.emit({
850
+ time: this.elapsedSeconds(payload.session_id),
851
+ type: "agent_complete",
852
+ payload: { name: childName }
853
+ }, payload.session_id);
854
+ }
855
+ handleNotification(payload) {
856
+ if (payload.notification_type !== "permission_prompt")
857
+ return;
858
+ this.emit({
859
+ time: this.elapsedSeconds(payload.session_id),
860
+ type: "permission_requested",
861
+ payload: {
862
+ agent: ORCHESTRATOR_NAME,
863
+ message: payload.message || "Permission needed",
864
+ title: payload.title || "Permission needed"
865
+ }
866
+ }, payload.session_id);
867
+ }
868
+ handleStop(payload) {
869
+ this.emit({
870
+ time: this.elapsedSeconds(payload.session_id),
871
+ type: "agent_complete",
872
+ payload: { name: ORCHESTRATOR_NAME }
873
+ }, payload.session_id);
874
+ }
875
+ handleSessionEnd(payload) {
876
+ this.emit({
877
+ time: this.elapsedSeconds(payload.session_id),
878
+ type: "agent_complete",
879
+ payload: { name: ORCHESTRATOR_NAME, sessionEnd: true }
880
+ }, payload.session_id);
881
+ this.sessionState.delete(payload.session_id);
882
+ }
883
+ // ─── Helpers ─────────────────────────────────────────────────────────────
884
+ resolveAgentName(payload) {
885
+ if (payload.agent_id) {
886
+ const name = this.sessionState.get(payload.session_id)?.agentNames.get(payload.agent_id);
887
+ if (name)
888
+ return name;
889
+ }
890
+ return ORCHESTRATOR_NAME;
891
+ }
892
+ emit(event, sessionId) {
893
+ this._onEvent.fire(sessionId ? { ...event, sessionId } : event);
894
+ }
895
+ dispose() {
896
+ if (this.server) {
897
+ this.server.close();
898
+ this.server = null;
899
+ }
900
+ this.sessionState.clear();
901
+ this._onEvent.dispose();
902
+ }
903
+ };
904
+
905
+ // extension/src/fs-utils.ts
906
+ var fs = __toESM(require("fs"));
907
+ function readFileChunk(filePath, offset, length) {
908
+ const fd = fs.openSync(filePath, "r");
909
+ try {
910
+ const buffer = Buffer.alloc(length);
911
+ fs.readSync(fd, buffer, 0, length, offset);
912
+ return buffer.toString("utf-8");
913
+ } finally {
914
+ fs.closeSync(fd);
915
+ }
916
+ }
917
+ function readNewFileLines(filePath, lastSize) {
918
+ let stat;
919
+ try {
920
+ stat = fs.statSync(filePath);
921
+ } catch {
922
+ return null;
923
+ }
924
+ if (stat.size < lastSize) {
925
+ return { lines: [], newSize: 0 };
926
+ }
927
+ if (stat.size <= lastSize) {
928
+ return null;
929
+ }
930
+ const newContent = readFileChunk(filePath, lastSize, stat.size - lastSize);
931
+ const lines = newContent.split(/\r?\n/).filter(Boolean);
932
+ return { lines, newSize: stat.size };
933
+ }
934
+
935
+ // extension/src/transcript-parser.ts
936
+ var log2 = createLogger("TranscriptParser");
937
+ function isRecord(v) {
938
+ return v !== null && typeof v === "object";
939
+ }
940
+ function safeText(block) {
941
+ if (!isRecord(block))
942
+ return "";
943
+ return String(block.text || "").trim();
944
+ }
945
+ function safeThinking(block) {
946
+ if (!isRecord(block))
947
+ return "";
948
+ return String(block.thinking || "").trim();
949
+ }
950
+ var TranscriptParser = class {
951
+ constructor(delegate) {
952
+ this.delegate = delegate;
953
+ }
954
+ /** Per-subagent dedup state for inline progress events, keyed by parentToolUseID */
955
+ inlineSubagentState = /* @__PURE__ */ new Map();
956
+ /** Maps Agent tool_use ID → resolved child agent name (set in handleToolUse) */
957
+ subagentChildNames = /* @__PURE__ */ new Map();
958
+ /** Clean up state associated with a completed session to prevent unbounded Map growth.
959
+ * Pass the session's pending tool_use_ids so we can remove orphaned entries. */
960
+ clearSessionState(pendingToolUseIds) {
961
+ for (const toolUseId of pendingToolUseIds) {
962
+ this.inlineSubagentState.delete(toolUseId);
963
+ this.subagentChildNames.delete(toolUseId);
964
+ }
965
+ }
966
+ processTranscriptLine(line, agentName = ORCHESTRATOR_NAME, ctxPending, ctxSeen, sessionId, ctxSeenMessages) {
967
+ let parsed;
968
+ try {
969
+ parsed = JSON.parse(line.trim());
970
+ } catch (err) {
971
+ log2.debug("Skipping unparseable line:", err);
972
+ return;
973
+ }
974
+ if (parsed.type === "progress") {
975
+ this.handleProgressEvent(parsed, sessionId);
976
+ return;
977
+ }
978
+ if (parsed.type !== "user" && parsed.type !== "assistant") {
979
+ return;
980
+ }
981
+ const msg = parsed.message;
982
+ if (!msg) {
983
+ return;
984
+ }
985
+ const entry = {
986
+ sessionId: parsed.sessionId,
987
+ type: parsed.type,
988
+ uuid: parsed.uuid,
989
+ message: msg
990
+ };
991
+ if (sessionId) {
992
+ this.maybeSetSessionLabel(entry, sessionId);
993
+ }
994
+ const role = msg.role;
995
+ const session = sessionId ? this.delegate.getSession(sessionId) : void 0;
996
+ if (session && !session.model && entry.type === "assistant" && msg.model) {
997
+ session.model = msg.model;
998
+ this.delegate.emit({
999
+ time: this.delegate.elapsed(sessionId),
1000
+ type: "model_detected",
1001
+ payload: { agent: agentName, model: msg.model }
1002
+ }, sessionId);
1003
+ }
1004
+ const seenMsgs = ctxSeenMessages ?? session?.seenMessageHashes;
1005
+ if (typeof msg.content === "string" && msg.content.trim()) {
1006
+ if (role === "user" || role === "human") {
1007
+ const text = msg.content.trim();
1008
+ if (!this.isSystemInjectedContent(text)) {
1009
+ const hash = entry.uuid ? `user:${entry.uuid}` : `user:${text.slice(0, HASH_PREFIX_MAX)}`;
1010
+ if (seenMsgs && seenMsgs.has(hash)) {
1011
+ } else {
1012
+ seenMsgs?.add(hash);
1013
+ if (session) {
1014
+ session.contextBreakdown.userMessages += estimateTokensFromText(text);
1015
+ }
1016
+ this.delegate.emit({
1017
+ time: this.delegate.elapsed(sessionId),
1018
+ type: "message",
1019
+ payload: { agent: agentName, role: "user", content: text.slice(0, MESSAGE_MAX) }
1020
+ }, sessionId);
1021
+ }
1022
+ }
1023
+ }
1024
+ if (session) {
1025
+ this.delegate.emitContextUpdate(agentName, session, sessionId);
1026
+ }
1027
+ return;
1028
+ }
1029
+ if (!Array.isArray(msg.content)) {
1030
+ return;
1031
+ }
1032
+ const emitRole = role === "user" || role === "human" ? "user" : "assistant";
1033
+ for (const block of msg.content) {
1034
+ if (block.type === "tool_use") {
1035
+ const toolBlock = block;
1036
+ if (ctxSeen.has(toolBlock.id)) {
1037
+ continue;
1038
+ }
1039
+ ctxSeen.add(toolBlock.id);
1040
+ this.handleToolUse(toolBlock, agentName, ctxPending, sessionId);
1041
+ } else if (block.type === "tool_result") {
1042
+ this.handleToolResult(block, agentName, ctxPending, sessionId);
1043
+ } else if (block.type === "text" && "text" in block) {
1044
+ this.handleTextBlock(block, emitRole, entry.uuid, agentName, seenMsgs, session, sessionId);
1045
+ } else if (block.type === "thinking" && "thinking" in block) {
1046
+ this.handleThinkingBlock(block, entry.uuid, agentName, seenMsgs, session, sessionId);
1047
+ }
1048
+ }
1049
+ if (session) {
1050
+ this.delegate.emitContextUpdate(agentName, session, sessionId);
1051
+ }
1052
+ }
1053
+ /** Process a text block — dedup, track tokens, emit message event */
1054
+ handleTextBlock(block, emitRole, entryUuid, agentName, seenMsgs, session, sessionId) {
1055
+ const text = safeText(block);
1056
+ if (!text)
1057
+ return;
1058
+ if (emitRole === "user" && this.isSystemInjectedContent(text))
1059
+ return;
1060
+ const hash = entryUuid ? `${emitRole}:${entryUuid}` : `${emitRole}:${text.slice(0, HASH_PREFIX_MAX)}`;
1061
+ if (seenMsgs?.has(hash))
1062
+ return;
1063
+ seenMsgs?.add(hash);
1064
+ if (session) {
1065
+ if (emitRole === "user") {
1066
+ session.contextBreakdown.userMessages += estimateTokensFromText(text);
1067
+ } else {
1068
+ session.contextBreakdown.reasoning += estimateTokensFromText(text);
1069
+ }
1070
+ }
1071
+ this.delegate.emit({
1072
+ time: this.delegate.elapsed(sessionId),
1073
+ type: "message",
1074
+ payload: { agent: agentName, role: emitRole, content: text.slice(0, MESSAGE_MAX) }
1075
+ }, sessionId);
1076
+ }
1077
+ /** Process a thinking block — dedup, track tokens, emit message event */
1078
+ handleThinkingBlock(block, entryUuid, agentName, seenMsgs, session, sessionId) {
1079
+ const thinking = safeThinking(block);
1080
+ if (!thinking)
1081
+ return;
1082
+ const hash = entryUuid ? `thinking:${entryUuid}` : `thinking:${thinking.slice(0, HASH_PREFIX_MAX)}`;
1083
+ if (seenMsgs?.has(hash))
1084
+ return;
1085
+ seenMsgs?.add(hash);
1086
+ if (session) {
1087
+ session.contextBreakdown.reasoning += estimateTokensFromText(thinking);
1088
+ }
1089
+ this.delegate.emit({
1090
+ time: this.delegate.elapsed(sessionId),
1091
+ type: "message",
1092
+ payload: { agent: agentName, role: "thinking", content: thinking.slice(0, MESSAGE_MAX) }
1093
+ }, sessionId);
1094
+ }
1095
+ handleToolUse(block, agentName, ctxPending, sessionId) {
1096
+ const toolName = block.name;
1097
+ const args2 = summarizeInput(toolName, block.input);
1098
+ const rawPath = block.input.file_path || block.input.path;
1099
+ const filePath = typeof rawPath === "string" ? rawPath : void 0;
1100
+ ctxPending.set(block.id, {
1101
+ name: toolName,
1102
+ args: args2,
1103
+ filePath,
1104
+ startTime: Date.now()
1105
+ });
1106
+ if (toolName === "Task" || toolName === "Agent") {
1107
+ const childName = resolveSubagentChildName(block.input);
1108
+ this.subagentChildNames.set(block.id, childName);
1109
+ const session = sessionId ? this.delegate.getSession(sessionId) : void 0;
1110
+ if (!session?.spawnedSubagents.has(childName)) {
1111
+ session?.spawnedSubagents.add(childName);
1112
+ emitSubagentSpawn(this.delegate, agentName, childName, args2, sessionId);
1113
+ }
1114
+ }
1115
+ this.delegate.emit({
1116
+ time: this.delegate.elapsed(sessionId),
1117
+ type: "tool_call_start",
1118
+ payload: {
1119
+ agent: agentName,
1120
+ tool: toolName,
1121
+ args: args2,
1122
+ preview: `${toolName}: ${args2}`.slice(0, PREVIEW_MAX),
1123
+ inputData: extractInputData(toolName, block.input)
1124
+ }
1125
+ }, sessionId);
1126
+ }
1127
+ handleToolResult(block, agentName, ctxPending, sessionId) {
1128
+ const pending = ctxPending.get(block.tool_use_id);
1129
+ if (!pending) {
1130
+ return;
1131
+ }
1132
+ const toolName = pending.name;
1133
+ const result = summarizeResult(block.content);
1134
+ const tokenCost = estimateTokensFromContent(block.content);
1135
+ if (sessionId) {
1136
+ const session = this.delegate.getSession(sessionId);
1137
+ if (session) {
1138
+ if (toolName === "Task" || toolName === "Agent") {
1139
+ session.contextBreakdown.subagentResults += tokenCost;
1140
+ } else {
1141
+ session.contextBreakdown.toolResults += tokenCost;
1142
+ }
1143
+ }
1144
+ }
1145
+ ctxPending.delete(block.tool_use_id);
1146
+ const discovery = buildDiscovery(toolName, pending?.filePath || "", result);
1147
+ if (toolName === "Task" || toolName === "Agent") {
1148
+ const childName = this.subagentChildNames.get(block.tool_use_id) || pending?.args?.slice(0, CHILD_NAME_MAX) || "subagent";
1149
+ this.subagentChildNames.delete(block.tool_use_id);
1150
+ this.inlineSubagentState.delete(block.tool_use_id);
1151
+ this.delegate.emit({
1152
+ time: this.delegate.elapsed(sessionId),
1153
+ type: "subagent_return",
1154
+ payload: { child: childName, parent: agentName, summary: result.slice(0, ARGS_MAX) }
1155
+ }, sessionId);
1156
+ this.delegate.emit({
1157
+ time: this.delegate.elapsed(sessionId),
1158
+ type: "agent_complete",
1159
+ payload: { name: childName }
1160
+ }, sessionId);
1161
+ }
1162
+ const isError = detectError(result);
1163
+ const errorMessage = isError ? result.slice(0, FAILED_RESULT_MAX) : void 0;
1164
+ this.delegate.emit({
1165
+ time: this.delegate.elapsed(sessionId),
1166
+ type: "tool_call_end",
1167
+ payload: {
1168
+ agent: agentName,
1169
+ tool: toolName,
1170
+ result: result.slice(0, RESULT_MAX),
1171
+ tokenCost,
1172
+ ...discovery ? { discovery } : {},
1173
+ ...isError ? { isError, errorMessage } : {}
1174
+ }
1175
+ }, sessionId);
1176
+ if (sessionId) {
1177
+ const session = this.delegate.getSession(sessionId);
1178
+ if (session) {
1179
+ this.delegate.emitContextUpdate(agentName, session, sessionId);
1180
+ }
1181
+ }
1182
+ }
1183
+ /**
1184
+ * Handle inline progress events from newer Claude Code versions.
1185
+ * These carry subagent transcript entries directly in the main JSONL,
1186
+ * keyed by parentToolUseID (the Agent tool_use that spawned the subagent).
1187
+ */
1188
+ handleProgressEvent(parsed, sessionId) {
1189
+ const data = parsed.data;
1190
+ if (!isRecord(data) || data.type !== "agent_progress") {
1191
+ return;
1192
+ }
1193
+ const innerEntry = data.message;
1194
+ if (!innerEntry?.message) {
1195
+ return;
1196
+ }
1197
+ const parentToolUseID = typeof parsed.parentToolUseID === "string" ? parsed.parentToolUseID : void 0;
1198
+ if (!parentToolUseID) {
1199
+ return;
1200
+ }
1201
+ let subState = this.inlineSubagentState.get(parentToolUseID);
1202
+ if (!subState) {
1203
+ const childName = this.subagentChildNames.get(parentToolUseID) || generateSubagentFallbackName("", this.inlineSubagentState.size + 1);
1204
+ subState = {
1205
+ agentName: childName,
1206
+ pending: /* @__PURE__ */ new Map(),
1207
+ seen: /* @__PURE__ */ new Set(),
1208
+ seenMessages: /* @__PURE__ */ new Set()
1209
+ };
1210
+ this.inlineSubagentState.set(parentToolUseID, subState);
1211
+ if (sessionId) {
1212
+ const session = this.delegate.getSession(sessionId);
1213
+ session?.inlineProgressAgents.add(childName);
1214
+ }
1215
+ }
1216
+ const innerLine = JSON.stringify(innerEntry);
1217
+ this.processTranscriptLine(innerLine, subState.agentName, subState.pending, subState.seen, sessionId, subState.seenMessages);
1218
+ }
1219
+ /**
1220
+ * Pre-scan existing file content:
1221
+ * 1. Build seenToolUseIds dedup set (prevents re-emitting old tool calls)
1222
+ * 2. Return all entries for catch-up emission
1223
+ */
1224
+ prescanExistingContent(filePath, size, session) {
1225
+ if (size === 0) {
1226
+ return [];
1227
+ }
1228
+ const catchUpEntries = [];
1229
+ try {
1230
+ const content = readFileChunk(filePath, 0, size);
1231
+ for (const line of content.split(/\r?\n/)) {
1232
+ if (!line.trim()) {
1233
+ continue;
1234
+ }
1235
+ try {
1236
+ const entry = JSON.parse(line.trim());
1237
+ const isUser = entry.message?.role === "user" || entry.message?.role === "human";
1238
+ if (entry.message && Array.isArray(entry.message.content)) {
1239
+ for (const block of entry.message.content) {
1240
+ if (block.type === "tool_use" && block.id) {
1241
+ const toolBlock = block;
1242
+ session.seenToolUseIds.add(toolBlock.id);
1243
+ if (toolBlock.name === "Agent" || toolBlock.name === "Task") {
1244
+ const childName = resolveSubagentChildName(toolBlock.input);
1245
+ session.spawnedSubagents.add(childName);
1246
+ this.subagentChildNames.set(toolBlock.id, childName);
1247
+ }
1248
+ const args2 = summarizeInput(toolBlock.name, toolBlock.input);
1249
+ const rawPath = toolBlock.input.file_path || toolBlock.input.path;
1250
+ const filePath2 = typeof rawPath === "string" ? rawPath : void 0;
1251
+ session.pendingToolCalls.set(toolBlock.id, { name: toolBlock.name, args: args2, filePath: filePath2, startTime: Date.now() });
1252
+ } else if (block.type === "tool_result") {
1253
+ const resultBlock = block;
1254
+ if (resultBlock.tool_use_id) {
1255
+ session.pendingToolCalls.delete(resultBlock.tool_use_id);
1256
+ }
1257
+ session.contextBreakdown.toolResults += estimateTokensFromContent(resultBlock.content);
1258
+ } else if (block.type === "text" && "text" in block) {
1259
+ const text = safeText(block);
1260
+ if (text) {
1261
+ const emitRole = isUser ? "user" : "assistant";
1262
+ const hashKey = entry.uuid ? `${emitRole}:${entry.uuid}` : `${emitRole}:${text.slice(0, HASH_PREFIX_MAX)}`;
1263
+ session.seenMessageHashes.add(hashKey);
1264
+ if (isUser) {
1265
+ session.contextBreakdown.userMessages += estimateTokensFromText(text);
1266
+ } else {
1267
+ session.contextBreakdown.reasoning += estimateTokensFromText(text);
1268
+ }
1269
+ }
1270
+ } else if (block.type === "thinking" && "thinking" in block) {
1271
+ const thinking = safeThinking(block);
1272
+ if (thinking) {
1273
+ const hashKey = entry.uuid ? `thinking:${entry.uuid}` : `thinking:${thinking.slice(0, HASH_PREFIX_MAX)}`;
1274
+ session.seenMessageHashes.add(hashKey);
1275
+ session.contextBreakdown.reasoning += estimateTokensFromText(thinking);
1276
+ }
1277
+ }
1278
+ }
1279
+ } else if (entry.type === "user" && typeof entry.message?.content === "string") {
1280
+ const text = entry.message.content.trim();
1281
+ if (text) {
1282
+ const hashKey = entry.uuid ? `user:${entry.uuid}` : `user:${text.slice(0, HASH_PREFIX_MAX)}`;
1283
+ session.seenMessageHashes.add(hashKey);
1284
+ session.contextBreakdown.userMessages += estimateTokensFromText(text);
1285
+ }
1286
+ }
1287
+ if (entry.type === "assistant" && entry.message?.model && !session.model) {
1288
+ session.model = entry.message.model;
1289
+ }
1290
+ if (entry.type === "user" || entry.type === "assistant") {
1291
+ catchUpEntries.push(entry);
1292
+ }
1293
+ } catch (err) {
1294
+ log2.debug("Skipping unparseable transcript line:", err);
1295
+ }
1296
+ }
1297
+ log2.info(`Pre-scanned ${session.seenToolUseIds.size} existing tool_use IDs, ${catchUpEntries.length} entries total`);
1298
+ return catchUpEntries;
1299
+ } catch (err) {
1300
+ log2.error("Pre-scan failed:", err);
1301
+ return [];
1302
+ }
1303
+ }
1304
+ /** Emit message events for pre-existing transcript entries (catch-up on session detection).
1305
+ * Only emits the last user message (the current turn), not the full history. */
1306
+ emitCatchUpEntries(entries, session, sessionId) {
1307
+ let lastUserIndex = -1;
1308
+ for (let i = entries.length - 1; i >= 0; i--) {
1309
+ if (entries[i].type === "user") {
1310
+ lastUserIndex = i;
1311
+ break;
1312
+ }
1313
+ }
1314
+ if (lastUserIndex === -1) {
1315
+ return;
1316
+ }
1317
+ const recentEntries = entries.slice(lastUserIndex);
1318
+ for (const entry of recentEntries) {
1319
+ const role = entry.type === "user" ? "user" : "assistant";
1320
+ const msg = entry.message;
1321
+ if (!msg) {
1322
+ continue;
1323
+ }
1324
+ if (typeof msg.content === "string" && msg.content.trim()) {
1325
+ const text = msg.content.trim();
1326
+ if (this.isSystemInjectedContent(text)) {
1327
+ continue;
1328
+ }
1329
+ this.delegate.emit({
1330
+ time: 0,
1331
+ type: "message",
1332
+ payload: { agent: ORCHESTRATOR_NAME, role, content: text.slice(0, MESSAGE_MAX) }
1333
+ }, sessionId);
1334
+ continue;
1335
+ }
1336
+ if (!Array.isArray(msg.content)) {
1337
+ continue;
1338
+ }
1339
+ for (const block of msg.content) {
1340
+ if (block.type === "text" && "text" in block) {
1341
+ const text = safeText(block);
1342
+ if (text && !this.isSystemInjectedContent(text)) {
1343
+ this.delegate.emit({
1344
+ time: 0,
1345
+ type: "message",
1346
+ payload: { agent: ORCHESTRATOR_NAME, role, content: text.slice(0, MESSAGE_MAX) }
1347
+ }, sessionId);
1348
+ }
1349
+ }
1350
+ }
1351
+ }
1352
+ }
1353
+ /** Extract a human-readable label from the first user message in transcript entries */
1354
+ extractSessionLabel(entries, session) {
1355
+ if (session.labelSet)
1356
+ return;
1357
+ for (const entry of entries) {
1358
+ if (entry.type !== "user")
1359
+ continue;
1360
+ const text = this.extractUserMessageText(entry);
1361
+ if (text) {
1362
+ session.label = this.truncateLabel(text);
1363
+ session.labelSet = true;
1364
+ return;
1365
+ }
1366
+ }
1367
+ }
1368
+ /** Extract text content from a user transcript entry, skipping system-injected tags */
1369
+ extractUserMessageText(entry) {
1370
+ const msg = entry.message;
1371
+ if (!msg)
1372
+ return null;
1373
+ if (typeof msg.content === "string" && msg.content.trim()) {
1374
+ const text = msg.content.trim();
1375
+ if (!this.isSystemInjectedContent(text))
1376
+ return text;
1377
+ return null;
1378
+ }
1379
+ if (Array.isArray(msg.content)) {
1380
+ for (const block of msg.content) {
1381
+ if (block.type === "text" && "text" in block) {
1382
+ const text = safeText(block);
1383
+ if (text && !this.isSystemInjectedContent(text))
1384
+ return text;
1385
+ }
1386
+ }
1387
+ }
1388
+ return null;
1389
+ }
1390
+ /** Check if text is system-injected context (not a real user/assistant message) */
1391
+ isSystemInjectedContent(text) {
1392
+ return SYSTEM_CONTENT_PREFIXES.some((prefix) => text.startsWith(prefix));
1393
+ }
1394
+ /** Truncate to first line, max chars — fits in a tab */
1395
+ truncateLabel(text) {
1396
+ const firstLine = text.split("\n")[0].trim();
1397
+ if (firstLine.length <= SESSION_LABEL_MAX)
1398
+ return firstLine;
1399
+ return firstLine.slice(0, SESSION_LABEL_TRUNCATED) + "..";
1400
+ }
1401
+ /** Update session label on first user message and notify the webview */
1402
+ maybeSetSessionLabel(entry, sessionId) {
1403
+ const session = this.delegate.getSession(sessionId);
1404
+ if (!session || session.labelSet)
1405
+ return;
1406
+ if (entry.type !== "user")
1407
+ return;
1408
+ const text = this.extractUserMessageText(entry);
1409
+ if (!text)
1410
+ return;
1411
+ session.label = this.truncateLabel(text);
1412
+ session.labelSet = true;
1413
+ this.delegate.fireSessionLifecycle({ type: "updated", sessionId, label: session.label });
1414
+ }
1415
+ };
1416
+
1417
+ // extension/src/subagent-watcher.ts
1418
+ var fs2 = __toESM(require("fs"));
1419
+ var path = __toESM(require("path"));
1420
+
1421
+ // extension/src/permission-detection.ts
1422
+ function handlePermissionDetection(delegate, agentName, pendingToolCalls, permState, sessionId, sessionCompleted, checkSessionActivity) {
1423
+ if (permState.permissionTimer) {
1424
+ clearTimeout(permState.permissionTimer);
1425
+ permState.permissionTimer = null;
1426
+ }
1427
+ if (permState.permissionEmitted) {
1428
+ permState.permissionEmitted = false;
1429
+ delegate.emit({
1430
+ time: delegate.elapsed(sessionId),
1431
+ type: "agent_idle",
1432
+ payload: { name: agentName }
1433
+ }, sessionId);
1434
+ }
1435
+ const needsPermission = Array.from(pendingToolCalls.values()).some((tc) => tc.name !== "Agent" && tc.name !== "Task");
1436
+ if (needsPermission) {
1437
+ const snapshotTime = Date.now();
1438
+ permState.permissionTimer = setTimeout(() => {
1439
+ if (permState.permissionEmitted || sessionCompleted)
1440
+ return;
1441
+ const stillPending = Array.from(pendingToolCalls.values()).some((tc) => tc.name !== "Agent" && tc.name !== "Task" && tc.startTime <= snapshotTime);
1442
+ if (!stillPending)
1443
+ return;
1444
+ if (checkSessionActivity) {
1445
+ const lastActivity = delegate.getLastActivityTime(sessionId);
1446
+ if (lastActivity !== void 0) {
1447
+ const recentThreshold = Date.now() - PERMISSION_DETECT_MS;
1448
+ if (lastActivity > recentThreshold)
1449
+ return;
1450
+ }
1451
+ }
1452
+ permState.permissionEmitted = true;
1453
+ delegate.emit({
1454
+ time: delegate.elapsed(sessionId),
1455
+ type: "permission_requested",
1456
+ payload: {
1457
+ agent: agentName,
1458
+ message: "Waiting for permission"
1459
+ }
1460
+ }, sessionId);
1461
+ }, PERMISSION_DETECT_MS);
1462
+ }
1463
+ }
1464
+
1465
+ // extension/src/subagent-watcher.ts
1466
+ var log3 = createLogger("SubagentWatcher");
1467
+ function resolveNameFromMeta(jsonlPath, fallbackIndex) {
1468
+ const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
1469
+ try {
1470
+ const raw = fs2.readFileSync(metaPath, "utf-8");
1471
+ const meta = JSON.parse(raw);
1472
+ const name = resolveSubagentChildName(meta);
1473
+ if (name && name !== "subagent")
1474
+ return name;
1475
+ } catch {
1476
+ }
1477
+ return generateSubagentFallbackName("", fallbackIndex);
1478
+ }
1479
+ function scanSubagentsDir(delegate, parser2, sessionId) {
1480
+ const session = delegate.getSession(sessionId);
1481
+ if (!session || !session.subagentsDir)
1482
+ return;
1483
+ if (!session.subagentsDirWatcher && fs2.existsSync(session.subagentsDir)) {
1484
+ try {
1485
+ session.subagentsDirWatcher = fs2.watch(session.subagentsDir, () => {
1486
+ scanSubagentsDir(delegate, parser2, sessionId);
1487
+ });
1488
+ } catch (err) {
1489
+ log3.debug("Subagent dir watch failed:", err);
1490
+ }
1491
+ }
1492
+ const subDir = session.subagentsDir;
1493
+ if (!fs2.existsSync(subDir))
1494
+ return;
1495
+ try {
1496
+ const files = fs2.readdirSync(subDir);
1497
+ for (const file of files) {
1498
+ if (!file.endsWith(".jsonl"))
1499
+ continue;
1500
+ const filePath = path.join(subDir, file);
1501
+ if (session.subagentWatchers.has(filePath))
1502
+ continue;
1503
+ startWatchingSubagentFile(delegate, parser2, filePath, sessionId);
1504
+ }
1505
+ } catch (err) {
1506
+ log3.debug("Subagent dir scan failed:", err);
1507
+ }
1508
+ }
1509
+ function startWatchingSubagentFile(delegate, parser2, filePath, sessionId) {
1510
+ const session = delegate.getSession(sessionId);
1511
+ if (!session)
1512
+ return;
1513
+ const agentName = resolveNameFromMeta(filePath, session.subagentWatchers.size + 1);
1514
+ log3.info(`Tailing subagent: ${path.basename(filePath)} as "${agentName}" (session ${sessionId.slice(0, SESSION_ID_DISPLAY)})`);
1515
+ const state = {
1516
+ watcher: null,
1517
+ fileSize: 0,
1518
+ agentName,
1519
+ pendingToolCalls: /* @__PURE__ */ new Map(),
1520
+ seenToolUseIds: /* @__PURE__ */ new Set(),
1521
+ permissionTimer: null,
1522
+ permissionEmitted: false,
1523
+ spawnEmitted: false
1524
+ };
1525
+ session.subagentWatchers.set(filePath, state);
1526
+ const pendingToolUseIds = /* @__PURE__ */ new Set();
1527
+ try {
1528
+ const stat = fs2.statSync(filePath);
1529
+ if (stat.size > 0) {
1530
+ const content = fs2.readFileSync(filePath, "utf-8");
1531
+ for (const line of content.split(/\r?\n/)) {
1532
+ if (!line.trim())
1533
+ continue;
1534
+ try {
1535
+ const raw = JSON.parse(line.trim());
1536
+ const entry = raw;
1537
+ if (raw && typeof raw === "object" && entry.message && Array.isArray(entry.message.content)) {
1538
+ for (const block of entry.message.content) {
1539
+ if (block.type === "tool_use" && block.id) {
1540
+ state.seenToolUseIds.add(block.id);
1541
+ pendingToolUseIds.add(block.id);
1542
+ } else if (block.type === "tool_result" && block.tool_use_id) {
1543
+ pendingToolUseIds.delete(block.tool_use_id);
1544
+ }
1545
+ }
1546
+ }
1547
+ } catch {
1548
+ }
1549
+ }
1550
+ state.fileSize = stat.size;
1551
+ }
1552
+ } catch (err) {
1553
+ log3.debug("Subagent initial read failed:", err);
1554
+ }
1555
+ const alreadySpawned = session.spawnedSubagents.has(agentName);
1556
+ state.spawnEmitted = pendingToolUseIds.size > 0 || alreadySpawned;
1557
+ if (pendingToolUseIds.size > 0 && !alreadySpawned) {
1558
+ session.spawnedSubagents.add(agentName);
1559
+ emitSubagentSpawn(delegate, ORCHESTRATOR_NAME, agentName, agentName, sessionId);
1560
+ }
1561
+ try {
1562
+ state.watcher = fs2.watch(filePath, () => {
1563
+ readSubagentNewLines(delegate, parser2, filePath, sessionId);
1564
+ });
1565
+ } catch (err) {
1566
+ log3.debug("Subagent file watch failed:", err);
1567
+ }
1568
+ }
1569
+ function readSubagentNewLines(delegate, parser2, filePath, sessionId) {
1570
+ const session = delegate.getSession(sessionId);
1571
+ if (!session)
1572
+ return;
1573
+ const state = session.subagentWatchers.get(filePath);
1574
+ if (!state)
1575
+ return;
1576
+ const result = readNewFileLines(filePath, state.fileSize);
1577
+ if (!result)
1578
+ return;
1579
+ state.fileSize = result.newSize;
1580
+ if (session.inlineProgressAgents.has(state.agentName)) {
1581
+ delegate.resetInactivityTimer(sessionId);
1582
+ return;
1583
+ }
1584
+ if (!state.spawnEmitted) {
1585
+ state.spawnEmitted = true;
1586
+ if (!session.spawnedSubagents.has(state.agentName)) {
1587
+ session.spawnedSubagents.add(state.agentName);
1588
+ emitSubagentSpawn(delegate, ORCHESTRATOR_NAME, state.agentName, state.agentName, sessionId);
1589
+ }
1590
+ }
1591
+ for (const line of result.lines) {
1592
+ parser2.processTranscriptLine(line, state.agentName, state.pendingToolCalls, state.seenToolUseIds, sessionId);
1593
+ }
1594
+ handlePermissionDetection(delegate, state.agentName, state.pendingToolCalls, state, sessionId);
1595
+ delegate.resetInactivityTimer(sessionId);
1596
+ }
1597
+
1598
+ // scripts/relay.ts
1599
+ var MAX_EVENT_BUFFER = 5e3;
1600
+ var DISCOVERY_DIR = path2.join(os.homedir(), ".claude", "agent-flow");
1601
+ var CLAUDE_DIR = path2.join(os.homedir(), ".claude", "projects");
1602
+ var relayCreated = false;
1603
+ var sseClients = /* @__PURE__ */ new Set();
1604
+ function sendSSE(res, data) {
1605
+ try {
1606
+ res.write(`data: ${JSON.stringify(data)}
1607
+
1608
+ `);
1609
+ } catch {
1610
+ sseClients.delete(res);
1611
+ }
1612
+ }
1613
+ function broadcast(data) {
1614
+ for (const res of sseClients) {
1615
+ try {
1616
+ res.write(`data: ${data}
1617
+
1618
+ `);
1619
+ } catch {
1620
+ sseClients.delete(res);
1621
+ }
1622
+ }
1623
+ }
1624
+ var eventBuffer = /* @__PURE__ */ new Map();
1625
+ function broadcastEvent(event) {
1626
+ const sid = event.sessionId?.slice(0, SESSION_ID_DISPLAY) || "?";
1627
+ console.log(`[event] ${event.type} (session ${sid})`);
1628
+ if (event.sessionId) {
1629
+ let buf = eventBuffer.get(event.sessionId) || [];
1630
+ buf.push(event);
1631
+ if (buf.length > MAX_EVENT_BUFFER) {
1632
+ buf = buf.slice(buf.length - MAX_EVENT_BUFFER);
1633
+ }
1634
+ eventBuffer.set(event.sessionId, buf);
1635
+ }
1636
+ broadcast(JSON.stringify({ type: "agent-event", event }));
1637
+ }
1638
+ function broadcastSessionLifecycle(type, sessionId, label) {
1639
+ if (type === "started") {
1640
+ broadcast(JSON.stringify({
1641
+ type: "session-started",
1642
+ session: { id: sessionId, label, status: "active", startTime: Date.now(), lastActivityTime: Date.now() }
1643
+ }));
1644
+ } else if (type === "ended") {
1645
+ broadcast(JSON.stringify({ type: "session-ended", sessionId }));
1646
+ } else if (type === "updated") {
1647
+ broadcast(JSON.stringify({ type: "session-updated", sessionId, label }));
1648
+ }
1649
+ }
1650
+ var sessions = /* @__PURE__ */ new Map();
1651
+ function elapsed(sessionId) {
1652
+ if (sessionId) {
1653
+ const session = sessions.get(sessionId);
1654
+ if (session)
1655
+ return (Date.now() - session.sessionStartTime) / 1e3;
1656
+ }
1657
+ return 0;
1658
+ }
1659
+ function emitContextUpdate(agentName, session, sessionId) {
1660
+ const bd = session.contextBreakdown;
1661
+ const total = bd.systemPrompt + bd.userMessages + bd.toolResults + bd.reasoning + bd.subagentResults;
1662
+ broadcastEvent({
1663
+ time: elapsed(sessionId),
1664
+ type: "context_update",
1665
+ payload: { agent: agentName, tokens: total, breakdown: { ...bd } },
1666
+ sessionId
1667
+ });
1668
+ }
1669
+ function emitEvent(event, sessionId) {
1670
+ broadcastEvent(sessionId ? { ...event, sessionId } : event);
1671
+ }
1672
+ var parser = new TranscriptParser({
1673
+ emit: emitEvent,
1674
+ elapsed,
1675
+ getSession: (sessionId) => sessions.get(sessionId),
1676
+ fireSessionLifecycle: (event) => broadcastSessionLifecycle(event.type, event.sessionId, event.label),
1677
+ emitContextUpdate
1678
+ });
1679
+ var watcherDelegate = {
1680
+ emit: emitEvent,
1681
+ elapsed,
1682
+ getSession: (sessionId) => sessions.get(sessionId),
1683
+ getLastActivityTime: (sessionId) => sessions.get(sessionId)?.lastActivityTime,
1684
+ resetInactivityTimer: (sessionId) => resetInactivityTimer(sessionId)
1685
+ };
1686
+ function resetInactivityTimer(sessionId) {
1687
+ const session = sessions.get(sessionId);
1688
+ if (!session)
1689
+ return;
1690
+ const wasCompleted = session.sessionCompleted;
1691
+ session.lastActivityTime = Date.now();
1692
+ session.sessionCompleted = false;
1693
+ if (wasCompleted) {
1694
+ broadcastEvent({
1695
+ time: elapsed(sessionId),
1696
+ type: "agent_spawn",
1697
+ payload: { name: ORCHESTRATOR_NAME, isMain: true, task: session.label, ...session.model ? { model: session.model } : {} },
1698
+ sessionId
1699
+ });
1700
+ broadcastSessionLifecycle("started", sessionId, session.label);
1701
+ }
1702
+ if (session.inactivityTimer)
1703
+ clearTimeout(session.inactivityTimer);
1704
+ session.inactivityTimer = setTimeout(() => {
1705
+ if (!session.sessionCompleted && session.sessionDetected) {
1706
+ console.log(`[session] ${sessionId.slice(0, SESSION_ID_DISPLAY)} inactive`);
1707
+ session.sessionCompleted = true;
1708
+ broadcastEvent({
1709
+ time: elapsed(sessionId),
1710
+ type: "agent_complete",
1711
+ payload: { name: ORCHESTRATOR_NAME },
1712
+ sessionId
1713
+ });
1714
+ broadcastSessionLifecycle("ended", sessionId, session.label);
1715
+ }
1716
+ }, INACTIVITY_TIMEOUT_MS);
1717
+ }
1718
+ function watchSession(sessionId, filePath) {
1719
+ const defaultLabel = `Session ${sessionId.slice(0, SESSION_ID_DISPLAY)}`;
1720
+ const session = {
1721
+ sessionId,
1722
+ filePath,
1723
+ fileWatcher: null,
1724
+ pollTimer: null,
1725
+ fileSize: 0,
1726
+ sessionStartTime: Date.now(),
1727
+ pendingToolCalls: /* @__PURE__ */ new Map(),
1728
+ seenToolUseIds: /* @__PURE__ */ new Set(),
1729
+ seenMessageHashes: /* @__PURE__ */ new Set(),
1730
+ sessionDetected: false,
1731
+ sessionCompleted: false,
1732
+ lastActivityTime: Date.now(),
1733
+ inactivityTimer: null,
1734
+ subagentWatchers: /* @__PURE__ */ new Map(),
1735
+ spawnedSubagents: /* @__PURE__ */ new Set(),
1736
+ inlineProgressAgents: /* @__PURE__ */ new Set(),
1737
+ subagentsDirWatcher: null,
1738
+ subagentsDir: null,
1739
+ label: defaultLabel,
1740
+ labelSet: false,
1741
+ model: null,
1742
+ permissionTimer: null,
1743
+ permissionEmitted: false,
1744
+ contextBreakdown: { systemPrompt: SYSTEM_PROMPT_BASE_TOKENS, userMessages: 0, toolResults: 0, reasoning: 0, subagentResults: 0 }
1745
+ };
1746
+ sessions.set(sessionId, session);
1747
+ const stat = fs3.statSync(filePath);
1748
+ const catchUpEntries = parser.prescanExistingContent(filePath, stat.size, session);
1749
+ session.fileSize = stat.size;
1750
+ parser.extractSessionLabel(catchUpEntries, session);
1751
+ broadcastSessionLifecycle("started", sessionId, session.label);
1752
+ broadcastEvent({
1753
+ time: 0,
1754
+ type: "agent_spawn",
1755
+ payload: { name: ORCHESTRATOR_NAME, isMain: true, task: session.label, ...session.model ? { model: session.model } : {} },
1756
+ sessionId
1757
+ });
1758
+ session.sessionDetected = true;
1759
+ emitContextUpdate(ORCHESTRATOR_NAME, session, sessionId);
1760
+ parser.emitCatchUpEntries(catchUpEntries, session, sessionId);
1761
+ session.fileWatcher = fs3.watch(filePath, (eventType) => {
1762
+ if (eventType === "change")
1763
+ readNewLines(sessionId);
1764
+ });
1765
+ session.pollTimer = setInterval(() => {
1766
+ readNewLines(sessionId);
1767
+ for (const [subPath] of session.subagentWatchers) {
1768
+ readSubagentNewLines(watcherDelegate, parser, subPath, sessionId);
1769
+ }
1770
+ scanSubagentsDir(watcherDelegate, parser, sessionId);
1771
+ }, POLL_FALLBACK_MS);
1772
+ session.subagentsDir = path2.join(path2.dirname(filePath), sessionId, "subagents");
1773
+ scanSubagentsDir(watcherDelegate, parser, sessionId);
1774
+ resetInactivityTimer(sessionId);
1775
+ console.log(`[session] Watching ${sessionId.slice(0, SESSION_ID_DISPLAY)} \u2014 "${session.label}"`);
1776
+ }
1777
+ function readNewLines(sessionId) {
1778
+ const session = sessions.get(sessionId);
1779
+ if (!session)
1780
+ return;
1781
+ const result = readNewFileLines(session.filePath, session.fileSize);
1782
+ if (!result)
1783
+ return;
1784
+ session.fileSize = result.newSize;
1785
+ for (const line of result.lines) {
1786
+ parser.processTranscriptLine(line, ORCHESTRATOR_NAME, session.pendingToolCalls, session.seenToolUseIds, sessionId, session.seenMessageHashes);
1787
+ }
1788
+ handlePermissionDetection(watcherDelegate, ORCHESTRATOR_NAME, session.pendingToolCalls, session, sessionId, session.sessionCompleted, true);
1789
+ scanSubagentsDir(watcherDelegate, parser, sessionId);
1790
+ resetInactivityTimer(sessionId);
1791
+ }
1792
+ function scanForActiveSessions(workspace) {
1793
+ if (!fs3.existsSync(CLAUDE_DIR))
1794
+ return;
1795
+ let resolved = workspace;
1796
+ try {
1797
+ resolved = fs3.realpathSync(resolved);
1798
+ } catch {
1799
+ }
1800
+ const encoded = resolved.replace(/[/\\:]/g, "-");
1801
+ const dirsToScan = [];
1802
+ const projectDir = path2.join(CLAUDE_DIR, encoded);
1803
+ if (fs3.existsSync(projectDir))
1804
+ dirsToScan.push(projectDir);
1805
+ try {
1806
+ for (const dir of fs3.readdirSync(CLAUDE_DIR, { withFileTypes: true })) {
1807
+ if (!dir.isDirectory())
1808
+ continue;
1809
+ const fullPath = path2.join(CLAUDE_DIR, dir.name);
1810
+ if (fullPath === projectDir)
1811
+ continue;
1812
+ if (dir.name.startsWith(encoded + "-")) {
1813
+ dirsToScan.push(fullPath);
1814
+ }
1815
+ }
1816
+ } catch {
1817
+ }
1818
+ for (const dirPath of dirsToScan) {
1819
+ try {
1820
+ for (const file of fs3.readdirSync(dirPath)) {
1821
+ if (!file.endsWith(".jsonl"))
1822
+ continue;
1823
+ const filePath = path2.join(dirPath, file);
1824
+ const stat = fs3.statSync(filePath);
1825
+ const sessionId = path2.basename(file, ".jsonl");
1826
+ let newestMtime = stat.mtimeMs;
1827
+ const subagentsDir = path2.join(dirPath, sessionId, "subagents");
1828
+ try {
1829
+ if (fs3.existsSync(subagentsDir)) {
1830
+ for (const subFile of fs3.readdirSync(subagentsDir)) {
1831
+ if (!subFile.endsWith(".jsonl"))
1832
+ continue;
1833
+ const subStat = fs3.statSync(path2.join(subagentsDir, subFile));
1834
+ if (subStat.mtimeMs > newestMtime)
1835
+ newestMtime = subStat.mtimeMs;
1836
+ }
1837
+ }
1838
+ } catch {
1839
+ }
1840
+ const ageSeconds = (Date.now() - newestMtime) / 1e3;
1841
+ if (ageSeconds <= ACTIVE_SESSION_AGE_S && !sessions.has(sessionId)) {
1842
+ watchSession(sessionId, filePath);
1843
+ }
1844
+ }
1845
+ } catch {
1846
+ }
1847
+ }
1848
+ }
1849
+ function normalizePath(p) {
1850
+ let resolved = path2.resolve(p);
1851
+ try {
1852
+ resolved = fs3.realpathSync(resolved);
1853
+ } catch {
1854
+ }
1855
+ return resolved;
1856
+ }
1857
+ function hashWorkspace(workspace) {
1858
+ return crypto.createHash("sha256").update(normalizePath(workspace)).digest("hex").slice(0, WORKSPACE_HASH_LENGTH);
1859
+ }
1860
+ var discoveryFilePath = null;
1861
+ function writeDiscoveryFile(port, workspace) {
1862
+ if (!fs3.existsSync(DISCOVERY_DIR))
1863
+ fs3.mkdirSync(DISCOVERY_DIR, { recursive: true });
1864
+ const hash = hashWorkspace(workspace);
1865
+ discoveryFilePath = path2.join(DISCOVERY_DIR, `${hash}-${process.pid}.json`);
1866
+ fs3.writeFileSync(discoveryFilePath, JSON.stringify({ port, pid: process.pid, workspace: normalizePath(workspace) }, null, 2) + "\n");
1867
+ }
1868
+ function removeDiscoveryFile() {
1869
+ if (discoveryFilePath) {
1870
+ try {
1871
+ fs3.unlinkSync(discoveryFilePath);
1872
+ } catch {
1873
+ }
1874
+ }
1875
+ }
1876
+ async function createRelay(workspace) {
1877
+ if (relayCreated) {
1878
+ throw new Error("createRelay() can only be called once per process");
1879
+ }
1880
+ relayCreated = true;
1881
+ const hookServer = new HookServer();
1882
+ const hookPort = await hookServer.start();
1883
+ if (hookPort === HOOK_SERVER_NOT_STARTED) {
1884
+ throw new Error("Failed to start hook server (port in use)");
1885
+ }
1886
+ hookServer.onEvent((event) => {
1887
+ broadcast(JSON.stringify({ type: "agent-event", event }));
1888
+ });
1889
+ writeDiscoveryFile(hookPort, workspace);
1890
+ scanForActiveSessions(workspace);
1891
+ const scanInterval = setInterval(() => scanForActiveSessions(workspace), SCAN_INTERVAL_MS);
1892
+ const resolved = (() => {
1893
+ try {
1894
+ return fs3.realpathSync(workspace);
1895
+ } catch {
1896
+ return workspace;
1897
+ }
1898
+ })();
1899
+ const encoded = resolved.replace(/[/\\:]/g, "-");
1900
+ const projectDir = path2.join(CLAUDE_DIR, encoded);
1901
+ let projectDirWatcher = null;
1902
+ if (fs3.existsSync(projectDir)) {
1903
+ try {
1904
+ projectDirWatcher = fs3.watch(projectDir, (_eventType, filename) => {
1905
+ if (filename?.endsWith(".jsonl"))
1906
+ scanForActiveSessions(workspace);
1907
+ });
1908
+ } catch {
1909
+ }
1910
+ }
1911
+ return {
1912
+ handleSSE(req, res) {
1913
+ res.writeHead(200, {
1914
+ "Content-Type": "text/event-stream",
1915
+ "Cache-Control": "no-cache",
1916
+ "Connection": "keep-alive"
1917
+ });
1918
+ sseClients.add(res);
1919
+ console.log(`[sse] Client connected (${sseClients.size} total)`);
1920
+ req.on("close", () => {
1921
+ sseClients.delete(res);
1922
+ console.log(`[sse] Client disconnected (${sseClients.size} total)`);
1923
+ });
1924
+ const sessionList = [];
1925
+ for (const session of sessions.values()) {
1926
+ if (!session.sessionDetected)
1927
+ continue;
1928
+ sessionList.push({
1929
+ id: session.sessionId,
1930
+ label: session.label,
1931
+ status: session.sessionCompleted ? "completed" : "active",
1932
+ startTime: session.sessionStartTime,
1933
+ lastActivityTime: session.lastActivityTime
1934
+ });
1935
+ }
1936
+ if (sessionList.length > 0) {
1937
+ sendSSE(res, { type: "session-list", sessions: sessionList });
1938
+ }
1939
+ const sorted = [...sessionList].sort((a, b) => {
1940
+ const aActive = a.status === "active" ? 1 : 0;
1941
+ const bActive = b.status === "active" ? 1 : 0;
1942
+ if (aActive !== bActive)
1943
+ return bActive - aActive;
1944
+ return b.lastActivityTime - a.lastActivityTime;
1945
+ });
1946
+ if (sorted.length > 0) {
1947
+ const buffered = eventBuffer.get(sorted[0].id);
1948
+ if (buffered) {
1949
+ sendSSE(res, { type: "agent-event-batch", events: buffered });
1950
+ }
1951
+ }
1952
+ },
1953
+ dispose() {
1954
+ removeDiscoveryFile();
1955
+ hookServer.dispose();
1956
+ clearInterval(scanInterval);
1957
+ projectDirWatcher?.close();
1958
+ for (const session of sessions.values()) {
1959
+ session.fileWatcher?.close();
1960
+ if (session.pollTimer)
1961
+ clearInterval(session.pollTimer);
1962
+ if (session.inactivityTimer)
1963
+ clearTimeout(session.inactivityTimer);
1964
+ }
1965
+ }
1966
+ };
1967
+ }
1968
+
1969
+ // app/src/static.ts
1970
+ var fs4 = __toESM(require("fs"));
1971
+ var path3 = __toESM(require("path"));
1972
+ var WEBVIEW_DIR = path3.join(__dirname, "webview");
1973
+ var HTML_SHELL = `<!DOCTYPE html>
1974
+ <html lang="en" class="dark">
1975
+ <head>
1976
+ <meta charset="UTF-8">
1977
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1978
+ <title>Agent Flow</title>
1979
+ <link rel="stylesheet" href="/index.css">
1980
+ <style>html, body { height: 100%; margin: 0; padding: 0; }</style>
1981
+ </head>
1982
+ <body class="font-sans antialiased" style="background: #0a0a1a;">
1983
+ <div id="root" style="height: 100%;"></div>
1984
+ <script src="/index.js"></script>
1985
+ </body>
1986
+ </html>`;
1987
+ var MIME_TYPES = {
1988
+ ".js": "application/javascript",
1989
+ ".css": "text/css",
1990
+ ".html": "text/html"
1991
+ };
1992
+ function serveStatic(req, res) {
1993
+ const url = req.url || "/";
1994
+ if (url === "/" || url === "/index.html") {
1995
+ res.writeHead(200, { "Content-Type": "text/html" });
1996
+ res.end(HTML_SHELL);
1997
+ return;
1998
+ }
1999
+ const basename4 = path3.basename(url);
2000
+ const ext = path3.extname(basename4);
2001
+ const mime = MIME_TYPES[ext];
2002
+ if (!mime) {
2003
+ res.writeHead(404);
2004
+ res.end("Not found");
2005
+ return;
2006
+ }
2007
+ const filePath = path3.join(WEBVIEW_DIR, basename4);
2008
+ if (!filePath.startsWith(WEBVIEW_DIR)) {
2009
+ res.writeHead(403);
2010
+ res.end("Forbidden");
2011
+ return;
2012
+ }
2013
+ try {
2014
+ const content = fs4.readFileSync(filePath);
2015
+ res.writeHead(200, { "Content-Type": mime });
2016
+ res.end(content);
2017
+ } catch {
2018
+ res.writeHead(404);
2019
+ res.end("Not found");
2020
+ }
2021
+ }
2022
+
2023
+ // app/src/server.ts
2024
+ async function startServer(options) {
2025
+ const { port, openBrowser, workspace } = options;
2026
+ const relay = await createRelay(workspace);
2027
+ const server = http2.createServer((req, res) => {
2028
+ if (req.url === "/events") {
2029
+ return relay.handleSSE(req, res);
2030
+ }
2031
+ if (req.method === "GET") {
2032
+ return serveStatic(req, res);
2033
+ }
2034
+ res.writeHead(404);
2035
+ res.end("Not found");
2036
+ });
2037
+ server.listen(port, "127.0.0.1", () => {
2038
+ const url = `http://127.0.0.1:${port}`;
2039
+ console.log(`Server running at ${url}`);
2040
+ console.log("Waiting for agent events...\n");
2041
+ if (openBrowser) {
2042
+ openURL(url);
2043
+ }
2044
+ });
2045
+ function cleanup() {
2046
+ server.close();
2047
+ relay.dispose();
2048
+ process.exit(0);
2049
+ }
2050
+ process.on("SIGINT", cleanup);
2051
+ process.on("SIGTERM", cleanup);
2052
+ process.on("exit", () => relay.dispose());
2053
+ }
2054
+ function openURL(url) {
2055
+ if (process.platform === "win32") {
2056
+ (0, import_child_process.exec)(`start "" "${url}"`);
2057
+ } else {
2058
+ const cmd = process.platform === "darwin" ? "open" : "xdg-open";
2059
+ (0, import_child_process.execFile)(cmd, [url], () => {
2060
+ });
2061
+ }
2062
+ }
2063
+
2064
+ // app/src/app.ts
2065
+ var args = parseArgs(process.argv.slice(2));
2066
+ console.log("Agent Flow\n");
2067
+ (0, import_setup.ensureSetup)();
2068
+ startServer({
2069
+ port: args.port,
2070
+ openBrowser: args.open,
2071
+ workspace: process.cwd()
2072
+ });