panopticon-cli 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +84 -2695
  2. package/dist/{agents-B5NRTVHK.js → agents-54LDKMHR.js} +8 -3
  3. package/dist/chunk-44EOY2ZL.js +58 -0
  4. package/dist/chunk-44EOY2ZL.js.map +1 -0
  5. package/dist/chunk-BWGFN44T.js +224 -0
  6. package/dist/chunk-BWGFN44T.js.map +1 -0
  7. package/dist/chunk-F7NQZD6H.js +49 -0
  8. package/dist/chunk-F7NQZD6H.js.map +1 -0
  9. package/dist/chunk-HCTJFIJJ.js +159 -0
  10. package/dist/chunk-HCTJFIJJ.js.map +1 -0
  11. package/dist/chunk-JM6V62LT.js +650 -0
  12. package/dist/chunk-JM6V62LT.js.map +1 -0
  13. package/dist/chunk-K45YD6A3.js +254 -0
  14. package/dist/chunk-K45YD6A3.js.map +1 -0
  15. package/dist/chunk-KGPRXDMX.js +137 -0
  16. package/dist/chunk-KGPRXDMX.js.map +1 -0
  17. package/dist/chunk-KQAEUOML.js +278 -0
  18. package/dist/chunk-KQAEUOML.js.map +1 -0
  19. package/dist/chunk-NYVQC3D7.js +90 -0
  20. package/dist/chunk-NYVQC3D7.js.map +1 -0
  21. package/dist/chunk-PUR532O7.js +1556 -0
  22. package/dist/chunk-PUR532O7.js.map +1 -0
  23. package/dist/chunk-VTDDVLCK.js +1977 -0
  24. package/dist/chunk-VTDDVLCK.js.map +1 -0
  25. package/dist/chunk-Z24TY3XN.js +916 -0
  26. package/dist/chunk-Z24TY3XN.js.map +1 -0
  27. package/dist/chunk-ZHC57RCV.js +44 -0
  28. package/dist/chunk-ZHC57RCV.js.map +1 -0
  29. package/dist/{chunk-ITI4IC5A.js → chunk-ZZ3477GY.js} +69 -100
  30. package/dist/chunk-ZZ3477GY.js.map +1 -0
  31. package/dist/cli/index.js +4664 -2912
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/dashboard/public/assets/index-CRqsEkmn.css +32 -0
  34. package/dist/dashboard/public/assets/index-DPSUbu4A.js +645 -0
  35. package/dist/dashboard/public/index.html +15 -3
  36. package/dist/dashboard/server.js +45663 -17860
  37. package/dist/dns-L3L2BB27.js +30 -0
  38. package/dist/dns-L3L2BB27.js.map +1 -0
  39. package/dist/index.d.ts +63 -3
  40. package/dist/index.js +42 -18
  41. package/dist/index.js.map +1 -1
  42. package/dist/projects-ESIB34QQ.js +43 -0
  43. package/dist/projects-ESIB34QQ.js.map +1 -0
  44. package/dist/remote-agents-Z3R2A5BN.js +25 -0
  45. package/dist/remote-agents-Z3R2A5BN.js.map +1 -0
  46. package/dist/remote-workspace-HI4VML6H.js +179 -0
  47. package/dist/remote-workspace-HI4VML6H.js.map +1 -0
  48. package/dist/specialist-context-SNCJ7O7G.js +256 -0
  49. package/dist/specialist-context-SNCJ7O7G.js.map +1 -0
  50. package/dist/specialist-logs-A7ODEK2T.js +43 -0
  51. package/dist/specialist-logs-A7ODEK2T.js.map +1 -0
  52. package/dist/specialists-C7XLNSXQ.js +121 -0
  53. package/dist/specialists-C7XLNSXQ.js.map +1 -0
  54. package/dist/traefik-WI3KSRGG.js +12 -0
  55. package/dist/traefik-WI3KSRGG.js.map +1 -0
  56. package/package.json +1 -1
  57. package/templates/traefik/docker-compose.yml +1 -1
  58. package/templates/traefik/dynamic/panopticon.yml.template +41 -0
  59. package/templates/traefik/traefik.yml +8 -0
  60. package/dist/chunk-7HHDVXBM.js +0 -349
  61. package/dist/chunk-7HHDVXBM.js.map +0 -1
  62. package/dist/chunk-H45CLB7E.js +0 -2044
  63. package/dist/chunk-H45CLB7E.js.map +0 -1
  64. package/dist/chunk-ITI4IC5A.js.map +0 -1
  65. package/dist/dashboard/public/assets/index-BDd8hGYb.css +0 -32
  66. package/dist/dashboard/public/assets/index-sFwLPko-.js +0 -556
  67. package/templates/traefik/dynamic/panopticon.yml +0 -51
  68. /package/dist/{agents-B5NRTVHK.js.map → agents-54LDKMHR.js.map} +0 -0
