panopticon-cli 0.4.28 → 0.4.30

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 (33) hide show
  1. package/dist/{agents-ND4NKCK2.js → agents-ZG4JIPO2.js} +4 -4
  2. package/dist/{chunk-4KNEZGKZ.js → chunk-42O6XJWZ.js} +45 -24
  3. package/dist/chunk-42O6XJWZ.js.map +1 -0
  4. package/dist/{chunk-46DPNFMW.js → chunk-J3J32DIR.js} +9 -8
  5. package/dist/chunk-J3J32DIR.js.map +1 -0
  6. package/dist/{chunk-ZLB6G4NW.js → chunk-VH27COUW.js} +6 -6
  7. package/dist/chunk-VH27COUW.js.map +1 -0
  8. package/dist/{chunk-ON5NIBGW.js → chunk-VIWUCJ4V.js} +37 -8
  9. package/dist/chunk-VIWUCJ4V.js.map +1 -0
  10. package/dist/cli/index.js +117 -68
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/dashboard/public/assets/{index--VPaQ2VU.css → index-C7X6LP5Z.css} +1 -1
  13. package/dist/dashboard/public/assets/{index-GYQaqwVS.js → index-Dhwz2I7n.js} +152 -152
  14. package/dist/dashboard/public/index.html +2 -2
  15. package/dist/dashboard/server.js +1741 -1381
  16. package/dist/feedback-writer-AAKF5BTK.js +111 -0
  17. package/dist/feedback-writer-AAKF5BTK.js.map +1 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.js +1 -1
  20. package/dist/{specialist-context-WXO3FKIB.js → specialist-context-QVRSHPYN.js} +3 -3
  21. package/dist/{specialist-logs-SJWLETJT.js → specialist-logs-7TQMHCUN.js} +3 -3
  22. package/dist/{specialists-5YJIDRW6.js → specialists-UUXB5KFV.js} +3 -3
  23. package/package.json +1 -1
  24. package/templates/traefik/docker-compose.yml +7 -4
  25. package/templates/traefik/dynamic/panopticon.yml.template +3 -1
  26. package/dist/chunk-46DPNFMW.js.map +0 -1
  27. package/dist/chunk-4KNEZGKZ.js.map +0 -1
  28. package/dist/chunk-ON5NIBGW.js.map +0 -1
  29. package/dist/chunk-ZLB6G4NW.js.map +0 -1
  30. /package/dist/{agents-ND4NKCK2.js.map → agents-ZG4JIPO2.js.map} +0 -0
  31. /package/dist/{specialist-context-WXO3FKIB.js.map → specialist-context-QVRSHPYN.js.map} +0 -0
  32. /package/dist/{specialist-logs-SJWLETJT.js.map → specialist-logs-7TQMHCUN.js.map} +0 -0
  33. /package/dist/{specialists-5YJIDRW6.js.map → specialists-UUXB5KFV.js.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  recordWake,
26
26
  wakeSpecialistOrQueue,
27
27
  wakeSpecialistWithTask
28
- } from "../chunk-4KNEZGKZ.js";
28
+ } from "../chunk-42O6XJWZ.js";
29
29
  import {
30
30
  addAlias,
31
31
  cleanOldBackups,
@@ -68,7 +68,7 @@ import {
68
68
  saveSessionId,
69
69
  spawnAgent,
70
70
  stopAgent
71
- } from "../chunk-ZLB6G4NW.js";
71
+ } from "../chunk-VH27COUW.js";
72
72
  import {
73
73
  checkHook,
74
74
  clearHook,
@@ -82,13 +82,14 @@ import {
82
82
  popFromHook,
83
83
  pushToHook,
84
84
  sendKeys,
85
+ sendKeysAsync,
85
86
  sendMail,
86
87
  sessionExists
87
- } from "../chunk-ON5NIBGW.js";
88
+ } from "../chunk-VIWUCJ4V.js";
88
89
  import {
89
90
  init_settings,
90
91
  loadSettings
91
- } from "../chunk-46DPNFMW.js";
92
+ } from "../chunk-J3J32DIR.js";
92
93
  import {
93
94
  init_config_yaml,
94
95
  loadConfig as loadConfig2
@@ -894,7 +895,7 @@ async function isRemoteAvailable() {
894
895
 
895
896
  // src/lib/cloister/work-agent-prompt.ts
896
897
  init_esm_shims();
897
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
898
+ import { existsSync as existsSync6, readFileSync as readFileSync4, readdirSync as readdirSync6 } from "fs";
898
899
  import { join as join6, dirname as dirname3 } from "path";
899
900
  import { fileURLToPath as fileURLToPath3 } from "url";
900
901
 
@@ -1008,6 +1009,7 @@ function buildWorkAgentPrompt(ctx) {
1008
1009
  let beadsTasksStr = "";
1009
1010
  let stitchDesignsStr = "";
1010
1011
  let polyrepoContextStr = "";
1012
+ let pendingFeedbackStr = "";
1011
1013
  if (!ctx.skipDynamicContext && ctx.projectRoot) {
1012
1014
  const planningContent = readPlanningContext(ctx.workspacePath);
1013
1015
  const beadsTasks = readBeadsTasks(ctx.workspacePath, ctx.projectRoot, ctx.issueId);
@@ -1019,6 +1021,7 @@ function buildWorkAgentPrompt(ctx) {
1019
1021
  stitchDesignsStr = stitchDesigns;
1020
1022
  }
1021
1023
  polyrepoContextStr = buildPolyrepoContext(ctx.issueId, ctx.workspacePath);
1024
+ pendingFeedbackStr = readPendingFeedback(ctx.workspacePath);
1022
1025
  }
1023
1026
  const apiUrl = process.env.DASHBOARD_URL || `http://localhost:${process.env.API_PORT || process.env.PORT || "3011"}`;
1024
1027
  const vars = {
@@ -1029,13 +1032,36 @@ function buildWorkAgentPrompt(ctx) {
1029
1032
  API_URL: apiUrl,
1030
1033
  BEADS_TASKS: beadsTasksStr,
1031
1034
  STITCH_DESIGNS: stitchDesignsStr,
1032
- POLYREPO_CONTEXT: polyrepoContextStr
1035
+ POLYREPO_CONTEXT: polyrepoContextStr,
1036
+ PENDING_FEEDBACK: pendingFeedbackStr
1033
1037
  };
1034
1038
  template = processEnvBlocks(template, ctx.env);
1035
1039
  template = processIfBlocks(template, vars);
1036
1040
  template = substituteVariables(template, vars);
1037
1041
  return template;
1038
1042
  }
1043
+ function readPendingFeedback(workspacePath) {
1044
+ const feedbackDir = join6(workspacePath, ".planning", "feedback");
1045
+ if (!existsSync6(feedbackDir)) return "";
1046
+ try {
1047
+ const files = readdirSync6(feedbackDir).filter((f) => f.endsWith(".md")).sort();
1048
+ if (files.length === 0) return "";
1049
+ const latest = files[files.length - 1];
1050
+ const lines = [
1051
+ `**${files.length} feedback file(s) in \`.planning/feedback/\`:**`,
1052
+ ""
1053
+ ];
1054
+ for (const file of files) {
1055
+ const marker = file === latest ? " \u2190 **latest, read this first**" : "";
1056
+ lines.push(`- \`.planning/feedback/${file}\`${marker}`);
1057
+ }
1058
+ lines.push("");
1059
+ lines.push("Read the latest feedback file and address any issues before continuing other work.");
1060
+ return lines.join("\n");
1061
+ } catch {
1062
+ return "";
1063
+ }
1064
+ }
1039
1065
  function readPlanningContext(workspacePath) {
1040
1066
  const statePath = join6(workspacePath, ".planning", "STATE.md");
1041
1067
  if (existsSync6(statePath)) {
@@ -3338,7 +3364,7 @@ import chalk19 from "chalk";
3338
3364
  // src/lib/context.ts
3339
3365
  init_esm_shims();
3340
3366
  init_paths();
3341
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync6, appendFileSync, readdirSync as readdirSync8 } from "fs";
3367
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync6, appendFileSync, readdirSync as readdirSync9 } from "fs";
3342
3368
  import { join as join13 } from "path";
3343
3369
  function getStateFile(agentId) {
3344
3370
  return join13(AGENTS_DIR, agentId, "STATE.md");
@@ -3498,7 +3524,7 @@ function searchHistory(agentId, pattern) {
3498
3524
  if (!existsSync13(historyDir)) return [];
3499
3525
  const results = [];
3500
3526
  const regex = new RegExp(pattern, "i");
3501
- const files = readdirSync8(historyDir).filter((f) => f.endsWith(".log"));
3527
+ const files = readdirSync9(historyDir).filter((f) => f.endsWith(".log"));
3502
3528
  files.sort().reverse();
3503
3529
  for (const file of files) {
3504
3530
  const content = readFileSync11(join13(historyDir, file), "utf-8");
@@ -3515,7 +3541,7 @@ function getRecentHistory(agentId, limit = 20) {
3515
3541
  const historyDir = getHistoryDir(agentId);
3516
3542
  if (!existsSync13(historyDir)) return [];
3517
3543
  const results = [];
3518
- const files = readdirSync8(historyDir).filter((f) => f.endsWith(".log"));
3544
+ const files = readdirSync9(historyDir).filter((f) => f.endsWith(".log"));
3519
3545
  files.sort().reverse();
3520
3546
  for (const file of files) {
3521
3547
  if (results.length >= limit) break;
@@ -3537,7 +3563,7 @@ function getMaterializedDir(agentId) {
3537
3563
  function listMaterialized(agentId) {
3538
3564
  const dir = getMaterializedDir(agentId);
3539
3565
  if (!existsSync13(dir)) return [];
3540
- return readdirSync8(dir).filter((f) => f.endsWith(".md")).map((f) => {
3566
+ return readdirSync9(dir).filter((f) => f.endsWith(".md")).map((f) => {
3541
3567
  const match = f.match(/^(.+)-(\d+)\.md$/);
3542
3568
  if (!match) return null;
3543
3569
  return {
@@ -3882,8 +3908,8 @@ async function runHealthCheck(config2 = {
3882
3908
  } catch {
3883
3909
  }
3884
3910
  if (existsSync15(AGENTS_DIR)) {
3885
- const { readdirSync: readdirSync19 } = await import("fs");
3886
- const dirs = readdirSync19(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-")).map((d) => d.name);
3911
+ const { readdirSync: readdirSync20 } = await import("fs");
3912
+ const dirs = readdirSync20(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-")).map((d) => d.name);
3887
3913
  for (const dir of dirs) {
3888
3914
  if (!sessions.includes(dir)) {
3889
3915
  sessions.push(dir);
@@ -5069,7 +5095,7 @@ init_esm_shims();
5069
5095
  init_paths();
5070
5096
  import {
5071
5097
  existsSync as existsSync20,
5072
- readdirSync as readdirSync10,
5098
+ readdirSync as readdirSync11,
5073
5099
  lstatSync,
5074
5100
  readlinkSync,
5075
5101
  symlinkSync as symlinkSync2,
@@ -5108,12 +5134,12 @@ function mergeSkillsIntoWorkspace(workspacePath) {
5108
5134
  mkdirSync11(skillsTarget, { recursive: true });
5109
5135
  const existingSkills = /* @__PURE__ */ new Set();
5110
5136
  if (existsSync20(skillsTarget)) {
5111
- for (const item of readdirSync10(skillsTarget)) {
5137
+ for (const item of readdirSync11(skillsTarget)) {
5112
5138
  existingSkills.add(item);
5113
5139
  }
5114
5140
  }
5115
5141
  if (!existsSync20(SKILLS_DIR)) return { added, skipped };
5116
- const panopticonSkills = readdirSync10(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
5142
+ const panopticonSkills = readdirSync11(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
5117
5143
  for (const skill of panopticonSkills) {
5118
5144
  const targetPath = join19(skillsTarget, skill);
5119
5145
  const sourcePath = join19(SKILLS_DIR, skill);
@@ -5216,7 +5242,7 @@ init_projects();
5216
5242
 
5217
5243
  // src/lib/workspace-manager.ts
5218
5244
  init_esm_shims();
5219
- import { existsSync as existsSync21, mkdirSync as mkdirSync12, writeFileSync as writeFileSync10, readFileSync as readFileSync19, readdirSync as readdirSync11, copyFileSync, symlinkSync as symlinkSync3, chmodSync, realpathSync } from "fs";
5245
+ import { existsSync as existsSync21, mkdirSync as mkdirSync12, writeFileSync as writeFileSync10, readFileSync as readFileSync19, readdirSync as readdirSync12, copyFileSync, symlinkSync as symlinkSync3, chmodSync, realpathSync } from "fs";
5220
5246
  import { join as join20, dirname as dirname8, basename as basename2 } from "path";
5221
5247
  import { homedir as homedir11 } from "os";
5222
5248
  import { exec as exec4 } from "child_process";
@@ -5355,7 +5381,7 @@ function processTemplates(templateDir, targetDir, placeholders, templates) {
5355
5381
  }
5356
5382
  }
5357
5383
  } else {
5358
- const files = readdirSync11(templateDir);
5384
+ const files = readdirSync12(templateDir);
5359
5385
  for (const file of files) {
5360
5386
  if (file.endsWith(".template")) {
5361
5387
  const sourcePath = join20(templateDir, file);
@@ -5449,7 +5475,7 @@ async function createWorkspace(options) {
5449
5475
  }
5450
5476
  const devcontainerDir = join20(workspacePath, ".devcontainer");
5451
5477
  if (existsSync21(devcontainerDir)) {
5452
- const composeFiles = readdirSync11(devcontainerDir).filter((f) => f.includes("compose") && (f.endsWith(".yml") || f.endsWith(".yaml")));
5478
+ const composeFiles = readdirSync12(devcontainerDir).filter((f) => f.includes("compose") && (f.endsWith(".yml") || f.endsWith(".yaml")));
5453
5479
  for (const composeFile of composeFiles) {
5454
5480
  sanitizeComposeFile(join20(devcontainerDir, composeFile));
5455
5481
  }
@@ -5510,7 +5536,7 @@ async function createWorkspace(options) {
5510
5536
  const templateSteps = processTemplates(templateDir, devcontainerDir2, placeholders);
5511
5537
  result.steps.push(...templateSteps);
5512
5538
  if (existsSync21(templateDir)) {
5513
- const files = readdirSync11(templateDir);
5539
+ const files = readdirSync12(templateDir);
5514
5540
  for (const file of files) {
5515
5541
  if (!file.endsWith(".template")) {
5516
5542
  const sourcePath = join20(templateDir, file);
@@ -5519,7 +5545,7 @@ async function createWorkspace(options) {
5519
5545
  }
5520
5546
  }
5521
5547
  }
5522
- const composeFiles = readdirSync11(devcontainerDir2).filter((f) => f.includes("compose") && (f.endsWith(".yml") || f.endsWith(".yaml")));
5548
+ const composeFiles = readdirSync12(devcontainerDir2).filter((f) => f.includes("compose") && (f.endsWith(".yml") || f.endsWith(".yaml")));
5523
5549
  for (const composeFile of composeFiles) {
5524
5550
  sanitizeComposeFile(join20(devcontainerDir2, composeFile));
5525
5551
  }
@@ -6986,7 +7012,7 @@ import chalk30 from "chalk";
6986
7012
  import ora17 from "ora";
6987
7013
  import inquirer5 from "inquirer";
6988
7014
  import { execSync as execSync5 } from "child_process";
6989
- import { existsSync as existsSync24, mkdirSync as mkdirSync15, writeFileSync as writeFileSync13, readFileSync as readFileSync21, copyFileSync as copyFileSync2, readdirSync as readdirSync12, statSync as statSync2 } from "fs";
7015
+ import { existsSync as existsSync24, mkdirSync as mkdirSync15, writeFileSync as writeFileSync13, readFileSync as readFileSync21, copyFileSync as copyFileSync2, readdirSync as readdirSync13, statSync as statSync2 } from "fs";
6990
7016
  import { join as join23 } from "path";
6991
7017
  import { homedir as homedir14 } from "os";
6992
7018
  function registerInstallCommand(program2) {
@@ -6997,7 +7023,7 @@ function copyDirectoryRecursive(source, dest) {
6997
7023
  throw new Error(`Source directory not found: ${source}`);
6998
7024
  }
6999
7025
  mkdirSync15(dest, { recursive: true });
7000
- const entries = readdirSync12(source);
7026
+ const entries = readdirSync13(source);
7001
7027
  for (const entry of entries) {
7002
7028
  const sourcePath = join23(source, entry);
7003
7029
  const destPath = join23(dest, entry);
@@ -7135,7 +7161,7 @@ async function installCommand(options) {
7135
7161
  if (existsSync24(SOURCE_SKILLS_DIR)) {
7136
7162
  spinner.start("Installing bundled skills...");
7137
7163
  try {
7138
- const skillDirs = readdirSync12(SOURCE_SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
7164
+ const skillDirs = readdirSync13(SOURCE_SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
7139
7165
  let installed = 0;
7140
7166
  let skipped = 0;
7141
7167
  for (const skillDir of skillDirs) {
@@ -7838,11 +7864,11 @@ var ClaudeCodeRuntime = class {
7838
7864
  /**
7839
7865
  * Send a message to a running agent
7840
7866
  */
7841
- sendMessage(agentId, message) {
7867
+ async sendMessage(agentId, message) {
7842
7868
  if (!sessionExists(agentId)) {
7843
7869
  throw new Error(`Agent ${agentId} is not running`);
7844
7870
  }
7845
- sendKeys(agentId, message);
7871
+ await sendKeysAsync(agentId, message);
7846
7872
  const mailDir = join25(getAgentDir(agentId), "mail");
7847
7873
  mkdirSync17(mailDir, { recursive: true });
7848
7874
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -9123,13 +9149,28 @@ init_config();
9123
9149
  init_specialists();
9124
9150
  init_agents();
9125
9151
  init_tmux();
9126
- import { readFileSync as readFileSync28, writeFileSync as writeFileSync20, existsSync as existsSync34, mkdirSync as mkdirSync22, readdirSync as readdirSync14, statSync as statSync4, rmSync as rmSync3 } from "fs";
9152
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync20, existsSync as existsSync34, mkdirSync as mkdirSync22, readdirSync as readdirSync15, statSync as statSync4, rmSync as rmSync3 } from "fs";
9127
9153
  import { join as join33 } from "path";
9128
9154
  import { exec as exec9 } from "child_process";
9129
9155
  import { promisify as promisify9 } from "util";
9130
9156
  import { homedir as homedir16 } from "os";
9131
9157
  var execAsync9 = promisify9(exec9);
9132
9158
  var REVIEW_STATUS_FILE = join33(homedir16(), ".panopticon", "review-status.json");
9159
+ function updateTestStatusToTesting(issueId) {
9160
+ try {
9161
+ if (!existsSync34(REVIEW_STATUS_FILE)) return;
9162
+ const data = JSON.parse(readFileSync28(REVIEW_STATUS_FILE, "utf-8"));
9163
+ const upper = issueId.toUpperCase();
9164
+ if (data[upper]) {
9165
+ data[upper].testStatus = "testing";
9166
+ data[upper].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9167
+ writeFileSync20(REVIEW_STATUS_FILE, JSON.stringify(data, null, 2), "utf-8");
9168
+ console.log(`[deacon] Updated testStatus to 'testing' for ${upper}`);
9169
+ }
9170
+ } catch (error) {
9171
+ console.error(`[deacon] Failed to update testStatus for ${issueId}:`, error);
9172
+ }
9173
+ }
9133
9174
  var DEFAULT_CONFIG = {
9134
9175
  pingTimeoutMs: 3e4,
9135
9176
  // How long to wait for response
@@ -9238,12 +9279,13 @@ function checkHeartbeat(name) {
9238
9279
  return { isResponsive: false };
9239
9280
  }
9240
9281
  }
9241
- function checkSpecialistHealth(name) {
9242
- const state = loadState();
9282
+ async function checkSpecialistHealth(name, sharedState) {
9283
+ const state = sharedState ?? loadState();
9243
9284
  const healthState = getSpecialistState(state, name);
9244
- const wasRunning = isRunning(name);
9285
+ const wasRunning = await isRunning(name);
9245
9286
  healthState.lastPingTime = (/* @__PURE__ */ new Date()).toISOString();
9246
9287
  if (!wasRunning) {
9288
+ if (!sharedState) saveState(state);
9247
9289
  return {
9248
9290
  specialistName: name,
9249
9291
  isResponsive: false,
@@ -9260,7 +9302,7 @@ function checkSpecialistHealth(name) {
9260
9302
  if (heartbeatResult.isResponsive) {
9261
9303
  healthState.consecutiveFailures = 0;
9262
9304
  healthState.lastResponseTime = (/* @__PURE__ */ new Date()).toISOString();
9263
- saveState(state);
9305
+ if (!sharedState) saveState(state);
9264
9306
  return {
9265
9307
  specialistName: name,
9266
9308
  isResponsive: true,
@@ -9272,7 +9314,7 @@ function checkSpecialistHealth(name) {
9272
9314
  };
9273
9315
  }
9274
9316
  healthState.consecutiveFailures++;
9275
- saveState(state);
9317
+ if (!sharedState) saveState(state);
9276
9318
  const shouldForceKill = healthState.consecutiveFailures >= config.consecutiveFailures && !isInCooldown(healthState);
9277
9319
  return {
9278
9320
  specialistName: name,
@@ -9284,9 +9326,9 @@ function checkSpecialistHealth(name) {
9284
9326
  cooldownRemainingMs: getCooldownRemaining(healthState)
9285
9327
  };
9286
9328
  }
9287
- async function forceKillSpecialist(name) {
9329
+ async function forceKillSpecialist(name, sharedState) {
9288
9330
  const tmuxSession = getTmuxSessionName(name);
9289
- const state = loadState();
9331
+ const state = sharedState ?? loadState();
9290
9332
  const healthState = getSpecialistState(state, name);
9291
9333
  if (isInCooldown(healthState)) {
9292
9334
  const remaining = getCooldownRemaining(healthState);
@@ -9305,7 +9347,7 @@ async function forceKillSpecialist(name) {
9305
9347
  state.recentDeaths = state.recentDeaths.filter(
9306
9348
  (d) => new Date(d).getTime() > windowStart
9307
9349
  );
9308
- saveState(state);
9350
+ if (!sharedState) saveState(state);
9309
9351
  console.log(`[deacon] Force-killed specialist ${name}`);
9310
9352
  return {
9311
9353
  success: true,
@@ -9319,19 +9361,19 @@ async function forceKillSpecialist(name) {
9319
9361
  };
9320
9362
  }
9321
9363
  }
9322
- function checkMassDeath() {
9323
- const state = loadState();
9364
+ function checkMassDeath(sharedState) {
9365
+ const state = sharedState ?? loadState();
9324
9366
  const windowStart = Date.now() - config.massDeathWindowMs;
9325
9367
  state.recentDeaths = state.recentDeaths.filter(
9326
9368
  (d) => new Date(d).getTime() > windowStart
9327
9369
  );
9328
- saveState(state);
9329
9370
  const deathCount = state.recentDeaths.length;
9330
9371
  if (deathCount >= config.massDeathThreshold) {
9331
9372
  if (state.lastMassDeathAlert) {
9332
9373
  const lastAlert = new Date(state.lastMassDeathAlert).getTime();
9333
9374
  const alertCooldown = 5 * 6e4;
9334
9375
  if (Date.now() - lastAlert < alertCooldown) {
9376
+ if (!sharedState) saveState(state);
9335
9377
  return {
9336
9378
  isMassDeath: true,
9337
9379
  deathCount,
@@ -9340,13 +9382,14 @@ function checkMassDeath() {
9340
9382
  }
9341
9383
  }
9342
9384
  state.lastMassDeathAlert = (/* @__PURE__ */ new Date()).toISOString();
9343
- saveState(state);
9385
+ if (!sharedState) saveState(state);
9344
9386
  return {
9345
9387
  isMassDeath: true,
9346
9388
  deathCount,
9347
9389
  message: `ALERT: ${deathCount} specialist deaths in ${config.massDeathWindowMs / 1e3}s - possible infrastructure issue`
9348
9390
  };
9349
9391
  }
9392
+ if (!sharedState) saveState(state);
9350
9393
  return {
9351
9394
  isMassDeath: false,
9352
9395
  deathCount
@@ -9384,14 +9427,11 @@ async function checkAndSuspendIdleAgents() {
9384
9427
  const idleMs = Date.now() - lastActivity.getTime();
9385
9428
  const idleMinutes = idleMs / (1e3 * 60);
9386
9429
  const isSpecialist = specialistNames.has(agent.id);
9387
- const timeoutMinutes = isSpecialist ? 5 : 10;
9388
9430
  const isWorkAgent = agent.id.startsWith("agent-") && !isSpecialist;
9389
9431
  if (isWorkAgent) {
9390
- const completedFile = join33(getAgentDir(agent.id), "completed");
9391
- if (existsSync34(completedFile)) {
9392
- continue;
9393
- }
9432
+ continue;
9394
9433
  }
9434
+ const timeoutMinutes = 5;
9395
9435
  if (idleMinutes > timeoutMinutes) {
9396
9436
  console.log(`[deacon] Auto-suspending ${agent.id} (idle for ${Math.round(idleMinutes)} minutes)`);
9397
9437
  try {
@@ -9574,7 +9614,7 @@ async function cleanupStaleAgentState() {
9574
9614
  return actions;
9575
9615
  }
9576
9616
  try {
9577
- const dirs = readdirSync14(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
9617
+ const dirs = readdirSync15(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
9578
9618
  for (const dir of dirs) {
9579
9619
  const agentDir = join33(AGENTS_DIR, dir.name);
9580
9620
  try {
@@ -9637,13 +9677,19 @@ async function checkOrphanedReviewStatuses() {
9637
9677
  const testAgentActive = testAgentRunning && testAgentState?.state === "active";
9638
9678
  let modified = false;
9639
9679
  for (const [issueId, status] of Object.entries(statuses)) {
9640
- if (status.reviewStatus === "reviewing" && !reviewAgentActive) {
9680
+ const hasPassedReview = status.history?.some(
9681
+ (h) => h.type === "review" && h.status === "passed"
9682
+ );
9683
+ const hasPassedTest = status.history?.some(
9684
+ (h) => h.type === "test" && (h.status === "passed" || h.status === "failed")
9685
+ );
9686
+ if (status.reviewStatus === "reviewing" && !reviewAgentActive && !hasPassedReview) {
9641
9687
  console.log(`[deacon] Orphaned review detected: ${issueId} shows 'reviewing' but review-agent is not active`);
9642
9688
  status.reviewStatus = "pending";
9643
9689
  modified = true;
9644
9690
  actions.push(`Reset orphaned review for ${issueId} (review-agent not active)`);
9645
9691
  }
9646
- if (status.testStatus === "testing" && !testAgentActive) {
9692
+ if (status.testStatus === "testing" && !testAgentActive && !hasPassedTest && !status.readyForMerge) {
9647
9693
  console.log(`[deacon] Orphaned test detected: ${issueId} shows 'testing' but test-agent is not active`);
9648
9694
  status.testStatus = "pending";
9649
9695
  modified = true;
@@ -9668,11 +9714,11 @@ async function runPatrol() {
9668
9714
  const actions = [];
9669
9715
  console.log(`[deacon] Patrol cycle ${state.patrolCycle} - checking ${enabled.length} specialists`);
9670
9716
  for (const specialist of enabled) {
9671
- const result = checkSpecialistHealth(specialist.name);
9717
+ const result = await checkSpecialistHealth(specialist.name, state);
9672
9718
  results.push(result);
9673
9719
  if (result.shouldForceKill) {
9674
9720
  console.log(`[deacon] ${specialist.name} stuck (${result.consecutiveFailures} failures), force-killing`);
9675
- const killResult = await forceKillSpecialist(specialist.name);
9721
+ const killResult = await forceKillSpecialist(specialist.name, state);
9676
9722
  actions.push(`Force-killed ${specialist.name}: ${killResult.message}`);
9677
9723
  if (killResult.success) {
9678
9724
  console.log(`[deacon] Auto-restarting ${specialist.name}...`);
@@ -9700,7 +9746,7 @@ async function runPatrol() {
9700
9746
  if (nextTask) {
9701
9747
  console.log(`[deacon] Auto-resuming suspended ${specialist.name} for queued task: ${nextTask.payload.issueId}`);
9702
9748
  try {
9703
- const { resumeAgent } = await import("../agents-ND4NKCK2.js");
9749
+ const { resumeAgent } = await import("../agents-ZG4JIPO2.js");
9704
9750
  const message = `# Queued Work
9705
9751
 
9706
9752
  Processing queued task: ${nextTask.payload.issueId}`;
@@ -9731,6 +9777,9 @@ Processing queued task: ${nextTask.payload.issueId}`;
9731
9777
  const wakeResult = await wakeSpecialistWithTask(specialist.name, taskDetails);
9732
9778
  if (wakeResult.success) {
9733
9779
  completeSpecialistTask(specialist.name, nextTask.id);
9780
+ if (specialist.name === "test-agent" && nextTask.payload.issueId) {
9781
+ updateTestStatusToTesting(nextTask.payload.issueId);
9782
+ }
9734
9783
  actions.push(`Processed queued task for ${specialist.name}: ${nextTask.payload.issueId}`);
9735
9784
  } else {
9736
9785
  console.error(`[deacon] Failed to wake ${specialist.name} for queued task: ${wakeResult.error}`);
@@ -9752,12 +9801,12 @@ Processing queued task: ${nextTask.payload.issueId}`;
9752
9801
  const cleanupActions = await cleanupStaleAgentState();
9753
9802
  actions.push(...cleanupActions);
9754
9803
  }
9755
- saveState(state);
9756
- const massDeathCheck = checkMassDeath();
9804
+ const massDeathCheck = checkMassDeath(state);
9757
9805
  if (massDeathCheck.isMassDeath && massDeathCheck.message) {
9758
9806
  console.error(`[deacon] ${massDeathCheck.message}`);
9759
9807
  actions.push(massDeathCheck.message);
9760
9808
  }
9809
+ saveState(state);
9761
9810
  return {
9762
9811
  cycle: state.patrolCycle,
9763
9812
  timestamp: state.lastPatrol,
@@ -9799,7 +9848,7 @@ function getDeaconStatus() {
9799
9848
  // src/lib/cloister/service.ts
9800
9849
  init_paths();
9801
9850
  init_paths();
9802
- import { existsSync as existsSync35, writeFileSync as writeFileSync21, unlinkSync as unlinkSync4, readFileSync as readFileSync29, readdirSync as readdirSync15, renameSync } from "fs";
9851
+ import { existsSync as existsSync35, writeFileSync as writeFileSync21, unlinkSync as unlinkSync4, readFileSync as readFileSync29, readdirSync as readdirSync16, renameSync } from "fs";
9803
9852
  import { join as join34 } from "path";
9804
9853
  var CLOISTER_STATE_FILE = join34(PANOPTICON_HOME, "cloister.state");
9805
9854
  function writeStateFile(running, pid) {
@@ -10041,7 +10090,7 @@ var CloisterService = class {
10041
10090
  async checkCompletionMarkers() {
10042
10091
  try {
10043
10092
  if (!existsSync35(AGENTS_DIR)) return;
10044
- const agentDirs = readdirSync15(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-"));
10093
+ const agentDirs = readdirSync16(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("agent-"));
10045
10094
  for (const dir of agentDirs) {
10046
10095
  const completedFile = join34(AGENTS_DIR, dir.name, "completed");
10047
10096
  const processedFile = join34(AGENTS_DIR, dir.name, "completed.processed");
@@ -11522,7 +11571,7 @@ import { promisify as promisify12 } from "util";
11522
11571
  var execAsync12 = promisify12(exec12);
11523
11572
  async function listLogsCommand(project2, type, options) {
11524
11573
  try {
11525
- const { listRunLogs } = await import("../specialist-logs-SJWLETJT.js");
11574
+ const { listRunLogs } = await import("../specialist-logs-7TQMHCUN.js");
11526
11575
  const limit = options.limit ? parseInt(options.limit) : 10;
11527
11576
  const runs = listRunLogs(project2, type, { limit });
11528
11577
  if (options.json) {
@@ -11567,7 +11616,7 @@ View a specific run: pan specialists logs ${project2} ${type} <runId>
11567
11616
  }
11568
11617
  async function viewLogCommand(project2, type, runId, options) {
11569
11618
  try {
11570
- const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-SJWLETJT.js");
11619
+ const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-7TQMHCUN.js");
11571
11620
  const content = getRunLog(project2, type, runId);
11572
11621
  if (!content) {
11573
11622
  console.error(`\u274C Run log not found: ${runId}`);
@@ -11591,8 +11640,8 @@ async function viewLogCommand(project2, type, runId, options) {
11591
11640
  }
11592
11641
  async function tailLogCommand(project2, type) {
11593
11642
  try {
11594
- const { getRunLogPath } = await import("../specialist-logs-SJWLETJT.js");
11595
- const { getProjectSpecialistMetadata } = await import("../specialists-5YJIDRW6.js");
11643
+ const { getRunLogPath } = await import("../specialist-logs-7TQMHCUN.js");
11644
+ const { getProjectSpecialistMetadata } = await import("../specialists-UUXB5KFV.js");
11596
11645
  const metadata = getProjectSpecialistMetadata(project2, type);
11597
11646
  if (!metadata.currentRun) {
11598
11647
  console.error(`\u274C No active run for ${project2}/${type}`);
@@ -11661,7 +11710,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
11661
11710
  console.log(" Use --force to confirm.");
11662
11711
  process.exit(1);
11663
11712
  }
11664
- const { cleanupAllLogs } = await import("../specialist-logs-SJWLETJT.js");
11713
+ const { cleanupAllLogs } = await import("../specialist-logs-7TQMHCUN.js");
11665
11714
  console.log("\u{1F9F9} Cleaning up old logs for all projects...\n");
11666
11715
  const results = cleanupAllLogs();
11667
11716
  console.log(`
@@ -11688,7 +11737,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
11688
11737
  console.log(" Use --force to confirm.");
11689
11738
  process.exit(1);
11690
11739
  }
11691
- const { cleanupOldLogs } = await import("../specialist-logs-SJWLETJT.js");
11740
+ const { cleanupOldLogs } = await import("../specialist-logs-7TQMHCUN.js");
11692
11741
  const { getSpecialistRetention } = await import("../projects-VXRUCMLM.js");
11693
11742
  const retention = getSpecialistRetention(projectOrAll);
11694
11743
  console.log(`\u{1F9F9} Cleaning up old logs for ${projectOrAll}/${type}...`);
@@ -11727,7 +11776,7 @@ import ora18 from "ora";
11727
11776
  // src/lib/convoy.ts
11728
11777
  init_esm_shims();
11729
11778
  init_tmux();
11730
- import { existsSync as existsSync41, mkdirSync as mkdirSync25, writeFileSync as writeFileSync26, readFileSync as readFileSync34, readdirSync as readdirSync16 } from "fs";
11779
+ import { existsSync as existsSync41, mkdirSync as mkdirSync25, writeFileSync as writeFileSync26, readFileSync as readFileSync34, readdirSync as readdirSync17 } from "fs";
11731
11780
  import { join as join39 } from "path";
11732
11781
  import { homedir as homedir19 } from "os";
11733
11782
  import { exec as exec13 } from "child_process";
@@ -11885,7 +11934,7 @@ function listConvoys(filter) {
11885
11934
  if (!existsSync41(CONVOY_DIR)) {
11886
11935
  return [];
11887
11936
  }
11888
- const files = readdirSync16(CONVOY_DIR).filter((f) => f.endsWith(".json"));
11937
+ const files = readdirSync17(CONVOY_DIR).filter((f) => f.endsWith(".json"));
11889
11938
  const convoys = [];
11890
11939
  for (const file of files) {
11891
11940
  const convoyId = file.replace(".json", "");
@@ -12352,7 +12401,7 @@ function registerConvoyCommands(program2) {
12352
12401
  init_esm_shims();
12353
12402
  init_projects();
12354
12403
  import chalk45 from "chalk";
12355
- import { existsSync as existsSync42, readFileSync as readFileSync35, symlinkSync as symlinkSync4, mkdirSync as mkdirSync26, readdirSync as readdirSync17, statSync as statSync6 } from "fs";
12404
+ import { existsSync as existsSync42, readFileSync as readFileSync35, symlinkSync as symlinkSync4, mkdirSync as mkdirSync26, readdirSync as readdirSync18, statSync as statSync6 } from "fs";
12356
12405
  import { join as join40, resolve, dirname as dirname11 } from "path";
12357
12406
  import { fileURLToPath as fileURLToPath4 } from "url";
12358
12407
  var __filename5 = fileURLToPath4(import.meta.url);
@@ -12368,7 +12417,7 @@ function installGitHooks(gitDir) {
12368
12417
  return 0;
12369
12418
  }
12370
12419
  try {
12371
- const hooks = readdirSync17(BUNDLED_HOOKS_DIR).filter((f) => {
12420
+ const hooks = readdirSync18(BUNDLED_HOOKS_DIR).filter((f) => {
12372
12421
  const p = join40(BUNDLED_HOOKS_DIR, f);
12373
12422
  return existsSync42(p) && statSync6(p).isFile();
12374
12423
  });
@@ -12446,9 +12495,9 @@ async function projectAddCommand(projectPath, options = {}) {
12446
12495
  const hasRootGit = existsSync42(join40(fullPath, ".git"));
12447
12496
  const subRepos = [];
12448
12497
  if (!hasRootGit) {
12449
- const { readdirSync: readdirSync19, statSync: statSync8 } = await import("fs");
12498
+ const { readdirSync: readdirSync20, statSync: statSync8 } = await import("fs");
12450
12499
  try {
12451
- const entries = readdirSync19(fullPath);
12500
+ const entries = readdirSync20(fullPath);
12452
12501
  for (const entry of entries) {
12453
12502
  const entryPath = join40(fullPath, entry);
12454
12503
  try {
@@ -12642,7 +12691,7 @@ Project: ${foundKey}
12642
12691
  init_esm_shims();
12643
12692
  init_paths();
12644
12693
  import chalk46 from "chalk";
12645
- import { existsSync as existsSync43, readdirSync as readdirSync18, readFileSync as readFileSync36 } from "fs";
12694
+ import { existsSync as existsSync43, readdirSync as readdirSync19, readFileSync as readFileSync36 } from "fs";
12646
12695
  import { execSync as execSync8 } from "child_process";
12647
12696
  import { homedir as homedir20 } from "os";
12648
12697
  import { join as join41 } from "path";
@@ -12660,7 +12709,7 @@ function checkDirectory(path) {
12660
12709
  function countItems(path) {
12661
12710
  if (!existsSync43(path)) return 0;
12662
12711
  try {
12663
- return readdirSync18(path).length;
12712
+ return readdirSync19(path).length;
12664
12713
  } catch {
12665
12714
  return 0;
12666
12715
  }
@@ -14449,7 +14498,7 @@ if (existsSync48(PANOPTICON_ENV_FILE)) {
14449
14498
  }
14450
14499
  }
14451
14500
  var program = new Command();
14452
- program.name("pan").description("Multi-agent orchestration for AI coding assistants").version("0.1.3");
14501
+ program.name("pan").description("Multi-agent orchestration for AI coding assistants").version(JSON.parse(readFileSync42(join48(import.meta.dirname, "../../package.json"), "utf-8")).version);
14453
14502
  program.command("init").description("Initialize Panopticon (~/.panopticon/)").action(initCommand);
14454
14503
  program.command("sync").description("Sync skills/commands to AI tools").option("--dry-run", "Show what would be synced").option("--force", "Overwrite without prompts").option("--backup-only", "Only create backup").action(syncCommand);
14455
14504
  program.command("restore [timestamp]").description("Restore from backup").action(restoreCommand);