panopticon-cli 0.3.7 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +1162 -6
  2. package/dist/agents-RCAPFJG7.js +41 -0
  3. package/dist/chunk-4BIZ4OVN.js +827 -0
  4. package/dist/chunk-4BIZ4OVN.js.map +1 -0
  5. package/dist/{chunk-C6A7S65K.js → chunk-7BGFIAWQ.js} +188 -43
  6. package/dist/chunk-7BGFIAWQ.js.map +1 -0
  7. package/dist/chunk-DGUM43GV.js +11 -0
  8. package/dist/chunk-DGUM43GV.js.map +1 -0
  9. package/dist/{chunk-P5TQ5C3J.js → chunk-U4LCHEVU.js} +14 -1
  10. package/dist/chunk-U4LCHEVU.js.map +1 -0
  11. package/dist/cli/index.js +6546 -2413
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/dashboard/public/assets/index-BZXQno9X.js +540 -0
  14. package/dist/dashboard/public/assets/{index-CUoYoWX_.css → index-BtAxF_yl.css} +1 -1
  15. package/dist/dashboard/public/index.html +2 -2
  16. package/dist/dashboard/server.js +9890 -47449
  17. package/dist/index.d.ts +12 -1
  18. package/dist/index.js +10 -3
  19. package/dist/js-yaml-DLUPUHNL.js +2648 -0
  20. package/dist/js-yaml-DLUPUHNL.js.map +1 -0
  21. package/package.json +2 -2
  22. package/scripts/git-hooks/post-checkout +109 -0
  23. package/templates/claude-md/sections/beads.md +1 -2
  24. package/templates/claude-md/sections/warnings.md +19 -0
  25. package/templates/docker/dotnet/docker-compose.yml +2 -0
  26. package/templates/docker/monorepo/docker-compose.yml +4 -0
  27. package/templates/docker/nextjs/docker-compose.yml +1 -0
  28. package/templates/docker/python-fastapi/docker-compose.yml +3 -0
  29. package/templates/docker/react-vite/docker-compose.yml +1 -0
  30. package/templates/docker/spring-boot/docker-compose.yml +3 -0
  31. package/dist/chunk-BH6BR26M.js +0 -173
  32. package/dist/chunk-BH6BR26M.js.map +0 -1
  33. package/dist/chunk-C6A7S65K.js.map +0 -1
  34. package/dist/chunk-P5TQ5C3J.js.map +0 -1
  35. package/dist/dashboard/public/assets/index-CY0Ew5B9.js +0 -423
  36. package/dist/projects-54CV437J.js +0 -34
  37. /package/dist/{projects-54CV437J.js.map → agents-RCAPFJG7.js.map} +0 -0