@@ -0,0 +1,916 @@
1
+ import {
2
+ capturePane,
3
+ checkHook,
4
+ createSession,
5
+ generateFixedPointPrompt,
6
+ getAgentSessions,
7
+ getModelId,
8
+ initHook,
9
+ init_hooks,
10
+ init_tmux,
11
+ init_work_type_router,
12
+ killSession,
13
+ sendKeys,
14
+ sessionExists
15
+ } from "./chunk-PUR532O7.js";
16
+ import {
17
+ getProviderEnv,
18
+ getProviderForModel,
19
+ init_providers,
20
+ init_settings,
21
+ loadSettings
22
+ } from "./chunk-KQAEUOML.js";
23
+ import {
24
+ AGENTS_DIR,
25
+ PANOPTICON_HOME,
26
+ init_paths
27
+ } from "./chunk-KGPRXDMX.js";
28
+ import {
29
+ __esm,
30
+ init_esm_shims
31
+ } from "./chunk-ZHC57RCV.js";
32
+
33
+ // src/lib/cv.ts
34
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from "fs";
35
+ import { join } from "path";
36
+ function getCVFile(agentId) {
37
+ return join(AGENTS_DIR, agentId, "cv.json");
38
+ }
39
+ function getAgentCV(agentId) {
40
+ const cvFile = getCVFile(agentId);
41
+ if (existsSync(cvFile)) {
42
+ try {
43
+ return JSON.parse(readFileSync(cvFile, "utf-8"));
44
+ } catch {
45
+ }
46
+ }
47
+ const cv = {
48
+ agentId,
49
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
50
+ lastActive: (/* @__PURE__ */ new Date()).toISOString(),
51
+ runtime: "claude",
52
+ model: "sonnet",
53
+ stats: {
54
+ totalIssues: 0,
55
+ successCount: 0,
56
+ failureCount: 0,
57
+ abandonedCount: 0,
58
+ avgDuration: 0,
59
+ successRate: 0
60
+ },
61
+ skillsUsed: [],
62
+ recentWork: []
63
+ };
64
+ saveAgentCV(cv);
65
+ return cv;
66
+ }
67
+ function saveAgentCV(cv) {
68
+ const dir = join(AGENTS_DIR, cv.agentId);
69
+ mkdirSync(dir, { recursive: true });
70
+ writeFileSync(getCVFile(cv.agentId), JSON.stringify(cv, null, 2));
71
+ }
72
+ function startWork(agentId, issueId, skills) {
73
+ const cv = getAgentCV(agentId);
74
+ const entry = {
75
+ issueId,
76
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
77
+ outcome: "in_progress",
78
+ skills
79
+ };
80
+ cv.recentWork.unshift(entry);
81
+ cv.stats.totalIssues++;
82
+ cv.lastActive = (/* @__PURE__ */ new Date()).toISOString();
83
+ if (skills) {
84
+ for (const skill of skills) {
85
+ if (!cv.skillsUsed.includes(skill)) {
86
+ cv.skillsUsed.push(skill);
87
+ }
88
+ }
89
+ }
90
+ if (cv.recentWork.length > 50) {
91
+ cv.recentWork = cv.recentWork.slice(0, 50);
92
+ }
93
+ saveAgentCV(cv);
94
+ }
95
+ function getAgentRankings() {
96
+ const rankings = [];
97
+ if (!existsSync(AGENTS_DIR)) return rankings;
98
+ const dirs = readdirSync(AGENTS_DIR, { withFileTypes: true }).filter(
99
+ (d) => d.isDirectory()
100
+ );
101
+ for (const dir of dirs) {
102
+ const cv = getAgentCV(dir.name);
103
+ if (cv.stats.totalIssues > 0) {
104
+ rankings.push({
105
+ agentId: dir.name,
106
+ successRate: cv.stats.successRate,
107
+ totalIssues: cv.stats.totalIssues,
108
+ avgDuration: cv.stats.avgDuration
109
+ });
110
+ }
111
+ }
112
+ rankings.sort((a, b) => {
113
+ if (b.successRate !== a.successRate) {
114
+ return b.successRate - a.successRate;
115
+ }
116
+ return b.totalIssues - a.totalIssues;
117
+ });
118
+ return rankings;
119
+ }
120
+ function formatCV(cv) {
121
+ const lines = [
122
+ `# Agent CV: ${cv.agentId}`,
123
+ "",
124
+ `Runtime: ${cv.runtime} (${cv.model})`,
125
+ `Created: ${cv.createdAt}`,
126
+ `Last Active: ${cv.lastActive}`,
127
+ "",
128
+ "## Statistics",
129
+ "",
130
+ `- Total Issues: ${cv.stats.totalIssues}`,
131
+ `- Success Rate: ${(cv.stats.successRate * 100).toFixed(1)}%`,
132
+ `- Successes: ${cv.stats.successCount}`,
133
+ `- Failures: ${cv.stats.failureCount}`,
134
+ `- Abandoned: ${cv.stats.abandonedCount}`,
135
+ `- Avg Duration: ${cv.stats.avgDuration} minutes`,
136
+ ""
137
+ ];
138
+ if (cv.skillsUsed.length > 0) {
139
+ lines.push("## Skills Used");
140
+ lines.push("");
141
+ lines.push(cv.skillsUsed.join(", "));
142
+ lines.push("");
143
+ }
144
+ if (cv.recentWork.length > 0) {
145
+ lines.push("## Recent Work");
146
+ lines.push("");
147
+ for (const work of cv.recentWork.slice(0, 10)) {
148
+ const statusIcon = {
149
+ success: "\u2713",
150
+ failed: "\u2717",
151
+ abandoned: "\u2298",
152
+ in_progress: "\u25CF"
153
+ }[work.outcome];
154
+ const duration = work.duration ? ` (${work.duration}m)` : "";
155
+ lines.push(`${statusIcon} ${work.issueId}${duration}`);
156
+ if (work.failureReason) {
157
+ lines.push(` Reason: ${work.failureReason}`);
158
+ }
159
+ }
160
+ lines.push("");
161
+ }
162
+ return lines.join("\n");
163
+ }
164
+ var init_cv = __esm({
165
+ "src/lib/cv.ts"() {
166
+ "use strict";
167
+ init_esm_shims();
168
+ init_paths();
169
+ }
170
+ });
171
+
172
+ // src/lib/cloister/config.ts
173
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
174
+ import { parse, stringify } from "@iarna/toml";
175
+ import { join as join2 } from "path";
176
+ function deepMerge(defaults, overrides) {
177
+ const result = { ...defaults };
178
+ for (const key of Object.keys(overrides)) {
179
+ const defaultVal = defaults[key];
180
+ const overrideVal = overrides[key];
181
+ if (overrideVal === void 0) continue;
182
+ if (typeof defaultVal === "object" && defaultVal !== null && !Array.isArray(defaultVal) && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(overrideVal)) {
183
+ result[key] = deepMerge(defaultVal, overrideVal);
184
+ } else {
185
+ result[key] = overrideVal;
186
+ }
187
+ }
188
+ return result;
189
+ }
190
+ function loadCloisterConfig() {
191
+ if (!existsSync2(PANOPTICON_HOME)) {
192
+ mkdirSync2(PANOPTICON_HOME, { recursive: true });
193
+ }
194
+ if (!existsSync2(CLOISTER_CONFIG_FILE)) {
195
+ saveCloisterConfig(DEFAULT_CLOISTER_CONFIG);
196
+ return DEFAULT_CLOISTER_CONFIG;
197
+ }
198
+ try {
199
+ const content = readFileSync2(CLOISTER_CONFIG_FILE, "utf-8");
200
+ const parsed = parse(content);
201
+ return deepMerge(DEFAULT_CLOISTER_CONFIG, parsed);
202
+ } catch (error) {
203
+ console.error("Failed to load Cloister config:", error);
204
+ console.error("Using default configuration");
205
+ return DEFAULT_CLOISTER_CONFIG;
206
+ }
207
+ }
208
+ function saveCloisterConfig(config) {
209
+ if (!existsSync2(PANOPTICON_HOME)) {
210
+ mkdirSync2(PANOPTICON_HOME, { recursive: true });
211
+ }
212
+ try {
213
+ const content = stringify(config);
214
+ writeFileSync2(CLOISTER_CONFIG_FILE, content, "utf-8");
215
+ } catch (error) {
216
+ console.error("Failed to save Cloister config:", error);
217
+ throw error;
218
+ }
219
+ }
220
+ function getHealthThresholdsMs() {
221
+ const config = loadCloisterConfig();
222
+ return {
223
+ stale: config.thresholds.stale * 60 * 1e3,
224
+ warning: config.thresholds.warning * 60 * 1e3,
225
+ stuck: config.thresholds.stuck * 60 * 1e3
226
+ };
227
+ }
228
+ var CLOISTER_CONFIG_FILE, DEFAULT_CLOISTER_CONFIG;
229
+ var init_config = __esm({
230
+ "src/lib/cloister/config.ts"() {
231
+ "use strict";
232
+ init_esm_shims();
233
+ init_paths();
234
+ CLOISTER_CONFIG_FILE = join2(PANOPTICON_HOME, "cloister.toml");
235
+ DEFAULT_CLOISTER_CONFIG = {
236
+ startup: {
237
+ auto_start: true
238
+ },
239
+ thresholds: {
240
+ stale: 5,
241
+ warning: 15,
242
+ stuck: 30
243
+ },
244
+ auto_actions: {
245
+ poke_on_warning: true,
246
+ kill_on_stuck: false,
247
+ // Manual by default for safety
248
+ restart_on_kill: false
249
+ },
250
+ monitoring: {
251
+ check_interval: 60,
252
+ // 1 minute
253
+ heartbeat_sources: ["jsonl_mtime", "tmux_activity", "git_activity"]
254
+ },
255
+ notifications: {
256
+ slack_webhook: void 0,
257
+ email: void 0
258
+ },
259
+ specialists: {
260
+ merge_agent: {
261
+ enabled: true,
262
+ auto_wake: false
263
+ // Only wake on explicit "Approve & Merge" click
264
+ },
265
+ review_agent: {
266
+ enabled: true,
267
+ auto_wake: false
268
+ // Only wake on explicit request
269
+ },
270
+ test_agent: {
271
+ enabled: false,
272
+ // Not yet implemented
273
+ auto_wake: false
274
+ }
275
+ },
276
+ model_selection: {
277
+ default_model: "sonnet",
278
+ complexity_routing: {
279
+ trivial: "haiku",
280
+ simple: "haiku",
281
+ medium: "sonnet",
282
+ complex: "sonnet",
283
+ expert: "opus"
284
+ },
285
+ specialist_models: {
286
+ merge_agent: "sonnet",
287
+ review_agent: "sonnet",
288
+ test_agent: "haiku",
289
+ planning_agent: "opus"
290
+ }
291
+ },
292
+ handoffs: {
293
+ auto_triggers: {
294
+ planning_complete: {
295
+ enabled: true,
296
+ from_model: "opus",
297
+ to_model: "sonnet"
298
+ },
299
+ stuck_escalation: {
300
+ enabled: true,
301
+ haiku_to_sonnet_minutes: 10,
302
+ sonnet_to_opus_minutes: 20
303
+ },
304
+ test_failure: {
305
+ enabled: true,
306
+ from_model: "haiku",
307
+ to_model: "sonnet",
308
+ trigger_on: "any_failure"
309
+ },
310
+ implementation_complete: {
311
+ enabled: true,
312
+ // Auto-handoff to test-agent when implementation done
313
+ to_specialist: "test-agent"
314
+ }
315
+ }
316
+ },
317
+ cost_tracking: {
318
+ display_enabled: true,
319
+ log_to_jsonl: true
320
+ },
321
+ auto_restart: {
322
+ enabled: true,
323
+ max_retries: 3,
324
+ backoff_seconds: [30, 60, 120]
325
+ // 30s, 1m, 2m
326
+ },
327
+ cost_limits: {
328
+ per_agent_usd: 10,
329
+ per_issue_usd: 25,
330
+ daily_total_usd: 100,
331
+ alert_threshold: 0.8
332
+ // Alert at 80%
333
+ },
334
+ retention: {
335
+ agent_state_days: 30,
336
+ health_staleness_hours: 24
337
+ }
338
+ };
339
+ }
340
+ });
341
+
342
+ // src/lib/agents.ts
343
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, appendFileSync, unlinkSync } from "fs";
344
+ import { join as join3 } from "path";
345
+ import { homedir } from "os";
346
+ import { exec } from "child_process";
347
+ import { promisify } from "util";
348
+ function getProviderEnvForModel(model) {
349
+ const provider = getProviderForModel(model);
350
+ if (provider.name === "anthropic") return {};
351
+ const settings = loadSettings();
352
+ const apiKey = settings.api_keys?.[provider.name];
353
+ if (apiKey) {
354
+ return getProviderEnv(provider, apiKey);
355
+ }
356
+ console.warn(`Warning: No API key configured for ${provider.displayName}, falling back to Anthropic`);
357
+ return {};
358
+ }
359
+ function getReadySignalPath(agentId) {
360
+ return join3(getAgentDir(agentId), "ready.json");
361
+ }
362
+ function clearReadySignal(agentId) {
363
+ const readyPath = getReadySignalPath(agentId);
364
+ if (existsSync3(readyPath)) {
365
+ try {
366
+ unlinkSync(readyPath);
367
+ } catch {
368
+ }
369
+ }
370
+ }
371
+ async function waitForReadySignal(agentId, timeoutSeconds = 30) {
372
+ const readyPath = getReadySignalPath(agentId);
373
+ for (let i = 0; i < timeoutSeconds; i++) {
374
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
375
+ if (existsSync3(readyPath)) {
376
+ try {
377
+ const content = readFileSync3(readyPath, "utf-8");
378
+ const signal = JSON.parse(content);
379
+ if (signal.ready === true) {
380
+ return true;
381
+ }
382
+ } catch {
383
+ }
384
+ }
385
+ }
386
+ return false;
387
+ }
388
+ function getAgentDir(agentId) {
389
+ return join3(AGENTS_DIR, agentId);
390
+ }
391
+ function getAgentState(agentId) {
392
+ const stateFile = join3(getAgentDir(agentId), "state.json");
393
+ if (!existsSync3(stateFile)) return null;
394
+ const content = readFileSync3(stateFile, "utf8");
395
+ return JSON.parse(content);
396
+ }
397
+ function saveAgentState(state) {
398
+ const dir = getAgentDir(state.id);
399
+ mkdirSync3(dir, { recursive: true });
400
+ writeFileSync3(
401
+ join3(dir, "state.json"),
402
+ JSON.stringify(state, null, 2)
403
+ );
404
+ }
405
+ function getAgentRuntimeState(agentId) {
406
+ const stateFile = join3(getAgentDir(agentId), "state.json");
407
+ if (!existsSync3(stateFile)) {
408
+ return {
409
+ state: "uninitialized",
410
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString()
411
+ };
412
+ }
413
+ try {
414
+ const content = readFileSync3(stateFile, "utf8");
415
+ return JSON.parse(content);
416
+ } catch {
417
+ return null;
418
+ }
419
+ }
420
+ function saveAgentRuntimeState(agentId, state) {
421
+ const dir = getAgentDir(agentId);
422
+ mkdirSync3(dir, { recursive: true });
423
+ const existing = getAgentRuntimeState(agentId);
424
+ const merged = {
425
+ ...existing || { state: "uninitialized", lastActivity: (/* @__PURE__ */ new Date()).toISOString() },
426
+ ...state
427
+ };
428
+ writeFileSync3(
429
+ join3(dir, "state.json"),
430
+ JSON.stringify(merged, null, 2)
431
+ );
432
+ }
433
+ function appendActivity(agentId, entry) {
434
+ const dir = getAgentDir(agentId);
435
+ mkdirSync3(dir, { recursive: true });
436
+ const activityFile = join3(dir, "activity.jsonl");
437
+ appendFileSync(activityFile, JSON.stringify(entry) + "\n");
438
+ if (existsSync3(activityFile)) {
439
+ try {
440
+ const lines = readFileSync3(activityFile, "utf8").trim().split("\n");
441
+ if (lines.length > 100) {
442
+ const trimmed = lines.slice(-100);
443
+ writeFileSync3(activityFile, trimmed.join("\n") + "\n");
444
+ }
445
+ } catch (error) {
446
+ }
447
+ }
448
+ }
449
+ function getActivity(agentId, limit = 100) {
450
+ const activityFile = join3(getAgentDir(agentId), "activity.jsonl");
451
+ if (!existsSync3(activityFile)) {
452
+ return [];
453
+ }
454
+ try {
455
+ const lines = readFileSync3(activityFile, "utf8").trim().split("\n");
456
+ const entries = lines.filter((line) => line.trim()).map((line) => JSON.parse(line)).slice(-limit);
457
+ return entries;
458
+ } catch {
459
+ return [];
460
+ }
461
+ }
462
+ function saveSessionId(agentId, sessionId) {
463
+ const dir = getAgentDir(agentId);
464
+ mkdirSync3(dir, { recursive: true });
465
+ writeFileSync3(join3(dir, "session.id"), sessionId);
466
+ }
467
+ function getSessionId(agentId) {
468
+ const sessionFile = join3(getAgentDir(agentId), "session.id");
469
+ if (!existsSync3(sessionFile)) {
470
+ return null;
471
+ }
472
+ try {
473
+ return readFileSync3(sessionFile, "utf8").trim();
474
+ } catch {
475
+ return null;
476
+ }
477
+ }
478
+ function determineModel(options) {
479
+ console.log(`[DEBUG] determineModel called with:`, { model: options.model, workType: options.workType, phase: options.phase, agentType: options.agentType, difficulty: options.difficulty });
480
+ if (options.model) {
481
+ console.log(`[DEBUG] Using explicit model: ${options.model}`);
482
+ return options.model;
483
+ }
484
+ try {
485
+ if (options.workType) {
486
+ return getModelId(options.workType);
487
+ }
488
+ if (options.phase) {
489
+ const workType = `issue-agent:${options.phase}`;
490
+ return getModelId(workType);
491
+ }
492
+ if (options.agentType && options.agentType !== "work-agent") {
493
+ if (options.agentType === "planning-agent") {
494
+ return getModelId("planning-agent");
495
+ }
496
+ const workType = `specialist-${options.agentType}`;
497
+ return getModelId(workType);
498
+ }
499
+ if (options.difficulty) {
500
+ const settings = loadSettings();
501
+ if (settings.models.complexity[options.difficulty]) {
502
+ console.warn(`Using legacy complexity-based routing for ${options.difficulty}. Consider migrating to work types.`);
503
+ return settings.models.complexity[options.difficulty];
504
+ }
505
+ }
506
+ try {
507
+ const cloisterConfig = loadCloisterConfig();
508
+ const defaultModel = cloisterConfig.model_selection?.default_model || "sonnet";
509
+ const modelMap = {
510
+ "opus": "claude-opus-4-6",
511
+ "sonnet": "claude-sonnet-4-5",
512
+ "haiku": "claude-haiku-4-5"
513
+ };
514
+ return modelMap[defaultModel] || "claude-sonnet-4-5";
515
+ } catch {
516
+ return "claude-sonnet-4-5";
517
+ }
518
+ } catch (error) {
519
+ console.warn("Warning: Could not resolve model using work type router, using default");
520
+ return options.model || "claude-sonnet-4-5";
521
+ }
522
+ }
523
+ async function spawnAgent(options) {
524
+ const agentId = `agent-${options.issueId.toLowerCase()}`;
525
+ if (sessionExists(agentId)) {
526
+ throw new Error(`Agent ${agentId} already running. Use 'pan work tell' to message it.`);
527
+ }
528
+ initHook(agentId);
529
+ const selectedModel = determineModel(options);
530
+ console.log(`[DEBUG] Selected model: ${selectedModel}`);
531
+ const state = {
532
+ id: agentId,
533
+ issueId: options.issueId,
534
+ workspace: options.workspace,
535
+ runtime: options.runtime || "claude",
536
+ model: selectedModel,
537
+ status: "starting",
538
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
539
+ // Initialize Phase 4 fields (legacy)
540
+ complexity: options.difficulty,
541
+ handoffCount: 0,
542
+ costSoFar: 0,
543
+ // Work type system (PAN-118)
544
+ phase: options.phase,
545
+ workType: options.workType
546
+ };
547
+ saveAgentState(state);
548
+ let prompt = options.prompt || "";
549
+ const { hasWork, items } = checkHook(agentId);
550
+ if (hasWork) {
551
+ const fixedPointPrompt = generateFixedPointPrompt(agentId);
552
+ if (fixedPointPrompt) {
553
+ prompt = fixedPointPrompt + "\n\n---\n\n" + prompt;
554
+ }
555
+ }
556
+ const promptFile = join3(getAgentDir(agentId), "initial-prompt.md");
557
+ if (prompt) {
558
+ writeFileSync3(promptFile, prompt);
559
+ }
560
+ checkAndSetupHooks();
561
+ writeTaskCache(agentId, options.issueId);
562
+ clearReadySignal(agentId);
563
+ const providerEnv = getProviderEnvForModel(selectedModel);
564
+ let claudeCmd;
565
+ if (prompt) {
566
+ const launcherScript = join3(getAgentDir(agentId), "launcher.sh");
567
+ const launcherContent = `#!/bin/bash
568
+ prompt=$(cat "${promptFile}")
569
+ exec claude --dangerously-skip-permissions --model ${state.model} "$prompt"
570
+ `;
571
+ writeFileSync3(launcherScript, launcherContent, { mode: 493 });
572
+ claudeCmd = `bash "${launcherScript}"`;
573
+ } else {
574
+ claudeCmd = `claude --dangerously-skip-permissions --model ${state.model}`;
575
+ }
576
+ createSession(agentId, options.workspace, claudeCmd, {
577
+ env: {
578
+ PANOPTICON_AGENT_ID: agentId,
579
+ ...providerEnv
580
+ // Add provider-specific env vars (BASE_URL, AUTH_TOKEN, etc.)
581
+ }
582
+ });
583
+ state.status = "running";
584
+ saveAgentState(state);
585
+ startWork(agentId, options.issueId);
586
+ return state;
587
+ }
588
+ function listRunningAgents() {
589
+ const tmuxSessions = getAgentSessions();
590
+ const tmuxNames = new Set(tmuxSessions.map((s) => s.name));
591
+ const agents = [];
592
+ if (!existsSync3(AGENTS_DIR)) return agents;
593
+ const dirs = readdirSync2(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
594
+ for (const dir of dirs) {
595
+ const state = getAgentState(dir.name);
596
+ if (state) {
597
+ agents.push({
598
+ ...state,
599
+ tmuxActive: tmuxNames.has(state.id)
600
+ });
601
+ }
602
+ }
603
+ return agents;
604
+ }
605
+ function stopAgent(agentId) {
606
+ const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
607
+ if (sessionExists(normalizedId)) {
608
+ try {
609
+ const output = capturePane(normalizedId, 5e3);
610
+ if (output) {
611
+ const agentDir = getAgentDir(normalizedId);
612
+ mkdirSync3(agentDir, { recursive: true });
613
+ writeFileSync3(join3(agentDir, "output.log"), output);
614
+ }
615
+ } catch {
616
+ }
617
+ killSession(normalizedId);
618
+ }
619
+ const state = getAgentState(normalizedId);
620
+ if (state) {
621
+ if (!state.id) state.id = normalizedId;
622
+ state.status = "stopped";
623
+ saveAgentState(state);
624
+ }
625
+ }
626
+ async function messageAgent(agentId, message) {
627
+ const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
628
+ const runtimeState = getAgentRuntimeState(normalizedId);
629
+ if (runtimeState?.state === "suspended") {
630
+ console.log(`[agents] Auto-resuming suspended agent ${normalizedId} to deliver message`);
631
+ const result = await resumeAgent(normalizedId, message);
632
+ if (!result.success) {
633
+ throw new Error(`Failed to auto-resume agent: ${result.error}`);
634
+ }
635
+ return;
636
+ }
637
+ const { loadRemoteAgentState, sendToRemoteAgent } = await import("./remote-agents-Z3R2A5BN.js");
638
+ const remoteState = loadRemoteAgentState(normalizedId);
639
+ if (remoteState && remoteState.vmName) {
640
+ console.log(`[agents] Sending message to remote agent ${normalizedId} on ${remoteState.vmName}`);
641
+ await sendToRemoteAgent(normalizedId, remoteState.vmName, message);
642
+ const mailDir2 = join3(getAgentDir(normalizedId), "mail");
643
+ mkdirSync3(mailDir2, { recursive: true });
644
+ const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
645
+ writeFileSync3(
646
+ join3(mailDir2, `${timestamp2}.md`),
647
+ `# Message
648
+
649
+ ${message}
650
+ `
651
+ );
652
+ return;
653
+ }
654
+ if (!sessionExists(normalizedId)) {
655
+ throw new Error(`Agent ${normalizedId} not running`);
656
+ }
657
+ sendKeys(normalizedId, message);
658
+ const mailDir = join3(getAgentDir(normalizedId), "mail");
659
+ mkdirSync3(mailDir, { recursive: true });
660
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
661
+ writeFileSync3(
662
+ join3(mailDir, `${timestamp}.md`),
663
+ `# Message
664
+
665
+ ${message}
666
+ `
667
+ );
668
+ }
669
+ async function resumeAgent(agentId, message) {
670
+ const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
671
+ const runtimeState = getAgentRuntimeState(normalizedId);
672
+ if (!runtimeState || runtimeState.state !== "suspended") {
673
+ return {
674
+ success: false,
675
+ error: `Cannot resume agent in state: ${runtimeState?.state || "unknown"}`
676
+ };
677
+ }
678
+ const sessionId = getSessionId(normalizedId);
679
+ if (!sessionId) {
680
+ return {
681
+ success: false,
682
+ error: "No saved session ID found"
683
+ };
684
+ }
685
+ const agentState = getAgentState(normalizedId);
686
+ if (!agentState) {
687
+ return {
688
+ success: false,
689
+ error: "Agent state not found"
690
+ };
691
+ }
692
+ if (sessionExists(normalizedId)) {
693
+ return {
694
+ success: false,
695
+ error: "Agent session already exists"
696
+ };
697
+ }
698
+ try {
699
+ clearReadySignal(normalizedId);
700
+ const providerEnv = agentState.model ? getProviderEnvForModel(agentState.model) : {};
701
+ const claudeCmd = `claude --resume "${sessionId}" --dangerously-skip-permissions`;
702
+ createSession(normalizedId, agentState.workspace, claudeCmd, {
703
+ env: {
704
+ PANOPTICON_AGENT_ID: normalizedId,
705
+ ...providerEnv
706
+ }
707
+ });
708
+ if (message) {
709
+ const ready = await waitForReadySignal(normalizedId, 30);
710
+ if (ready) {
711
+ sendKeys(normalizedId, message);
712
+ } else {
713
+ console.error("Claude SessionStart hook did not fire during resume, message not sent");
714
+ }
715
+ }
716
+ saveAgentRuntimeState(normalizedId, {
717
+ state: "active",
718
+ resumedAt: (/* @__PURE__ */ new Date()).toISOString()
719
+ });
720
+ if (agentState) {
721
+ agentState.status = "running";
722
+ agentState.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
723
+ saveAgentState(agentState);
724
+ }
725
+ return { success: true };
726
+ } catch (error) {
727
+ const msg = error instanceof Error ? error.message : String(error);
728
+ return {
729
+ success: false,
730
+ error: `Failed to resume agent: ${msg}`
731
+ };
732
+ }
733
+ }
734
+ function detectCrashedAgents() {
735
+ const agents = listRunningAgents();
736
+ return agents.filter(
737
+ (agent) => agent.status === "running" && !agent.tmuxActive
738
+ );
739
+ }
740
+ function recoverAgent(agentId) {
741
+ const normalizedId = agentId.startsWith("agent-") ? agentId : `agent-${agentId.toLowerCase()}`;
742
+ const state = getAgentState(normalizedId);
743
+ if (!state) {
744
+ return null;
745
+ }
746
+ if (!state.id) state.id = normalizedId;
747
+ if (!state.workspace || !state.model) {
748
+ console.error(`[agents] Cannot recover ${normalizedId}: state.json missing workspace or model`);
749
+ return null;
750
+ }
751
+ if (sessionExists(normalizedId)) {
752
+ return state;
753
+ }
754
+ const healthFile = join3(getAgentDir(normalizedId), "health.json");
755
+ let health = { consecutiveFailures: 0, killCount: 0, recoveryCount: 0 };
756
+ if (existsSync3(healthFile)) {
757
+ try {
758
+ health = { ...health, ...JSON.parse(readFileSync3(healthFile, "utf-8")) };
759
+ } catch {
760
+ }
761
+ }
762
+ health.recoveryCount = (health.recoveryCount || 0) + 1;
763
+ writeFileSync3(healthFile, JSON.stringify(health, null, 2));
764
+ const recoveryPrompt = generateRecoveryPrompt(state);
765
+ const providerEnv = state.model ? getProviderEnvForModel(state.model) : {};
766
+ const claudeCmd = `claude --dangerously-skip-permissions --model ${state.model} "${recoveryPrompt.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
767
+ createSession(normalizedId, state.workspace, claudeCmd, {
768
+ env: {
769
+ PANOPTICON_AGENT_ID: normalizedId,
770
+ ...providerEnv
771
+ }
772
+ });
773
+ state.status = "running";
774
+ state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
775
+ saveAgentState(state);
776
+ return state;
777
+ }
778
+ function generateRecoveryPrompt(state) {
779
+ const lines = [
780
+ "# Agent Recovery",
781
+ "",
782
+ "\u26A0\uFE0F This agent session was recovered after a crash.",
783
+ "",
784
+ "## Previous Context",
785
+ `- Issue: ${state.issueId}`,
786
+ `- Workspace: ${state.workspace}`,
787
+ `- Started: ${state.startedAt}`,
788
+ "",
789
+ "## Recovery Steps",
790
+ "1. Check beads for context: `bd show " + state.issueId + "`",
791
+ "2. Review recent git commits: `git log --oneline -10`",
792
+ "3. Check hook for pending work: `pan work hook check`",
793
+ "4. Resume from last known state",
794
+ "",
795
+ "## FPP Reminder",
796
+ '> "Any runnable action is a fixed point and must resolve before the system can rest."',
797
+ ""
798
+ ];
799
+ const { hasWork } = checkHook(state.id);
800
+ if (hasWork) {
801
+ const fixedPointPrompt = generateFixedPointPrompt(state.id);
802
+ if (fixedPointPrompt) {
803
+ lines.push("---");
804
+ lines.push("");
805
+ lines.push(fixedPointPrompt);
806
+ }
807
+ }
808
+ return lines.join("\n");
809
+ }
810
+ function autoRecoverAgents() {
811
+ const crashed = detectCrashedAgents();
812
+ const recovered = [];
813
+ const failed = [];
814
+ for (const agent of crashed) {
815
+ try {
816
+ const result = recoverAgent(agent.id);
817
+ if (result) {
818
+ recovered.push(agent.id);
819
+ } else {
820
+ failed.push(agent.id);
821
+ }
822
+ } catch (error) {
823
+ failed.push(agent.id);
824
+ }
825
+ }
826
+ return { recovered, failed };
827
+ }
828
+ function checkAndSetupHooks() {
829
+ const settingsPath = join3(homedir(), ".claude", "settings.json");
830
+ const hookPath = join3(homedir(), ".panopticon", "bin", "heartbeat-hook");
831
+ if (existsSync3(settingsPath)) {
832
+ try {
833
+ const settingsContent = readFileSync3(settingsPath, "utf-8");
834
+ const settings = JSON.parse(settingsContent);
835
+ const postToolUse = settings?.hooks?.PostToolUse || [];
836
+ const hookConfigured = postToolUse.some(
837
+ (hookConfig) => hookConfig.hooks?.some(
838
+ (hook) => hook.command === hookPath || hook.command?.includes("panopticon") || hook.command?.includes("heartbeat-hook")
839
+ )
840
+ );
841
+ if (hookConfigured) {
842
+ return;
843
+ }
844
+ } catch {
845
+ }
846
+ }
847
+ try {
848
+ console.log("Configuring Panopticon heartbeat hooks...");
849
+ exec("pan setup hooks", (error) => {
850
+ if (error) {
851
+ console.warn("\u26A0 Failed to auto-configure hooks. Run `pan setup hooks` manually.");
852
+ } else {
853
+ console.log("\u2713 Heartbeat hooks configured");
854
+ }
855
+ });
856
+ } catch (error) {
857
+ console.warn("\u26A0 Failed to auto-configure hooks. Run `pan setup hooks` manually.");
858
+ }
859
+ }
860
+ function writeTaskCache(agentId, issueId) {
861
+ const cacheDir = join3(getAgentDir(agentId));
862
+ mkdirSync3(cacheDir, { recursive: true });
863
+ const cacheFile = join3(cacheDir, "current-task.json");
864
+ writeFileSync3(
865
+ cacheFile,
866
+ JSON.stringify({
867
+ id: issueId,
868
+ title: `Working on ${issueId}`,
869
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
870
+ }, null, 2)
871
+ );
872
+ }
873
+ var execAsync;
874
+ var init_agents = __esm({
875
+ "src/lib/agents.ts"() {
876
+ init_esm_shims();
877
+ init_paths();
878
+ init_tmux();
879
+ init_hooks();
880
+ init_cv();
881
+ init_config();
882
+ init_settings();
883
+ init_work_type_router();
884
+ init_providers();
885
+ execAsync = promisify(exec);
886
+ }
887
+ });
888
+
889
+ export {
890
+ getAgentCV,
891
+ getAgentRankings,
892
+ formatCV,
893
+ init_cv,
894
+ loadCloisterConfig,
895
+ getHealthThresholdsMs,
896
+ init_config,
897
+ getAgentDir,
898
+ getAgentState,
899
+ saveAgentState,
900
+ getAgentRuntimeState,
901
+ saveAgentRuntimeState,
902
+ appendActivity,
903
+ getActivity,
904
+ saveSessionId,
905
+ getSessionId,
906
+ spawnAgent,
907
+ listRunningAgents,
908
+ stopAgent,
909
+ messageAgent,
910
+ resumeAgent,
911
+ detectCrashedAgents,
912
+ recoverAgent,
913
+ autoRecoverAgents,
914
+ init_agents
915
+ };
916
+ //# sourceMappingURL=chunk-Z24TY3XN.js.map