@@ -0,0 +1,827 @@
1
+ import {
2
+ AGENTS_DIR
3
+ } from "./chunk-U4LCHEVU.js";
4
+
5
+ // src/lib/agents.ts
6
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync3, readdirSync as readdirSync3, appendFileSync, unlinkSync as unlinkSync2 } from "fs";
7
+ import { join as join3 } from "path";
8
+ import { homedir } from "os";
9
+ import { exec } from "child_process";
10
+ import { promisify } from "util";
11
+
12
+ // src/lib/tmux.ts
13
+ import { execSync } from "child_process";
14
+ import { writeFileSync, chmodSync } from "fs";
15
+ function listSessions() {
16
+ try {
17
+ const output = execSync('tmux list-sessions -F "#{session_name}|#{session_created}|#{session_attached}|#{session_windows}"', {
18
+ encoding: "utf8"
19
+ });
20
+ return output.trim().split("\n").filter(Boolean).map((line) => {
21
+ const [name, created, attached, windows] = line.split("|");
22
+ return {
23
+ name,
24
+ created: new Date(parseInt(created) * 1e3),
25
+ attached: attached === "1",
26
+ windows: parseInt(windows)
27
+ };
28
+ });
29
+ } catch {
30
+ return [];
31
+ }
32
+ }
33
+ function sessionExists(name) {
34
+ try {
35
+ execSync(`tmux has-session -t ${name} 2>/dev/null`);
36
+ return true;
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+ function createSession(name, cwd, initialCommand, options) {
42
+ const escapedCwd = cwd.replace(/"/g, '\\"');
43
+ let envFlags = "";
44
+ if (options?.env) {
45
+ for (const [key, value] of Object.entries(options.env)) {
46
+ envFlags += ` -e ${key}="${value.replace(/"/g, '\\"')}"`;
47
+ }
48
+ }
49
+ if (initialCommand && (initialCommand.includes("`") || initialCommand.includes("\n") || initialCommand.length > 500)) {
50
+ execSync(`tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags}`);
51
+ execSync("sleep 0.5");
52
+ const tmpFile = `/tmp/pan-cmd-${name}.sh`;
53
+ writeFileSync(tmpFile, initialCommand);
54
+ chmodSync(tmpFile, "755");
55
+ execSync(`tmux send-keys -t ${name} "bash ${tmpFile}"`);
56
+ execSync(`tmux send-keys -t ${name} C-m`);
57
+ } else if (initialCommand) {
58
+ const cmd = `tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags} "${initialCommand.replace(/"/g, '\\"')}"`;
59
+ execSync(cmd);
60
+ } else {
61
+ execSync(`tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags}`);
62
+ }
63
+ }
64
+ function killSession(name) {
65
+ execSync(`tmux kill-session -t ${name}`);
66
+ }
67
+ function sendKeys(sessionName, keys) {
68
+ const escapedKeys = keys.replace(/"/g, '\\"');
69
+ execSync(`tmux send-keys -t ${sessionName} "${escapedKeys}"`);
70
+ execSync(`tmux send-keys -t ${sessionName} C-m`);
71
+ }
72
+ function getAgentSessions() {
73
+ return listSessions().filter((s) => s.name.startsWith("agent-"));
74
+ }
75
+
76
+ // src/lib/hooks.ts
77
+ import { existsSync, mkdirSync, readFileSync, writeFileSync as writeFileSync2, readdirSync, unlinkSync } from "fs";
78
+ import { join } from "path";
79
+ function getHookDir(agentId) {
80
+ return join(AGENTS_DIR, agentId);
81
+ }
82
+ function getHookFile(agentId) {
83
+ return join(getHookDir(agentId), "hook.json");
84
+ }
85
+ function getMailDir(agentId) {
86
+ return join(getHookDir(agentId), "mail");
87
+ }
88
+ function initHook(agentId) {
89
+ const hookDir = getHookDir(agentId);
90
+ const mailDir = getMailDir(agentId);
91
+ mkdirSync(hookDir, { recursive: true });
92
+ mkdirSync(mailDir, { recursive: true });
93
+ const hookFile = getHookFile(agentId);
94
+ if (!existsSync(hookFile)) {
95
+ const hook = {
96
+ agentId,
97
+ items: []
98
+ };
99
+ writeFileSync2(hookFile, JSON.stringify(hook, null, 2));
100
+ }
101
+ }
102
+ function getHook(agentId) {
103
+ const hookFile = getHookFile(agentId);
104
+ if (!existsSync(hookFile)) {
105
+ return null;
106
+ }
107
+ try {
108
+ const content = readFileSync(hookFile, "utf-8");
109
+ return JSON.parse(content);
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+ function pushToHook(agentId, item) {
115
+ initHook(agentId);
116
+ const hook = getHook(agentId) || { agentId, items: [] };
117
+ const newItem = {
118
+ ...item,
119
+ id: `hook-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
120
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
121
+ };
122
+ hook.items.push(newItem);
123
+ writeFileSync2(getHookFile(agentId), JSON.stringify(hook, null, 2));
124
+ return newItem;
125
+ }
126
+ function checkHook(agentId) {
127
+ const hook = getHook(agentId);
128
+ if (!hook || hook.items.length === 0) {
129
+ const mailDir = getMailDir(agentId);
130
+ if (existsSync(mailDir)) {
131
+ const mails = readdirSync(mailDir).filter((f) => f.endsWith(".json"));
132
+ if (mails.length > 0) {
133
+ const mailItems = mails.map((file) => {
134
+ try {
135
+ const content = readFileSync(join(mailDir, file), "utf-8");
136
+ return JSON.parse(content);
137
+ } catch {
138
+ return null;
139
+ }
140
+ }).filter(Boolean);
141
+ return {
142
+ hasWork: mailItems.length > 0,
143
+ urgentCount: mailItems.filter((i) => i.priority === "urgent").length,
144
+ items: mailItems
145
+ };
146
+ }
147
+ }
148
+ return { hasWork: false, urgentCount: 0, items: [] };
149
+ }
150
+ const now = /* @__PURE__ */ new Date();
151
+ const activeItems = hook.items.filter((item) => {
152
+ if (item.expiresAt) {
153
+ return new Date(item.expiresAt) > now;
154
+ }
155
+ return true;
156
+ });
157
+ const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 };
158
+ activeItems.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
159
+ return {
160
+ hasWork: activeItems.length > 0,
161
+ urgentCount: activeItems.filter((i) => i.priority === "urgent").length,
162
+ items: activeItems
163
+ };
164
+ }
165
+ function popFromHook(agentId, itemId) {
166
+ const hook = getHook(agentId);
167
+ if (!hook) return false;
168
+ const index = hook.items.findIndex((i) => i.id === itemId);
169
+ if (index === -1) return false;
170
+ hook.items.splice(index, 1);
171
+ hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
172
+ writeFileSync2(getHookFile(agentId), JSON.stringify(hook, null, 2));
173
+ return true;
174
+ }
175
+ function clearHook(agentId) {
176
+ const hook = getHook(agentId);
177
+ if (!hook) return;
178
+ hook.items = [];
179
+ hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
180
+ writeFileSync2(getHookFile(agentId), JSON.stringify(hook, null, 2));
181
+ }
182
+ function sendMail(toAgentId, from, message, priority = "normal") {
183
+ initHook(toAgentId);
184
+ const mailDir = getMailDir(toAgentId);
185
+ const mailItem = {
186
+ id: `mail-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
187
+ type: "message",
188
+ priority,
189
+ source: from,
190
+ payload: { message },
191
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
192
+ };
193
+ writeFileSync2(
194
+ join(mailDir, `${mailItem.id}.json`),
195
+ JSON.stringify(mailItem, null, 2)
196
+ );
197
+ }
198
+ function generateFixedPointPrompt(agentId) {
199
+ const { hasWork, urgentCount, items } = checkHook(agentId);
200
+ if (!hasWork) return null;
201
+ const lines = [
202
+ "# FPP: Work Found on Your Hook",
203
+ "",
204
+ '> "Any runnable action is a fixed point and must resolve before the system can rest."',
205
+ ""
206
+ ];
207
+ if (urgentCount > 0) {
208
+ lines.push(`\u26A0\uFE0F **${urgentCount} URGENT item(s) require immediate attention**`);
209
+ lines.push("");
210
+ }
211
+ lines.push(`## Pending Work Items (${items.length})`);
212
+ lines.push("");
213
+ for (const item of items) {
214
+ const priorityEmoji = {
215
+ urgent: "\u{1F534}",
216
+ high: "\u{1F7E0}",
217
+ normal: "\u{1F7E2}",
218
+ low: "\u26AA"
219
+ }[item.priority];
220
+ lines.push(`### ${priorityEmoji} ${item.type.toUpperCase()}: ${item.id}`);
221
+ lines.push(`- Source: ${item.source}`);
222
+ lines.push(`- Created: ${item.createdAt}`);
223
+ if (item.payload.issueId) {
224
+ lines.push(`- Issue: ${item.payload.issueId}`);
225
+ }
226
+ if (item.payload.message) {
227
+ lines.push(`- Message: ${item.payload.message}`);
228
+ }
229
+ if (item.payload.action) {
230
+ lines.push(`- Action: ${item.payload.action}`);
231
+ }
232
+ lines.push("");
233
+ }
234
+ lines.push("---");
235
+ lines.push("");
236
+ lines.push("Execute these items in priority order. Use `bd hook pop <id>` after completing each item.");
237
+ return lines.join("\n");
238
+ }
239
+
240
+ // src/lib/cv.ts
241
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3, readdirSync as readdirSync2 } from "fs";
242
+ import { join as join2 } from "path";
243
+ function getCVFile(agentId) {
244
+ return join2(AGENTS_DIR, agentId, "cv.json");
245
+ }
246
+ function getAgentCV(agentId) {
247
+ const cvFile = getCVFile(agentId);
248
+ if (existsSync2(cvFile)) {
249
+ try {
250
+ return JSON.parse(readFileSync2(cvFile, "utf-8"));
251
+ } catch {
252
+ }
253
+ }
254
+ const cv = {
255
+ agentId,
256
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
257
+ lastActive: (/* @__PURE__ */ new Date()).toISOString(),
258
+ runtime: "claude",
259
+ model: "sonnet",
260
+ stats: {
261
+ totalIssues: 0,
262
+ successCount: 0,
263
+ failureCount: 0,
264
+ abandonedCount: 0,
265
+ avgDuration: 0,
266
+ successRate: 0
267
+ },
268
+ skillsUsed: [],
269
+ recentWork: []
270
+ };
271
+ saveAgentCV(cv);
272
+ return cv;
273
+ }
274
+ function saveAgentCV(cv) {
275
+ const dir = join2(AGENTS_DIR, cv.agentId);
276
+ mkdirSync2(dir, { recursive: true });
277
+ writeFileSync3(getCVFile(cv.agentId), JSON.stringify(cv, null, 2));
278
+ }
279
+ function startWork(agentId, issueId, skills) {
280
+ const cv = getAgentCV(agentId);
281
+ const entry = {
282
+ issueId,
283
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
284
+ outcome: "in_progress",
285
+ skills
286
+ };
287
+ cv.recentWork.unshift(entry);
288
+ cv.stats.totalIssues++;
289
+ cv.lastActive = (/* @__PURE__ */ new Date()).toISOString();
290
+ if (skills) {
291
+ for (const skill of skills) {
292
+ if (!cv.skillsUsed.includes(skill)) {
293
+ cv.skillsUsed.push(skill);
294
+ }
295
+ }
296
+ }
297
+ if (cv.recentWork.length > 50) {
298
+ cv.recentWork = cv.recentWork.slice(0, 50);
299
+ }
300
+ saveAgentCV(cv);
301
+ }
302
+ function getAgentRankings() {
303
+ const rankings = [];
304
+ if (!existsSync2(AGENTS_DIR)) return rankings;
305
+ const dirs = readdirSync2(AGENTS_DIR, { withFileTypes: true }).filter(
306
+ (d) => d.isDirectory()
307
+ );
308
+ for (const dir of dirs) {
309
+ const cv = getAgentCV(dir.name);
310
+ if (cv.stats.totalIssues > 0) {
311
+ rankings.push({
312
+ agentId: dir.name,
313
+ successRate: cv.stats.successRate,
314
+ totalIssues: cv.stats.totalIssues,
315
+ avgDuration: cv.stats.avgDuration
316
+ });
317
+ }
318
+ }
319
+ rankings.sort((a, b) => {
320
+ if (b.successRate !== a.successRate) {
321
+ return b.successRate - a.successRate;
322
+ }
323
+ return b.totalIssues - a.totalIssues;
324
+ });
325
+ return rankings;
326
+ }
327
+ function formatCV(cv) {
328
+ const lines = [
329
+ `# Agent CV: ${cv.agentId}`,
330
+ "",
331
+ `Runtime: ${cv.runtime} (${cv.model})`,
332
+ `Created: ${cv.createdAt}`,
333
+ `Last Active: ${cv.lastActive}`,
334
+ "",
335
+ "## Statistics",
336
+ "",
337
+ `- Total Issues: ${cv.stats.totalIssues}`,
338
+ `- Success Rate: ${(cv.stats.successRate * 100).toFixed(1)}%`,
339
+ `- Successes: ${cv.stats.successCount}`,
340
+ `- Failures: ${cv.stats.failureCount}`,
341
+ `- Abandoned: ${cv.stats.abandonedCount}`,
342
+ `- Avg Duration: ${cv.stats.avgDuration} minutes`,
343
+ ""
344
+ ];
345
+ if (cv.skillsUsed.length > 0) {
346
+ lines.push("## Skills Used");
347
+ lines.push("");
348
+ lines.push(cv.skillsUsed.join(", "));
349
+ lines.push("");
350
+ }
351
+ if (cv.recentWork.length > 0) {
352
+ lines.push("## Recent Work");
353
+ lines.push("");
354
+ for (const work of cv.recentWork.slice(0, 10)) {
355
+ const statusIcon = {
356
+ success: "\u2713",
357
+ failed: "\u2717",
358
+ abandoned: "\u2298",
359
+ in_progress: "\u25CF"
360
+ }[work.outcome];
361
+ const duration = work.duration ? ` (${work.duration}m)` : "";
362
+ lines.push(`${statusIcon} ${work.issueId}${duration}`);
363
+ if (work.failureReason) {
364
+ lines.push(` Reason: ${work.failureReason}`);
365
+ }
366
+ }
367
+ lines.push("");
368
+ }
369
+ return lines.join("\n");
370
+ }
371
+
372
+ // src/lib/agents.ts
373
+ var execAsync = promisify(exec);
374
+ function getReadySignalPath(agentId) {
375
+ return join3(getAgentDir(agentId), "ready.json");
376
+ }
377
+ function clearReadySignal(agentId) {
378
+ const readyPath = getReadySignalPath(agentId);
379
+ if (existsSync3(readyPath)) {
380
+ try {
381
+ unlinkSync2(readyPath);
382
+ } catch {
383
+ }
384
+ }
385
+ }
386
+ async function waitForReadySignal(agentId, timeoutSeconds = 30) {
387
+ const readyPath = getReadySignalPath(agentId);
388
+ for (let i = 0; i < timeoutSeconds; i++) {
389
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
390
+ if (existsSync3(readyPath)) {
391
+ try {
392
+ const content = readFileSync3(readyPath, "utf-8");
393
+ const signal = JSON.parse(content);
394
+ if (signal.ready === true) {
395
+ return true;
396
+ }
397
+ } catch {
398
+ }
399
+ }
400
+ }
401
+ return false;
402
+ }
403
+ function getAgentDir(agentId) {
404
+ return join3(AGENTS_DIR, agentId);
405
+ }
406
+ function getAgentState(agentId) {
407
+ const stateFile = join3(getAgentDir(agentId), "state.json");
408
+ if (!existsSync3(stateFile)) return null;
409
+ const content = readFileSync3(stateFile, "utf8");
410
+ return JSON.parse(content);
411
+ }
412
+ function saveAgentState(state) {
413
+ const dir = getAgentDir(state.id);
414
+ mkdirSync3(dir, { recursive: true });
415
+ writeFileSync4(
416
+ join3(dir, "state.json"),
417
+ JSON.stringify(state, null, 2)
418
+ );
419
+ }
420
+ function getAgentRuntimeState(agentId) {
421
+ const stateFile = join3(getAgentDir(agentId), "state.json");
422
+ if (!existsSync3(stateFile)) {
423
+ return {
424
+ state: "uninitialized",
425
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString()
426
+ };
427
+ }
428
+ try {
429
+ const content = readFileSync3(stateFile, "utf8");
430
+ return JSON.parse(content);
431
+ } catch {
432
+ return null;
433
+ }
434
+ }
435
+ function saveAgentRuntimeState(agentId, state) {
436
+ const dir = getAgentDir(agentId);
437
+ mkdirSync3(dir, { recursive: true });
438
+ const existing = getAgentRuntimeState(agentId);
439
+ const merged = {
440
+ ...existing || { state: "uninitialized", lastActivity: (/* @__PURE__ */ new Date()).toISOString() },
441
+ ...state
442
+ };
443
+ writeFileSync4(
444
+ join3(dir, "state.json"),
445
+ JSON.stringify(merged, null, 2)
446
+ );
447
+ }
448
+ function appendActivity(agentId, entry) {
449
+ const dir = getAgentDir(agentId);
450
+ mkdirSync3(dir, { recursive: true });
451
+ const activityFile = join3(dir, "activity.jsonl");
452
+ appendFileSync(activityFile, JSON.stringify(entry) + "\n");
453
+ if (existsSync3(activityFile)) {
454
+ try {
455
+ const lines = readFileSync3(activityFile, "utf8").trim().split("\n");
456
+ if (lines.length > 100) {
457
+ const trimmed = lines.slice(-100);
458
+ writeFileSync4(activityFile, trimmed.join("\n") + "\n");
459
+ }
460
+ } catch (error) {
461
+ }
462
+ }
463
+ }
464
+ function getActivity(agentId, limit = 100) {
465
+ const activityFile = join3(getAgentDir(agentId), "activity.jsonl");
466
+ if (!existsSync3(activityFile)) {
467
+ return [];
468
+ }
469
+ try {
470
+ const lines = readFileSync3(activityFile, "utf8").trim().split("\n");
471
+ const entries = lines.filter((line) => line.trim()).map((line) => JSON.parse(line)).slice(-limit);
472
+ return entries;
473
+ } catch {
474
+ return [];
475
+ }
476
+ }
477
+ function saveSessionId(agentId, sessionId) {
478
+ const dir = getAgentDir(agentId);
479
+ mkdirSync3(dir, { recursive: true });
480
+ writeFileSync4(join3(dir, "session.id"), sessionId);
481
+ }
482
+ function getSessionId(agentId) {
483
+ const sessionFile = join3(getAgentDir(agentId), "session.id");
484
+ if (!existsSync3(sessionFile)) {
485
+ return null;
486
+ }
487
+ try {
488
+ return readFileSync3(sessionFile, "utf8").trim();
489
+ } catch {
490
+ return null;
491
+ }
492
+ }
493
+ async function spawnAgent(options) {
494
+ const agentId = `agent-${options.issueId.toLowerCase()}`;
495
+ if (sessionExists(agentId)) {
496
+ throw new Error(`Agent ${agentId} already running. Use 'pan work tell' to message it.`);
497
+ }
498
+ initHook(agentId);
499
+ const state = {
500
+ id: agentId,
501
+ issueId: options.issueId,
502
+ workspace: options.workspace,
503
+ runtime: options.runtime || "claude",
504
+ model: options.model || "sonnet",
505
+ status: "starting",
506
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
507
+ // Initialize Phase 4 fields
508
+ complexity: options.difficulty,
509
+ handoffCount: 0,
510
+ costSoFar: 0
511
+ };
512
+ saveAgentState(state);
513
+ let prompt = options.prompt || "";
514
+ const { hasWork, items } = checkHook(agentId);
515
+ if (hasWork) {
516
+ const fixedPointPrompt = generateFixedPointPrompt(agentId);
517
+ if (fixedPointPrompt) {
518
+ prompt = fixedPointPrompt + "\n\n---\n\n" + prompt;
519
+ }
520
+ }
521
+ const promptFile = join3(getAgentDir(agentId), "initial-prompt.md");
522
+ if (prompt) {
523
+ writeFileSync4(promptFile, prompt);
524
+ }
525
+ checkAndSetupHooks();
526
+ writeTaskCache(agentId, options.issueId);
527
+ clearReadySignal(agentId);
528
+ let claudeCmd;
529
+ if (prompt) {
530
+ const launcherScript = join3(getAgentDir(agentId), "launcher.sh");
531
+ const launcherContent = `#!/bin/bash
532
+ prompt=$(cat "${promptFile}")
533
+ exec claude --dangerously-skip-permissions --model ${state.model} "$prompt"
534
+ `;
535
+ writeFileSync4(launcherScript, launcherContent, { mode: 493 });
536
+ claudeCmd = `bash "${launcherScript}"`;
537
+ } else {
538
+ claudeCmd = `claude --dangerously-skip-permissions --model ${state.model}`;
539
+ }
540
+ createSession(agentId, options.workspace, claudeCmd, {
541
+ env: {
542
+ PANOPTICON_AGENT_ID: agentId
543
+ }
544
+ });
545
+ state.status = "running";
546
+ saveAgentState(state);
547
+ startWork(agentId, options.issueId);
548
+ return state;
549
+ }
550
+ function listRunningAgents() {
551
+ const tmuxSessions = getAgentSessions();
552
+ const tmuxNames = new Set(tmuxSessions.map((s) => s.name));
553
+ const agents = [];
554
+ if (!existsSync3(AGENTS_DIR)) return agents;
555
+ const dirs = readdirSync3(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
556
+ for (const dir of dirs) {
557
+ const state = getAgentState(dir.name);
558
+ if (state) {
559
+ agents.push({
560
+ ...state,
561
+ tmuxActive: tmuxNames.has(state.id)
562
+ });
563
+ }
564
+ }
565
+ return agents;
566
+ }
567
+ function stopAgent(agentId) {
568
+ const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
569
+ if (sessionExists(normalizedId)) {
570
+ killSession(normalizedId);
571
+ }
572
+ const state = getAgentState(normalizedId);
573
+ if (state) {
574
+ state.status = "stopped";
575
+ saveAgentState(state);
576
+ }
577
+ }
578
+ async function messageAgent(agentId, message) {
579
+ const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
580
+ const runtimeState = getAgentRuntimeState(normalizedId);
581
+ if (runtimeState?.state === "suspended") {
582
+ console.log(`[agents] Auto-resuming suspended agent ${normalizedId} to deliver message`);
583
+ const result = await resumeAgent(normalizedId, message);
584
+ if (!result.success) {
585
+ throw new Error(`Failed to auto-resume agent: ${result.error}`);
586
+ }
587
+ return;
588
+ }
589
+ if (!sessionExists(normalizedId)) {
590
+ throw new Error(`Agent ${normalizedId} not running`);
591
+ }
592
+ sendKeys(normalizedId, message);
593
+ const mailDir = join3(getAgentDir(normalizedId), "mail");
594
+ mkdirSync3(mailDir, { recursive: true });
595
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
596
+ writeFileSync4(
597
+ join3(mailDir, `${timestamp}.md`),
598
+ `# Message
599
+
600
+ ${message}
601
+ `
602
+ );
603
+ }
604
+ async function resumeAgent(agentId, message) {
605
+ const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
606
+ const runtimeState = getAgentRuntimeState(normalizedId);
607
+ if (!runtimeState || runtimeState.state !== "suspended") {
608
+ return {
609
+ success: false,
610
+ error: `Cannot resume agent in state: ${runtimeState?.state || "unknown"}`
611
+ };
612
+ }
613
+ const sessionId = getSessionId(normalizedId);
614
+ if (!sessionId) {
615
+ return {
616
+ success: false,
617
+ error: "No saved session ID found"
618
+ };
619
+ }
620
+ const agentState = getAgentState(normalizedId);
621
+ if (!agentState) {
622
+ return {
623
+ success: false,
624
+ error: "Agent state not found"
625
+ };
626
+ }
627
+ if (sessionExists(normalizedId)) {
628
+ return {
629
+ success: false,
630
+ error: "Agent session already exists"
631
+ };
632
+ }
633
+ try {
634
+ clearReadySignal(normalizedId);
635
+ const claudeCmd = `claude --resume "${sessionId}" --dangerously-skip-permissions`;
636
+ createSession(normalizedId, agentState.workspace, claudeCmd, {
637
+ env: {
638
+ PANOPTICON_AGENT_ID: normalizedId
639
+ }
640
+ });
641
+ if (message) {
642
+ const ready = await waitForReadySignal(normalizedId, 30);
643
+ if (ready) {
644
+ sendKeys(normalizedId, message);
645
+ } else {
646
+ console.error("Claude SessionStart hook did not fire during resume, message not sent");
647
+ }
648
+ }
649
+ saveAgentRuntimeState(normalizedId, {
650
+ state: "active",
651
+ resumedAt: (/* @__PURE__ */ new Date()).toISOString()
652
+ });
653
+ if (agentState) {
654
+ agentState.status = "running";
655
+ agentState.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
656
+ saveAgentState(agentState);
657
+ }
658
+ return { success: true };
659
+ } catch (error) {
660
+ const msg = error instanceof Error ? error.message : String(error);
661
+ return {
662
+ success: false,
663
+ error: `Failed to resume agent: ${msg}`
664
+ };
665
+ }
666
+ }
667
+ function detectCrashedAgents() {
668
+ const agents = listRunningAgents();
669
+ return agents.filter(
670
+ (agent) => agent.status === "running" && !agent.tmuxActive
671
+ );
672
+ }
673
+ function recoverAgent(agentId) {
674
+ const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
675
+ const state = getAgentState(normalizedId);
676
+ if (!state) {
677
+ return null;
678
+ }
679
+ if (sessionExists(normalizedId)) {
680
+ return state;
681
+ }
682
+ const healthFile = join3(getAgentDir(normalizedId), "health.json");
683
+ let health = { consecutiveFailures: 0, killCount: 0, recoveryCount: 0 };
684
+ if (existsSync3(healthFile)) {
685
+ try {
686
+ health = { ...health, ...JSON.parse(readFileSync3(healthFile, "utf-8")) };
687
+ } catch {
688
+ }
689
+ }
690
+ health.recoveryCount = (health.recoveryCount || 0) + 1;
691
+ writeFileSync4(healthFile, JSON.stringify(health, null, 2));
692
+ const recoveryPrompt = generateRecoveryPrompt(state);
693
+ const claudeCmd = `claude --dangerously-skip-permissions --model ${state.model} "${recoveryPrompt.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
694
+ createSession(normalizedId, state.workspace, claudeCmd);
695
+ state.status = "running";
696
+ state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
697
+ saveAgentState(state);
698
+ return state;
699
+ }
700
+ function generateRecoveryPrompt(state) {
701
+ const lines = [
702
+ "# Agent Recovery",
703
+ "",
704
+ "\u26A0\uFE0F This agent session was recovered after a crash.",
705
+ "",
706
+ "## Previous Context",
707
+ `- Issue: ${state.issueId}`,
708
+ `- Workspace: ${state.workspace}`,
709
+ `- Started: ${state.startedAt}`,
710
+ "",
711
+ "## Recovery Steps",
712
+ "1. Check beads for context: `bd show " + state.issueId + "`",
713
+ "2. Review recent git commits: `git log --oneline -10`",
714
+ "3. Check hook for pending work: `pan work hook check`",
715
+ "4. Resume from last known state",
716
+ "",
717
+ "## FPP Reminder",
718
+ '> "Any runnable action is a fixed point and must resolve before the system can rest."',
719
+ ""
720
+ ];
721
+ const { hasWork } = checkHook(state.id);
722
+ if (hasWork) {
723
+ const fixedPointPrompt = generateFixedPointPrompt(state.id);
724
+ if (fixedPointPrompt) {
725
+ lines.push("---");
726
+ lines.push("");
727
+ lines.push(fixedPointPrompt);
728
+ }
729
+ }
730
+ return lines.join("\n");
731
+ }
732
+ function autoRecoverAgents() {
733
+ const crashed = detectCrashedAgents();
734
+ const recovered = [];
735
+ const failed = [];
736
+ for (const agent of crashed) {
737
+ try {
738
+ const result = recoverAgent(agent.id);
739
+ if (result) {
740
+ recovered.push(agent.id);
741
+ } else {
742
+ failed.push(agent.id);
743
+ }
744
+ } catch (error) {
745
+ failed.push(agent.id);
746
+ }
747
+ }
748
+ return { recovered, failed };
749
+ }
750
+ function checkAndSetupHooks() {
751
+ const settingsPath = join3(homedir(), ".claude", "settings.json");
752
+ const hookPath = join3(homedir(), ".panopticon", "bin", "heartbeat-hook");
753
+ if (existsSync3(settingsPath)) {
754
+ try {
755
+ const settingsContent = readFileSync3(settingsPath, "utf-8");
756
+ const settings = JSON.parse(settingsContent);
757
+ const postToolUse = settings?.hooks?.PostToolUse || [];
758
+ const hookConfigured = postToolUse.some(
759
+ (hookConfig) => hookConfig.hooks?.some(
760
+ (hook) => hook.command === hookPath || hook.command?.includes("panopticon") || hook.command?.includes("heartbeat-hook")
761
+ )
762
+ );
763
+ if (hookConfigured) {
764
+ return;
765
+ }
766
+ } catch {
767
+ }
768
+ }
769
+ try {
770
+ console.log("Configuring Panopticon heartbeat hooks...");
771
+ exec("pan setup hooks", (error) => {
772
+ if (error) {
773
+ console.warn("\u26A0 Failed to auto-configure hooks. Run `pan setup hooks` manually.");
774
+ } else {
775
+ console.log("\u2713 Heartbeat hooks configured");
776
+ }
777
+ });
778
+ } catch (error) {
779
+ console.warn("\u26A0 Failed to auto-configure hooks. Run `pan setup hooks` manually.");
780
+ }
781
+ }
782
+ function writeTaskCache(agentId, issueId) {
783
+ const cacheDir = join3(getAgentDir(agentId));
784
+ mkdirSync3(cacheDir, { recursive: true });
785
+ const cacheFile = join3(cacheDir, "current-task.json");
786
+ writeFileSync4(
787
+ cacheFile,
788
+ JSON.stringify({
789
+ id: issueId,
790
+ title: `Working on ${issueId}`,
791
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
792
+ }, null, 2)
793
+ );
794
+ }
795
+
796
+ export {
797
+ sessionExists,
798
+ killSession,
799
+ sendKeys,
800
+ pushToHook,
801
+ checkHook,
802
+ popFromHook,
803
+ clearHook,
804
+ sendMail,
805
+ generateFixedPointPrompt,
806
+ getAgentCV,
807
+ getAgentRankings,
808
+ formatCV,
809
+ getAgentDir,
810
+ getAgentState,
811
+ saveAgentState,
812
+ getAgentRuntimeState,
813
+ saveAgentRuntimeState,
814
+ appendActivity,
815
+ getActivity,
816
+ saveSessionId,
817
+ getSessionId,
818
+ spawnAgent,
819
+ listRunningAgents,
820
+ stopAgent,
821
+ messageAgent,
822
+ resumeAgent,
823
+ detectCrashedAgents,
824
+ recoverAgent,
825
+ autoRecoverAgents
826
+ };
827
+ //# sourceMappingURL=chunk-4BIZ4OVN.js.map