panopticon-cli 0.5.1 → 0.5.3

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 (117) hide show
  1. package/dist/{agents-5OPQKM5K.js → agents-DMPT32H7.js} +7 -6
  2. package/dist/{chunk-OWHXCGVO.js → chunk-2V2DQ3IX.js} +378 -101
  3. package/dist/chunk-2V2DQ3IX.js.map +1 -0
  4. package/dist/{chunk-F5555J3A.js → chunk-4HST45MO.js} +13 -27
  5. package/dist/chunk-4HST45MO.js.map +1 -0
  6. package/dist/{chunk-YLPSQAM2.js → chunk-565HZ6VV.js} +2 -2
  7. package/dist/chunk-6N2KBSJA.js +452 -0
  8. package/dist/chunk-6N2KBSJA.js.map +1 -0
  9. package/dist/{chunk-VHKSS7QX.js → chunk-D67AQTHF.js} +21 -19
  10. package/dist/chunk-D67AQTHF.js.map +1 -0
  11. package/dist/{chunk-4YSYJ4HM.js → chunk-DFNVHK3N.js} +2 -2
  12. package/dist/{chunk-7SN4L4PH.js → chunk-HOGYHJ2G.js} +2 -2
  13. package/dist/{chunk-FTCPTHIJ.js → chunk-HRU7S4TA.js} +24 -7
  14. package/dist/chunk-HRU7S4TA.js.map +1 -0
  15. package/dist/{chunk-NLQRED36.js → chunk-KBHRXV5T.js} +3 -3
  16. package/dist/chunk-KBHRXV5T.js.map +1 -0
  17. package/dist/{chunk-CWELWPWQ.js → chunk-MOPGR3CL.js} +1 -1
  18. package/dist/chunk-MOPGR3CL.js.map +1 -0
  19. package/dist/{chunk-2V4NF7J2.js → chunk-RLZQB7HS.js} +2 -2
  20. package/dist/chunk-RLZQB7HS.js.map +1 -0
  21. package/dist/{chunk-76F6DSVS.js → chunk-T7BBPDEJ.js} +11 -7
  22. package/dist/chunk-T7BBPDEJ.js.map +1 -0
  23. package/dist/chunk-USYP2SBE.js +317 -0
  24. package/dist/chunk-USYP2SBE.js.map +1 -0
  25. package/dist/{chunk-JM6V62LT.js → chunk-ZDNQFWR5.js} +2 -2
  26. package/dist/{chunk-JM6V62LT.js.map → chunk-ZDNQFWR5.js.map} +1 -1
  27. package/dist/{chunk-HJSM6E6U.js → chunk-ZP6EWSZV.js} +29 -322
  28. package/dist/chunk-ZP6EWSZV.js.map +1 -0
  29. package/dist/{chunk-PELXV435.js → chunk-ZTYHZMEC.js} +2 -2
  30. package/dist/chunk-ZTYHZMEC.js.map +1 -0
  31. package/dist/cli/index.js +1381 -777
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/config-yaml-OVZLKFMA.js +18 -0
  34. package/dist/dashboard/prompts/merge-agent.md +7 -5
  35. package/dist/dashboard/prompts/review-agent.md +12 -1
  36. package/dist/dashboard/prompts/test-agent.md +3 -1
  37. package/dist/dashboard/public/assets/index-BJKEp64j.css +32 -0
  38. package/dist/dashboard/public/assets/index-CgJjqjAV.js +767 -0
  39. package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  40. package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  41. package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  42. package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  43. package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  44. package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  45. package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  46. package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  47. package/dist/dashboard/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  48. package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
  49. package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
  50. package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-BflQw4A9.woff +0 -0
  51. package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-DjKNqYRj.woff2 +0 -0
  52. package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
  53. package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
  54. package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
  55. package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
  56. package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-DxxdqCpr.woff2 +0 -0
  57. package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-VcznFIpX.woff +0 -0
  58. package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
  59. package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
  60. package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
  61. package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
  62. package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-D6zpsUhD.woff +0 -0
  63. package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-DUi7WF5p.woff2 +0 -0
  64. package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
  65. package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
  66. package/dist/dashboard/public/index.html +5 -3
  67. package/dist/dashboard/server.js +4647 -2723
  68. package/dist/{feedback-writer-VRMMWWTW.js → feedback-writer-T43PI5S2.js} +2 -2
  69. package/dist/{hume-WMAUBBV2.js → hume-CKJJ3OUU.js} +3 -3
  70. package/dist/index.js +4 -3
  71. package/dist/index.js.map +1 -1
  72. package/dist/{projects-CFX3RTDL.js → projects-KVM3MN3Y.js} +2 -2
  73. package/dist/{remote-agents-TFSMW7GN.js → remote-agents-ULPD6C5U.js} +3 -3
  74. package/dist/{remote-workspace-7FPGF2RM.js → remote-workspace-XX6ARE6I.js} +3 -3
  75. package/dist/{review-status-TDPSOU5J.js → review-status-XKUKZF6J.js} +3 -2
  76. package/dist/{specialist-context-WGUUYDWY.js → specialist-context-53AWO6AE.js} +6 -5
  77. package/dist/{specialist-context-WGUUYDWY.js.map → specialist-context-53AWO6AE.js.map} +1 -1
  78. package/dist/{specialist-logs-XJB5TCKJ.js → specialist-logs-QREUJ4HN.js} +6 -5
  79. package/dist/{specialists-5LBRHYFA.js → specialists-2DBBXRCK.js} +6 -5
  80. package/dist/{traefik-WFMQX2LY.js → traefik-5GL3Q7DJ.js} +3 -3
  81. package/dist/{tunnel-W2GZBLEV.js → tunnel-BKC7KLBX.js} +3 -3
  82. package/dist/{workspace-manager-E434Z45T.js → workspace-manager-ALBR62AS.js} +5 -5
  83. package/dist/workspace-manager-ALBR62AS.js.map +1 -0
  84. package/package.json +1 -1
  85. package/scripts/record-cost-event.js +8424 -42
  86. package/scripts/work-agent-stop-hook +221 -24
  87. package/dist/chunk-2V4NF7J2.js.map +0 -1
  88. package/dist/chunk-76F6DSVS.js.map +0 -1
  89. package/dist/chunk-CWELWPWQ.js.map +0 -1
  90. package/dist/chunk-F5555J3A.js.map +0 -1
  91. package/dist/chunk-FTCPTHIJ.js.map +0 -1
  92. package/dist/chunk-HJSM6E6U.js.map +0 -1
  93. package/dist/chunk-NLQRED36.js.map +0 -1
  94. package/dist/chunk-OWHXCGVO.js.map +0 -1
  95. package/dist/chunk-PELXV435.js.map +0 -1
  96. package/dist/chunk-VHKSS7QX.js.map +0 -1
  97. package/dist/chunk-YGJ54GW2.js +0 -96
  98. package/dist/chunk-YGJ54GW2.js.map +0 -1
  99. package/dist/dashboard/public/assets/index-Ce6q21Fm.js +0 -743
  100. package/dist/dashboard/public/assets/index-NzpI0ItZ.css +0 -32
  101. package/dist/git-utils-I2UDKNZH.js +0 -131
  102. package/dist/git-utils-I2UDKNZH.js.map +0 -1
  103. /package/dist/{agents-5OPQKM5K.js.map → agents-DMPT32H7.js.map} +0 -0
  104. /package/dist/{chunk-YLPSQAM2.js.map → chunk-565HZ6VV.js.map} +0 -0
  105. /package/dist/{chunk-4YSYJ4HM.js.map → chunk-DFNVHK3N.js.map} +0 -0
  106. /package/dist/{chunk-7SN4L4PH.js.map → chunk-HOGYHJ2G.js.map} +0 -0
  107. /package/dist/{hume-WMAUBBV2.js.map → config-yaml-OVZLKFMA.js.map} +0 -0
  108. /package/dist/{feedback-writer-VRMMWWTW.js.map → feedback-writer-T43PI5S2.js.map} +0 -0
  109. /package/dist/{projects-CFX3RTDL.js.map → hume-CKJJ3OUU.js.map} +0 -0
  110. /package/dist/{remote-agents-TFSMW7GN.js.map → projects-KVM3MN3Y.js.map} +0 -0
  111. /package/dist/{review-status-TDPSOU5J.js.map → remote-agents-ULPD6C5U.js.map} +0 -0
  112. /package/dist/{remote-workspace-7FPGF2RM.js.map → remote-workspace-XX6ARE6I.js.map} +0 -0
  113. /package/dist/{specialist-logs-XJB5TCKJ.js.map → review-status-XKUKZF6J.js.map} +0 -0
  114. /package/dist/{specialists-5LBRHYFA.js.map → specialist-logs-QREUJ4HN.js.map} +0 -0
  115. /package/dist/{traefik-WFMQX2LY.js.map → specialists-2DBBXRCK.js.map} +0 -0
  116. /package/dist/{tunnel-W2GZBLEV.js.map → traefik-5GL3Q7DJ.js.map} +0 -0
  117. /package/dist/{workspace-manager-E434Z45T.js.map → tunnel-BKC7KLBX.js.map} +0 -0
@@ -10,7 +10,7 @@ import {
10
10
  pushToHook,
11
11
  sendKeysAsync,
12
12
  waitForClaudePrompt
13
- } from "./chunk-FTCPTHIJ.js";
13
+ } from "./chunk-HRU7S4TA.js";
14
14
  import {
15
15
  init_pipeline_notifier,
16
16
  notifyPipeline
@@ -23,11 +23,11 @@ import {
23
23
  init_settings,
24
24
  loadSettings,
25
25
  setupCredentialFileAuth
26
- } from "./chunk-HJSM6E6U.js";
26
+ } from "./chunk-USYP2SBE.js";
27
27
  import {
28
28
  init_projects,
29
29
  projects_exports
30
- } from "./chunk-2V4NF7J2.js";
30
+ } from "./chunk-RLZQB7HS.js";
31
31
  import {
32
32
  COSTS_DIR,
33
33
  PANOPTICON_HOME,
@@ -42,6 +42,7 @@ import {
42
42
  } from "./chunk-ZHC57RCV.js";
43
43
 
44
44
  // src/lib/cost.ts
45
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from "fs";
45
46
  import { join } from "path";
46
47
  function calculateCost(usage, pricing) {
47
48
  let cost = 0;
@@ -77,6 +78,218 @@ function getPricing(provider, model) {
77
78
  }
78
79
  return pricing || null;
79
80
  }
81
+ function getCostFile(date) {
82
+ return join(COSTS_DIR, `costs-${date}.jsonl`);
83
+ }
84
+ function getCurrentDateString() {
85
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
86
+ }
87
+ function readCosts(startDate, endDate) {
88
+ const entries = [];
89
+ const start = new Date(startDate);
90
+ const end = new Date(endDate);
91
+ for (let date = start; date <= end; date.setDate(date.getDate() + 1)) {
92
+ const dateStr = date.toISOString().split("T")[0];
93
+ const costFile = getCostFile(dateStr);
94
+ if (existsSync(costFile)) {
95
+ const content = readFileSync(costFile, "utf-8");
96
+ const lines = content.split("\n").filter((line) => line.trim());
97
+ for (const line of lines) {
98
+ try {
99
+ entries.push(JSON.parse(line));
100
+ } catch {
101
+ }
102
+ }
103
+ }
104
+ }
105
+ return entries;
106
+ }
107
+ function readTodayCosts() {
108
+ const today = getCurrentDateString();
109
+ return readCosts(today, today);
110
+ }
111
+ function readIssueCosts(issueId, days = 30) {
112
+ const end = /* @__PURE__ */ new Date();
113
+ const start = /* @__PURE__ */ new Date();
114
+ start.setDate(start.getDate() - days);
115
+ const allCosts = readCosts(
116
+ start.toISOString().split("T")[0],
117
+ end.toISOString().split("T")[0]
118
+ );
119
+ return allCosts.filter((entry) => entry.issueId === issueId);
120
+ }
121
+ function summarizeCosts(entries) {
122
+ const summary = {
123
+ totalCost: 0,
124
+ currency: "USD",
125
+ period: {
126
+ start: entries[0]?.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
127
+ end: entries[entries.length - 1]?.timestamp || (/* @__PURE__ */ new Date()).toISOString()
128
+ },
129
+ byProvider: {},
130
+ byModel: {},
131
+ byIssue: {},
132
+ byFeature: {},
133
+ entryCount: entries.length,
134
+ totalTokens: {
135
+ input: 0,
136
+ output: 0,
137
+ cacheRead: 0,
138
+ cacheWrite: 0,
139
+ total: 0
140
+ }
141
+ };
142
+ for (const entry of entries) {
143
+ summary.totalCost += entry.cost;
144
+ summary.byProvider[entry.provider] = (summary.byProvider[entry.provider] || 0) + entry.cost;
145
+ summary.byModel[entry.model] = (summary.byModel[entry.model] || 0) + entry.cost;
146
+ if (entry.issueId) {
147
+ summary.byIssue[entry.issueId] = (summary.byIssue[entry.issueId] || 0) + entry.cost;
148
+ }
149
+ if (entry.featureId) {
150
+ summary.byFeature[entry.featureId] = (summary.byFeature[entry.featureId] || 0) + entry.cost;
151
+ }
152
+ summary.totalTokens.input += entry.usage.inputTokens;
153
+ summary.totalTokens.output += entry.usage.outputTokens;
154
+ summary.totalTokens.cacheRead += entry.usage.cacheReadTokens || 0;
155
+ summary.totalTokens.cacheWrite += entry.usage.cacheWriteTokens || 0;
156
+ }
157
+ summary.totalTokens.total = summary.totalTokens.input + summary.totalTokens.output + summary.totalTokens.cacheRead + summary.totalTokens.cacheWrite;
158
+ summary.totalCost = Math.round(summary.totalCost * 100) / 100;
159
+ return summary;
160
+ }
161
+ function getDailySummary(date) {
162
+ const targetDate = date || getCurrentDateString();
163
+ const entries = readCosts(targetDate, targetDate);
164
+ return summarizeCosts(entries);
165
+ }
166
+ function getWeeklySummary() {
167
+ const end = /* @__PURE__ */ new Date();
168
+ const start = /* @__PURE__ */ new Date();
169
+ start.setDate(start.getDate() - 7);
170
+ const entries = readCosts(
171
+ start.toISOString().split("T")[0],
172
+ end.toISOString().split("T")[0]
173
+ );
174
+ return summarizeCosts(entries);
175
+ }
176
+ function getMonthlySummary() {
177
+ const end = /* @__PURE__ */ new Date();
178
+ const start = /* @__PURE__ */ new Date();
179
+ start.setDate(start.getDate() - 30);
180
+ const entries = readCosts(
181
+ start.toISOString().split("T")[0],
182
+ end.toISOString().split("T")[0]
183
+ );
184
+ return summarizeCosts(entries);
185
+ }
186
+ function loadBudgets() {
187
+ if (!existsSync(BUDGETS_FILE)) {
188
+ return [];
189
+ }
190
+ try {
191
+ const content = readFileSync(BUDGETS_FILE, "utf-8");
192
+ return JSON.parse(content);
193
+ } catch {
194
+ return [];
195
+ }
196
+ }
197
+ function saveBudgets(budgets) {
198
+ mkdirSync(COSTS_DIR, { recursive: true });
199
+ writeFileSync(BUDGETS_FILE, JSON.stringify(budgets, null, 2));
200
+ }
201
+ function createBudget(budget) {
202
+ const budgets = loadBudgets();
203
+ const newBudget = {
204
+ ...budget,
205
+ id: `budget-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
206
+ spent: 0
207
+ };
208
+ budgets.push(newBudget);
209
+ saveBudgets(budgets);
210
+ return newBudget;
211
+ }
212
+ function getBudget(id) {
213
+ const budgets = loadBudgets();
214
+ return budgets.find((b) => b.id === id) || null;
215
+ }
216
+ function getAllBudgets() {
217
+ return loadBudgets();
218
+ }
219
+ function checkBudget(id) {
220
+ const budget = getBudget(id);
221
+ if (!budget) {
222
+ return {
223
+ budget: null,
224
+ remaining: 0,
225
+ percentUsed: 0,
226
+ exceeded: false,
227
+ alert: false
228
+ };
229
+ }
230
+ const remaining = budget.limit - budget.spent;
231
+ const percentUsed = budget.spent / budget.limit;
232
+ return {
233
+ budget,
234
+ remaining,
235
+ percentUsed,
236
+ exceeded: percentUsed >= 1,
237
+ alert: percentUsed >= budget.alertThreshold
238
+ };
239
+ }
240
+ function deleteBudget(id) {
241
+ const budgets = loadBudgets();
242
+ const index = budgets.findIndex((b) => b.id === id);
243
+ if (index === -1) return false;
244
+ budgets.splice(index, 1);
245
+ saveBudgets(budgets);
246
+ return true;
247
+ }
248
+ function generateReport(startDate, endDate) {
249
+ const entries = readCosts(startDate, endDate);
250
+ const summary = summarizeCosts(entries);
251
+ const lines = [
252
+ "# Cost Report",
253
+ "",
254
+ `**Period:** ${startDate} to ${endDate}`,
255
+ "",
256
+ "## Summary",
257
+ "",
258
+ `- **Total Cost:** $${summary.totalCost.toFixed(2)}`,
259
+ `- **Total Entries:** ${summary.entryCount}`,
260
+ `- **Total Tokens:** ${summary.totalTokens.total.toLocaleString()}`,
261
+ ` - Input: ${summary.totalTokens.input.toLocaleString()}`,
262
+ ` - Output: ${summary.totalTokens.output.toLocaleString()}`,
263
+ "",
264
+ "## By Provider",
265
+ ""
266
+ ];
267
+ for (const [provider, cost] of Object.entries(summary.byProvider)) {
268
+ lines.push(`- **${provider}:** $${cost.toFixed(2)}`);
269
+ }
270
+ lines.push("");
271
+ lines.push("## By Model");
272
+ lines.push("");
273
+ for (const [model, cost] of Object.entries(summary.byModel)) {
274
+ lines.push(`- **${model}:** $${cost.toFixed(2)}`);
275
+ }
276
+ if (Object.keys(summary.byIssue).length > 0) {
277
+ lines.push("");
278
+ lines.push("## By Issue");
279
+ lines.push("");
280
+ const sortedIssues = Object.entries(summary.byIssue).sort(([, a], [, b]) => b - a);
281
+ for (const [issue, cost] of sortedIssues.slice(0, 10)) {
282
+ lines.push(`- **${issue}:** $${cost.toFixed(2)}`);
283
+ }
284
+ }
285
+ return lines.join("\n");
286
+ }
287
+ function formatCost(cost, currency = "USD") {
288
+ if (currency === "USD") {
289
+ return `$${cost.toFixed(4)}`;
290
+ }
291
+ return `${cost.toFixed(4)} ${currency}`;
292
+ }
80
293
  var DEFAULT_PRICING, BUDGETS_FILE;
81
294
  var init_cost = __esm({
82
295
  "src/lib/cost.ts"() {
@@ -110,14 +323,14 @@ var init_cost = __esm({
110
323
  });
111
324
 
112
325
  // src/lib/cost-parsers/jsonl-parser.ts
113
- import { existsSync, readFileSync, readdirSync, statSync } from "fs";
326
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync } from "fs";
114
327
  import { join as join2, basename } from "path";
115
328
  import { homedir } from "os";
116
329
  function getProjectDirs() {
117
- if (!existsSync(CLAUDE_PROJECTS_DIR)) {
330
+ if (!existsSync2(CLAUDE_PROJECTS_DIR)) {
118
331
  return [];
119
332
  }
120
- return readdirSync(CLAUDE_PROJECTS_DIR).map((name) => join2(CLAUDE_PROJECTS_DIR, name)).filter((path) => {
333
+ return readdirSync2(CLAUDE_PROJECTS_DIR).map((name) => join2(CLAUDE_PROJECTS_DIR, name)).filter((path) => {
121
334
  try {
122
335
  return statSync(path).isDirectory();
123
336
  } catch {
@@ -126,10 +339,10 @@ function getProjectDirs() {
126
339
  });
127
340
  }
128
341
  function getSessionFiles(projectDir) {
129
- if (!existsSync(projectDir)) {
342
+ if (!existsSync2(projectDir)) {
130
343
  return [];
131
344
  }
132
- return readdirSync(projectDir).filter((name) => name.endsWith(".jsonl")).map((name) => join2(projectDir, name)).sort((a, b) => {
345
+ return readdirSync2(projectDir).filter((name) => name.endsWith(".jsonl")).map((name) => join2(projectDir, name)).sort((a, b) => {
133
346
  try {
134
347
  return statSync(b).mtime.getTime() - statSync(a).mtime.getTime();
135
348
  } catch {
@@ -183,10 +396,10 @@ function normalizeModelName(model) {
183
396
  return { provider: "anthropic", model: "claude-sonnet-4" };
184
397
  }
185
398
  function parseClaudeSession(sessionFile) {
186
- if (!existsSync(sessionFile)) {
399
+ if (!existsSync2(sessionFile)) {
187
400
  return null;
188
401
  }
189
- const content = readFileSync(sessionFile, "utf-8");
402
+ const content = readFileSync2(sessionFile, "utf-8");
190
403
  const lines = content.split("\n").filter((line) => line.trim());
191
404
  let sessionId = "";
192
405
  let startTime = "";
@@ -297,18 +510,18 @@ var init_jsonl_parser = __esm({
297
510
  });
298
511
 
299
512
  // src/lib/cloister/specialist-handoff-logger.ts
300
- import { existsSync as existsSync2, mkdirSync, appendFileSync, readFileSync as readFileSync2 } from "fs";
513
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, appendFileSync as appendFileSync2, readFileSync as readFileSync3 } from "fs";
301
514
  import { join as join3 } from "path";
302
515
  function ensureLogDir() {
303
516
  const logDir = join3(PANOPTICON_HOME, "logs");
304
- if (!existsSync2(logDir)) {
305
- mkdirSync(logDir, { recursive: true });
517
+ if (!existsSync3(logDir)) {
518
+ mkdirSync2(logDir, { recursive: true });
306
519
  }
307
520
  }
308
521
  function logSpecialistHandoff(event) {
309
522
  ensureLogDir();
310
523
  const line = JSON.stringify(event) + "\n";
311
- appendFileSync(SPECIALIST_HANDOFF_LOG_FILE, line, "utf-8");
524
+ appendFileSync2(SPECIALIST_HANDOFF_LOG_FILE, line, "utf-8");
312
525
  }
313
526
  function createSpecialistHandoff(fromSpecialist, toSpecialist, issueId, priority, context) {
314
527
  return {
@@ -391,7 +604,7 @@ __export(specialists_exports, {
391
604
  wakeSpecialistOrQueue: () => wakeSpecialistOrQueue,
392
605
  wakeSpecialistWithTask: () => wakeSpecialistWithTask
393
606
  });
394
- import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, unlinkSync, appendFileSync as appendFileSync2 } from "fs";
607
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync3, unlinkSync, appendFileSync as appendFileSync3 } from "fs";
395
608
  import { join as join4, basename as basename2 } from "path";
396
609
  import { homedir as homedir2 } from "os";
397
610
  import { exec } from "child_process";
@@ -403,13 +616,13 @@ async function resolveWorkspaceGitInfo(workspace, taskBranch) {
403
616
  if (!workspace || workspace === "unknown") {
404
617
  return { gitDirs, branch, isPolyrepo: false };
405
618
  }
406
- if (existsSync3(join4(workspace, ".git"))) {
619
+ if (existsSync4(join4(workspace, ".git"))) {
407
620
  gitDirs.push(workspace);
408
621
  } else {
409
622
  try {
410
- const entries = readdirSync2(workspace, { withFileTypes: true });
623
+ const entries = readdirSync3(workspace, { withFileTypes: true });
411
624
  for (const entry of entries) {
412
- if (entry.isDirectory() && existsSync3(join4(workspace, entry.name, ".git"))) {
625
+ if (entry.isDirectory() && existsSync4(join4(workspace, entry.name, ".git"))) {
413
626
  gitDirs.push(join4(workspace, entry.name));
414
627
  }
415
628
  }
@@ -450,10 +663,10 @@ function buildTmuxEnvFlags(env) {
450
663
  return flags;
451
664
  }
452
665
  function initSpecialistsDirectory() {
453
- if (!existsSync3(SPECIALISTS_DIR)) {
454
- mkdirSync2(SPECIALISTS_DIR, { recursive: true });
666
+ if (!existsSync4(SPECIALISTS_DIR)) {
667
+ mkdirSync3(SPECIALISTS_DIR, { recursive: true });
455
668
  }
456
- if (!existsSync3(REGISTRY_FILE)) {
669
+ if (!existsSync4(REGISTRY_FILE)) {
457
670
  const registry = {
458
671
  version: "2.0",
459
672
  // Updated for per-project structure
@@ -477,7 +690,7 @@ function initSpecialistsDirectory() {
477
690
  }
478
691
  function migrateRegistryIfNeeded() {
479
692
  try {
480
- const content = readFileSync3(REGISTRY_FILE, "utf-8");
693
+ const content = readFileSync4(REGISTRY_FILE, "utf-8");
481
694
  const registry = JSON.parse(content);
482
695
  if (registry.version === "2.0" || registry.projects) {
483
696
  return;
@@ -507,7 +720,7 @@ function migrateRegistryIfNeeded() {
507
720
  function loadRegistry() {
508
721
  initSpecialistsDirectory();
509
722
  try {
510
- const content = readFileSync3(REGISTRY_FILE, "utf-8");
723
+ const content = readFileSync4(REGISTRY_FILE, "utf-8");
511
724
  return JSON.parse(content);
512
725
  } catch (error) {
513
726
  console.error("Failed to load specialist registry:", error);
@@ -525,53 +738,59 @@ function loadRegistry() {
525
738
  }
526
739
  }
527
740
  function saveRegistry(registry) {
528
- if (!existsSync3(SPECIALISTS_DIR)) {
529
- mkdirSync2(SPECIALISTS_DIR, { recursive: true });
741
+ if (!existsSync4(SPECIALISTS_DIR)) {
742
+ mkdirSync3(SPECIALISTS_DIR, { recursive: true });
530
743
  }
531
744
  registry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
532
745
  try {
533
746
  const content = JSON.stringify(registry, null, 2);
534
- writeFileSync(REGISTRY_FILE, content, "utf-8");
747
+ writeFileSync2(REGISTRY_FILE, content, "utf-8");
535
748
  } catch (error) {
536
749
  console.error("Failed to save specialist registry:", error);
537
750
  throw error;
538
751
  }
539
752
  }
540
- function getSessionFilePath(name) {
753
+ function getSessionFilePath(name, projectKey) {
754
+ if (projectKey) {
755
+ return join4(SPECIALISTS_DIR, "projects", projectKey, `${name}.session`);
756
+ }
541
757
  return join4(SPECIALISTS_DIR, `${name}.session`);
542
758
  }
543
- function getSessionId(name) {
544
- const sessionFile = getSessionFilePath(name);
545
- if (!existsSync3(sessionFile)) {
759
+ function getSessionId(name, projectKey) {
760
+ const sessionFile = getSessionFilePath(name, projectKey);
761
+ if (!existsSync4(sessionFile)) {
546
762
  return null;
547
763
  }
548
764
  try {
549
- return readFileSync3(sessionFile, "utf-8").trim();
765
+ return readFileSync4(sessionFile, "utf-8").trim();
550
766
  } catch (error) {
551
- console.error(`Failed to read session file for ${name}:`, error);
767
+ console.error(`Failed to read session file for ${name} (${projectKey ?? "global"}):`, error);
552
768
  return null;
553
769
  }
554
770
  }
555
- function setSessionId(name, sessionId) {
556
- initSpecialistsDirectory();
557
- const sessionFile = getSessionFilePath(name);
771
+ function setSessionId(name, sessionId, projectKey) {
772
+ const sessionFile = getSessionFilePath(name, projectKey);
773
+ const dir = projectKey ? join4(SPECIALISTS_DIR, "projects", projectKey) : SPECIALISTS_DIR;
774
+ if (!existsSync4(dir)) {
775
+ mkdirSync3(dir, { recursive: true });
776
+ }
558
777
  try {
559
- writeFileSync(sessionFile, sessionId.trim(), "utf-8");
778
+ writeFileSync2(sessionFile, sessionId.trim(), "utf-8");
560
779
  } catch (error) {
561
- console.error(`Failed to write session file for ${name}:`, error);
780
+ console.error(`Failed to write session file for ${name} (${projectKey ?? "global"}):`, error);
562
781
  throw error;
563
782
  }
564
783
  }
565
- function clearSessionId(name) {
566
- const sessionFile = getSessionFilePath(name);
567
- if (!existsSync3(sessionFile)) {
784
+ function clearSessionId(name, projectKey) {
785
+ const sessionFile = getSessionFilePath(name, projectKey);
786
+ if (!existsSync4(sessionFile)) {
568
787
  return false;
569
788
  }
570
789
  try {
571
790
  unlinkSync(sessionFile);
572
791
  return true;
573
792
  } catch (error) {
574
- console.error(`Failed to delete session file for ${name}:`, error);
793
+ console.error(`Failed to delete session file for ${name} (${projectKey ?? "global"}):`, error);
575
794
  throw error;
576
795
  }
577
796
  }
@@ -622,9 +841,9 @@ function recordWake(name, sessionId) {
622
841
  }
623
842
  async function spawnEphemeralSpecialist(projectKey, specialistType, task) {
624
843
  ensureProjectSpecialistDir(projectKey, specialistType);
625
- const { loadContextDigest } = await import("./specialist-context-WGUUYDWY.js");
844
+ const { loadContextDigest } = await import("./specialist-context-53AWO6AE.js");
626
845
  const contextDigest = loadContextDigest(projectKey, specialistType);
627
- const { createRunLog: createRunLog2 } = await import("./specialist-logs-XJB5TCKJ.js");
846
+ const { createRunLog: createRunLog2 } = await import("./specialist-logs-QREUJ4HN.js");
628
847
  const { runId, filePath: logFilePath } = createRunLog2(
629
848
  projectKey,
630
849
  specialistType,
@@ -633,10 +852,31 @@ async function spawnEphemeralSpecialist(projectKey, specialistType, task) {
633
852
  );
634
853
  setCurrentRun(projectKey, specialistType, runId);
635
854
  incrementProjectRunCount(projectKey, specialistType);
636
- const taskPrompt = await buildTaskPrompt(projectKey, specialistType, task, contextDigest);
855
+ const taskPrompt = task.promptOverride ?? await buildTaskPrompt(projectKey, specialistType, task, contextDigest);
856
+ if (task.promptOverride) {
857
+ console.log(`[specialist] Using promptOverride for ${projectKey}/${task.issueId} (${taskPrompt.length} chars)`);
858
+ }
637
859
  const tmuxSession = getTmuxSessionName(specialistType, projectKey);
638
- const cwd = process.env.HOME || "/home/exedev";
860
+ const cwd = homedir2();
639
861
  try {
862
+ try {
863
+ const { stdout: sessions } = await execAsync('tmux list-sessions -F "#{session_name}" 2>/dev/null || echo ""', { encoding: "utf-8" });
864
+ if (sessions.split("\n").map((s) => s.trim()).includes(tmuxSession)) {
865
+ const { getAgentRuntimeState } = await import("./agents-DMPT32H7.js");
866
+ const existingState = getAgentRuntimeState(tmuxSession);
867
+ if (existingState?.state === "active") {
868
+ return {
869
+ success: false,
870
+ message: `Specialist ${specialistType} (${projectKey}) is already running task ${existingState.currentIssue ?? "unknown"}`,
871
+ error: "specialist_busy"
872
+ };
873
+ }
874
+ console.log(`[specialist] Killing stale ${tmuxSession} session before respawn`);
875
+ await execAsync(`tmux kill-session -t "${tmuxSession}"`, { encoding: "utf-8" }).catch(() => {
876
+ });
877
+ }
878
+ } catch {
879
+ }
640
880
  let model = "claude-sonnet-4-6";
641
881
  try {
642
882
  const workTypeId = `specialist-${specialistType}`;
@@ -656,9 +896,9 @@ async function spawnEphemeralSpecialist(projectKey, specialistType, task) {
656
896
  const agentDir = join4(homedir2(), ".panopticon", "agents", tmuxSession);
657
897
  await execAsync(`mkdir -p "${agentDir}"`, { encoding: "utf-8" });
658
898
  const promptFile = join4(agentDir, "task-prompt.md");
659
- writeFileSync(promptFile, taskPrompt);
899
+ writeFileSync2(promptFile, taskPrompt);
660
900
  const launcherScript = join4(agentDir, "launcher.sh");
661
- writeFileSync(launcherScript, `#!/bin/bash
901
+ writeFileSync2(launcherScript, `#!/bin/bash
662
902
  cd "${cwd}"
663
903
  prompt=$(cat "${promptFile}")
664
904
 
@@ -673,7 +913,7 @@ echo "## Specialist completed task"
673
913
  `tmux new-session -d -s "${tmuxSession}"${envFlags} "bash '${launcherScript}'"`,
674
914
  { encoding: "utf-8" }
675
915
  );
676
- const { saveAgentRuntimeState } = await import("./agents-5OPQKM5K.js");
916
+ const { saveAgentRuntimeState } = await import("./agents-DMPT32H7.js");
677
917
  saveAgentRuntimeState(tmuxSession, {
678
918
  state: "active",
679
919
  lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
@@ -697,7 +937,7 @@ echo "## Specialist completed task"
697
937
  }
698
938
  }
699
939
  async function buildTaskPrompt(projectKey, specialistType, task, contextDigest) {
700
- const { getSpecialistPromptOverride } = await import("./projects-CFX3RTDL.js");
940
+ const { getSpecialistPromptOverride } = await import("./projects-KVM3MN3Y.js");
701
941
  const customPrompt = getSpecialistPromptOverride(projectKey, specialistType);
702
942
  let prompt = `# ${specialistType} Task - ${task.issueId}
703
943
 
@@ -867,7 +1107,7 @@ async function terminateSpecialist(projectKey, specialistType) {
867
1107
  console.error(`[specialist] Failed to kill tmux session ${tmuxSession}:`, error);
868
1108
  }
869
1109
  if (metadata.currentRun) {
870
- const { finalizeRunLog: finalizeRunLog2 } = await import("./specialist-logs-XJB5TCKJ.js");
1110
+ const { finalizeRunLog: finalizeRunLog2 } = await import("./specialist-logs-QREUJ4HN.js");
871
1111
  try {
872
1112
  finalizeRunLog2(projectKey, specialistType, metadata.currentRun, {
873
1113
  status: metadata.lastRunStatus || "incomplete",
@@ -880,20 +1120,20 @@ async function terminateSpecialist(projectKey, specialistType) {
880
1120
  }
881
1121
  const key = `${projectKey}-${specialistType}`;
882
1122
  gracePeriodStates.delete(key);
883
- const { saveAgentRuntimeState } = await import("./agents-5OPQKM5K.js");
1123
+ const { saveAgentRuntimeState } = await import("./agents-DMPT32H7.js");
884
1124
  saveAgentRuntimeState(tmuxSession, {
885
1125
  state: "suspended",
886
1126
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
887
1127
  });
888
- const { scheduleDigestGeneration } = await import("./specialist-context-WGUUYDWY.js");
1128
+ const { scheduleDigestGeneration } = await import("./specialist-context-53AWO6AE.js");
889
1129
  scheduleDigestGeneration(projectKey, specialistType);
890
1130
  scheduleLogCleanup(projectKey, specialistType);
891
1131
  }
892
1132
  function scheduleLogCleanup(projectKey, specialistType) {
893
1133
  Promise.resolve().then(async () => {
894
1134
  try {
895
- const { cleanupOldLogs: cleanupOldLogs2 } = await import("./specialist-logs-XJB5TCKJ.js");
896
- const { getSpecialistRetention } = await import("./projects-CFX3RTDL.js");
1135
+ const { cleanupOldLogs: cleanupOldLogs2 } = await import("./specialist-logs-QREUJ4HN.js");
1136
+ const { getSpecialistRetention } = await import("./projects-KVM3MN3Y.js");
897
1137
  const retention = getSpecialistRetention(projectKey);
898
1138
  const deleted = cleanupOldLogs2(projectKey, specialistType, { maxDays: retention.max_days, maxRuns: retention.max_runs });
899
1139
  if (deleted > 0) {
@@ -911,11 +1151,11 @@ function ensureProjectSpecialistDir(projectKey, specialistType) {
911
1151
  const specialistDir = getProjectSpecialistDir(projectKey, specialistType);
912
1152
  const runsDir = join4(specialistDir, "runs");
913
1153
  const contextDir = join4(specialistDir, "context");
914
- if (!existsSync3(runsDir)) {
915
- mkdirSync2(runsDir, { recursive: true });
1154
+ if (!existsSync4(runsDir)) {
1155
+ mkdirSync3(runsDir, { recursive: true });
916
1156
  }
917
- if (!existsSync3(contextDir)) {
918
- mkdirSync2(contextDir, { recursive: true });
1157
+ if (!existsSync4(contextDir)) {
1158
+ mkdirSync3(contextDir, { recursive: true });
919
1159
  }
920
1160
  }
921
1161
  function getProjectSpecialistMetadata(projectKey, specialistType) {
@@ -1002,7 +1242,7 @@ function updateContextTokens(name, tokens) {
1002
1242
  function listSessionFiles() {
1003
1243
  initSpecialistsDirectory();
1004
1244
  try {
1005
- const files = readdirSync2(SPECIALISTS_DIR);
1245
+ const files = readdirSync3(SPECIALISTS_DIR);
1006
1246
  const sessionFiles = files.filter((f) => f.endsWith(".session"));
1007
1247
  return sessionFiles.map((f) => f.replace(".session", ""));
1008
1248
  } catch (error) {
@@ -1055,7 +1295,17 @@ async function isRunning(name, projectKey) {
1055
1295
  const tmuxSession = getTmuxSessionName(name, projectKey);
1056
1296
  try {
1057
1297
  await execAsync(`tmux has-session -t ${tmuxSession}`);
1058
- return true;
1298
+ const { stdout } = await execAsync(
1299
+ `tmux list-panes -t ${tmuxSession} -F "#{pane_pid}" 2>/dev/null`,
1300
+ { encoding: "utf-8" }
1301
+ );
1302
+ const panePid = stdout.trim();
1303
+ if (!panePid) return false;
1304
+ const { stdout: children } = await execAsync(
1305
+ `ps --ppid ${panePid} --no-headers 2>/dev/null || echo ""`,
1306
+ { encoding: "utf-8" }
1307
+ );
1308
+ return children.trim().length > 0;
1059
1309
  } catch {
1060
1310
  return false;
1061
1311
  }
@@ -1068,10 +1318,10 @@ async function getSpecialistStatus(name, projectKey) {
1068
1318
  enabled: false,
1069
1319
  autoWake: false
1070
1320
  };
1071
- const sessionId = getSessionId(name);
1321
+ const sessionId = getSessionId(name, projectKey);
1072
1322
  const running = await isRunning(name, projectKey);
1073
1323
  const contextTokens = countContextTokens(name);
1074
- const { getAgentRuntimeState } = await import("./agents-5OPQKM5K.js");
1324
+ const { getAgentRuntimeState } = await import("./agents-DMPT32H7.js");
1075
1325
  const tmuxSession = getTmuxSessionName(name, projectKey);
1076
1326
  const runtimeState = getAgentRuntimeState(tmuxSession);
1077
1327
  let state;
@@ -1156,9 +1406,9 @@ Say: "I am the ${name} specialist, ready and waiting for tasks."`;
1156
1406
  await execAsync(`mkdir -p "${agentDir}"`, { encoding: "utf-8" });
1157
1407
  const promptFile = join4(agentDir, "identity-prompt.md");
1158
1408
  const launcherScript = join4(agentDir, "launcher.sh");
1159
- writeFileSync(promptFile, identityPrompt);
1409
+ writeFileSync2(promptFile, identityPrompt);
1160
1410
  const newSessionId = randomUUID();
1161
- writeFileSync(launcherScript, `#!/bin/bash
1411
+ writeFileSync2(launcherScript, `#!/bin/bash
1162
1412
  cd "${cwd}"
1163
1413
  prompt=$(cat "${promptFile}")
1164
1414
  exec claude --dangerously-skip-permissions --session-id "${newSessionId}" --model ${model} "$prompt"
@@ -1225,6 +1475,20 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
1225
1475
  const tmuxSession = getTmuxSessionName(name);
1226
1476
  const sessionId = getSessionId(name);
1227
1477
  const wasAlreadyRunning = await isRunning(name);
1478
+ if (wasAlreadyRunning && !options.skipBusyGuard) {
1479
+ const { getAgentRuntimeState } = await import("./agents-DMPT32H7.js");
1480
+ const runtimeState = getAgentRuntimeState(tmuxSession);
1481
+ if (runtimeState?.state === "active") {
1482
+ console.warn(`[specialist] ${name} is busy (working on ${runtimeState.currentIssue}), refusing to interrupt`);
1483
+ return {
1484
+ success: false,
1485
+ message: `Specialist ${name} is busy (working on ${runtimeState.currentIssue}). Use wakeSpecialistOrQueue() instead.`,
1486
+ tmuxSession,
1487
+ wasAlreadyRunning: true,
1488
+ error: "specialist_busy"
1489
+ };
1490
+ }
1491
+ }
1228
1492
  if (!wasAlreadyRunning) {
1229
1493
  if (!startIfNotRunning) {
1230
1494
  return {
@@ -1290,11 +1554,11 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
1290
1554
  const isLargePrompt = taskPrompt.length > 500 || taskPrompt.includes("\n");
1291
1555
  let messageToSend;
1292
1556
  if (isLargePrompt) {
1293
- if (!existsSync3(TASKS_DIR)) {
1294
- mkdirSync2(TASKS_DIR, { recursive: true });
1557
+ if (!existsSync4(TASKS_DIR)) {
1558
+ mkdirSync3(TASKS_DIR, { recursive: true });
1295
1559
  }
1296
1560
  const taskFile = join4(TASKS_DIR, `${name}-${Date.now()}.md`);
1297
- writeFileSync(taskFile, taskPrompt, "utf-8");
1561
+ writeFileSync2(taskFile, taskPrompt, "utf-8");
1298
1562
  messageToSend = `Read and execute the task in: ${taskFile}`;
1299
1563
  } else {
1300
1564
  messageToSend = taskPrompt;
@@ -1318,7 +1582,7 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
1318
1582
  }
1319
1583
  }
1320
1584
  recordWake(name, sessionId || void 0);
1321
- const { saveAgentRuntimeState } = await import("./agents-5OPQKM5K.js");
1585
+ const { saveAgentRuntimeState } = await import("./agents-DMPT32H7.js");
1322
1586
  saveAgentRuntimeState(tmuxSession, {
1323
1587
  state: "active",
1324
1588
  lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1341,7 +1605,7 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
1341
1605
  };
1342
1606
  }
1343
1607
  }
1344
- async function wakeSpecialistWithTask(name, task) {
1608
+ async function wakeSpecialistWithTask(name, task, options = {}) {
1345
1609
  const apiPort = process.env.API_PORT || process.env.PORT || "3011";
1346
1610
  const apiUrl = process.env.DASHBOARD_URL || `http://localhost:${apiPort}`;
1347
1611
  let prompt;
@@ -1420,14 +1684,14 @@ CRITICAL: Do NOT delete the feature branch.`;
1420
1684
  if (totalChangedFiles === 0) {
1421
1685
  staleBranch = true;
1422
1686
  console.log(`[specialist] review-agent: stale branch detected for ${task.issueId} \u2014 0 files changed vs main`);
1423
- const { setReviewStatus } = await import("./review-status-TDPSOU5J.js");
1687
+ const { setReviewStatus } = await import("./review-status-XKUKZF6J.js");
1424
1688
  setReviewStatus(task.issueId.toUpperCase(), {
1425
1689
  reviewStatus: "passed",
1426
1690
  reviewNotes: "No changes to review \u2014 branch identical to main (already merged or stale)"
1427
1691
  });
1428
1692
  console.log(`[specialist] review-agent: auto-passed ${task.issueId} (stale branch)`);
1429
1693
  const tmuxSession = getTmuxSessionName("review-agent");
1430
- const { saveAgentRuntimeState } = await import("./agents-5OPQKM5K.js");
1694
+ const { saveAgentRuntimeState } = await import("./agents-DMPT32H7.js");
1431
1695
  saveAgentRuntimeState(tmuxSession, {
1432
1696
  state: "idle",
1433
1697
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
@@ -1532,7 +1796,7 @@ curl -s -X POST ${apiUrl}/api/specialists/test-agent/queue -H "Content-Type: app
1532
1796
  const testWorkspace = task.workspace || "unknown";
1533
1797
  const testGitInfo = await resolveWorkspaceGitInfo(task.workspace, task.branch);
1534
1798
  const testIsPolyrepo = testGitInfo.isPolyrepo;
1535
- const { extractTeamPrefix, findProjectByTeam } = await import("./projects-CFX3RTDL.js");
1799
+ const { extractTeamPrefix, findProjectByTeam } = await import("./projects-KVM3MN3Y.js");
1536
1800
  const testTeamPrefix = extractTeamPrefix(task.issueId);
1537
1801
  const testProjectConfig = testTeamPrefix ? findProjectByTeam(testTeamPrefix) : null;
1538
1802
  const testConfigs = testProjectConfig?.tests;
@@ -1666,12 +1930,12 @@ IMPORTANT: Do NOT hand off to merge-agent. Human clicks Merge button when ready.
1666
1930
  default:
1667
1931
  prompt = `Task for ${task.issueId}: Please process this task and report findings.`;
1668
1932
  }
1669
- return wakeSpecialist(name, prompt, { issueId: task.issueId });
1933
+ return wakeSpecialist(name, prompt, { issueId: task.issueId, skipBusyGuard: options.skipBusyGuard });
1670
1934
  }
1671
1935
  async function wakeSpecialistOrQueue(name, task, options = {}) {
1672
1936
  const { priority = "normal", source = "handoff" } = options;
1673
1937
  const running = await isRunning(name);
1674
- const { getAgentRuntimeState } = await import("./agents-5OPQKM5K.js");
1938
+ const { getAgentRuntimeState } = await import("./agents-DMPT32H7.js");
1675
1939
  const tmuxSession = getTmuxSessionName(name);
1676
1940
  const runtimeState = getAgentRuntimeState(tmuxSession);
1677
1941
  const idle = runtimeState?.state === "idle" || runtimeState?.state === "suspended";
@@ -1702,7 +1966,7 @@ async function wakeSpecialistOrQueue(name, task, options = {}) {
1702
1966
  };
1703
1967
  }
1704
1968
  }
1705
- const { saveAgentRuntimeState } = await import("./agents-5OPQKM5K.js");
1969
+ const { saveAgentRuntimeState } = await import("./agents-DMPT32H7.js");
1706
1970
  saveAgentRuntimeState(tmuxSession, {
1707
1971
  state: "active",
1708
1972
  lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1710,7 +1974,7 @@ async function wakeSpecialistOrQueue(name, task, options = {}) {
1710
1974
  });
1711
1975
  console.log(`[specialist] ${name} marked active (preventing concurrent wakes)`);
1712
1976
  try {
1713
- const wakeResult = await wakeSpecialistWithTask(name, task);
1977
+ const wakeResult = await wakeSpecialistWithTask(name, task, { skipBusyGuard: true });
1714
1978
  if (!wakeResult.success) {
1715
1979
  saveAgentRuntimeState(tmuxSession, {
1716
1980
  state: "idle",
@@ -1786,8 +2050,8 @@ function getNextSpecialistTask(specialistName) {
1786
2050
  }
1787
2051
  async function sendFeedbackToAgent(feedback) {
1788
2052
  const { fromSpecialist, toIssueId, summary, details } = feedback;
1789
- if (!existsSync3(FEEDBACK_DIR)) {
1790
- mkdirSync2(FEEDBACK_DIR, { recursive: true });
2053
+ if (!existsSync4(FEEDBACK_DIR)) {
2054
+ mkdirSync3(FEEDBACK_DIR, { recursive: true });
1791
2055
  }
1792
2056
  const fullFeedback = {
1793
2057
  ...feedback,
@@ -1796,13 +2060,13 @@ async function sendFeedbackToAgent(feedback) {
1796
2060
  };
1797
2061
  try {
1798
2062
  const line = JSON.stringify(fullFeedback) + "\n";
1799
- appendFileSync2(FEEDBACK_LOG, line, "utf-8");
2063
+ appendFileSync3(FEEDBACK_LOG, line, "utf-8");
1800
2064
  } catch (error) {
1801
2065
  console.error(`[specialist] Failed to log feedback:`, error);
1802
2066
  }
1803
2067
  const agentSession = `agent-${toIssueId.toLowerCase()}`;
1804
2068
  const feedbackMessage = formatFeedbackForAgent(fullFeedback);
1805
- const { writeFeedbackFile } = await import("./feedback-writer-VRMMWWTW.js");
2069
+ const { writeFeedbackFile } = await import("./feedback-writer-T43PI5S2.js");
1806
2070
  const specialistMap = {
1807
2071
  "review-agent": "review-agent",
1808
2072
  "test-agent": "test-agent",
@@ -1822,7 +2086,7 @@ async function sendFeedbackToAgent(feedback) {
1822
2086
  return false;
1823
2087
  }
1824
2088
  try {
1825
- const { messageAgent } = await import("./agents-5OPQKM5K.js");
2089
+ const { messageAgent } = await import("./agents-DMPT32H7.js");
1826
2090
  const msg = `SPECIALIST FEEDBACK: ${fromSpecialist} reported ${feedback.feedbackType.toUpperCase()} for ${toIssueId}.
1827
2091
  Read and address: ${fileResult.relativePath}`;
1828
2092
  await messageAgent(agentSession, msg);
@@ -1881,11 +2145,11 @@ ${details}
1881
2145
  return message;
1882
2146
  }
1883
2147
  function getPendingFeedback(issueId) {
1884
- if (!existsSync3(FEEDBACK_LOG)) {
2148
+ if (!existsSync4(FEEDBACK_LOG)) {
1885
2149
  return [];
1886
2150
  }
1887
2151
  try {
1888
- const content = readFileSync3(FEEDBACK_LOG, "utf-8");
2152
+ const content = readFileSync4(FEEDBACK_LOG, "utf-8");
1889
2153
  const lines = content.trim().split("\n").filter((l) => l.length > 0);
1890
2154
  const allFeedback = lines.map((line) => JSON.parse(line));
1891
2155
  return allFeedback.filter((f) => f.toIssueId.toLowerCase() === issueId.toLowerCase());
@@ -1904,11 +2168,11 @@ function getFeedbackStats() {
1904
2168
  byType: {},
1905
2169
  total: 0
1906
2170
  };
1907
- if (!existsSync3(FEEDBACK_LOG)) {
2171
+ if (!existsSync4(FEEDBACK_LOG)) {
1908
2172
  return stats;
1909
2173
  }
1910
2174
  try {
1911
- const content = readFileSync3(FEEDBACK_LOG, "utf-8");
2175
+ const content = readFileSync4(FEEDBACK_LOG, "utf-8");
1912
2176
  const lines = content.trim().split("\n").filter((l) => l.length > 0);
1913
2177
  for (const line of lines) {
1914
2178
  const feedback = JSON.parse(line);
@@ -1987,7 +2251,7 @@ __export(specialist_logs_exports, {
1987
2251
  listRunLogs: () => listRunLogs,
1988
2252
  parseLogMetadata: () => parseLogMetadata
1989
2253
  });
1990
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, appendFileSync as appendFileSync3, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
2254
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, appendFileSync as appendFileSync4, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
1991
2255
  import { join as join5, basename as basename3 } from "path";
1992
2256
  function getSpecialistsDir() {
1993
2257
  return join5(getPanopticonHome(), "specialists");
@@ -2005,8 +2269,8 @@ function getRunLogPath(projectKey, specialistType, runId) {
2005
2269
  }
2006
2270
  function ensureRunsDirectory(projectKey, specialistType) {
2007
2271
  const runsDir = getRunsDirectory(projectKey, specialistType);
2008
- if (!existsSync4(runsDir)) {
2009
- mkdirSync3(runsDir, { recursive: true });
2272
+ if (!existsSync5(runsDir)) {
2273
+ mkdirSync4(runsDir, { recursive: true });
2010
2274
  }
2011
2275
  }
2012
2276
  function createRunLog(projectKey, specialistType, issueId, contextSeed) {
@@ -2025,22 +2289,22 @@ ${contextSeed ? contextSeed : "[No context digest available]"}
2025
2289
 
2026
2290
  ## Session Transcript
2027
2291
  `;
2028
- writeFileSync2(filePath, header, "utf-8");
2292
+ writeFileSync3(filePath, header, "utf-8");
2029
2293
  return { runId, filePath };
2030
2294
  }
2031
2295
  function appendToRunLog(projectKey, specialistType, runId, content) {
2032
2296
  const filePath = getRunLogPath(projectKey, specialistType, runId);
2033
- if (!existsSync4(filePath)) {
2297
+ if (!existsSync5(filePath)) {
2034
2298
  throw new Error(`Run log not found: ${filePath}`);
2035
2299
  }
2036
- appendFileSync3(filePath, content, "utf-8");
2300
+ appendFileSync4(filePath, content, "utf-8");
2037
2301
  }
2038
2302
  function finalizeRunLog(projectKey, specialistType, runId, result) {
2039
2303
  const filePath = getRunLogPath(projectKey, specialistType, runId);
2040
- if (!existsSync4(filePath)) {
2304
+ if (!existsSync5(filePath)) {
2041
2305
  throw new Error(`Run log not found: ${filePath}`);
2042
2306
  }
2043
- const content = readFileSync4(filePath, "utf-8");
2307
+ const content = readFileSync5(filePath, "utf-8");
2044
2308
  const startMatch = content.match(/^Started: (.+)$/m);
2045
2309
  const startedAt = startMatch ? new Date(startMatch[1]) : /* @__PURE__ */ new Date();
2046
2310
  const finishedAt = /* @__PURE__ */ new Date();
@@ -2057,15 +2321,15 @@ ${result.notes ? `Notes: ${result.notes}` : ""}
2057
2321
  Duration: ${durationStr}
2058
2322
  Finished: ${finishedAt.toISOString()}
2059
2323
  `;
2060
- appendFileSync3(filePath, resultSection, "utf-8");
2324
+ appendFileSync4(filePath, resultSection, "utf-8");
2061
2325
  }
2062
2326
  function getRunLog(projectKey, specialistType, runId) {
2063
2327
  const filePath = getRunLogPath(projectKey, specialistType, runId);
2064
- if (!existsSync4(filePath)) {
2328
+ if (!existsSync5(filePath)) {
2065
2329
  return null;
2066
2330
  }
2067
2331
  try {
2068
- return readFileSync4(filePath, "utf-8");
2332
+ return readFileSync5(filePath, "utf-8");
2069
2333
  } catch (error) {
2070
2334
  console.error(`Failed to read run log ${runId}:`, error);
2071
2335
  return null;
@@ -2100,15 +2364,15 @@ function parseLogMetadata(logContent) {
2100
2364
  }
2101
2365
  function listRunLogs(projectKey, specialistType, options = {}) {
2102
2366
  const runsDir = getRunsDirectory(projectKey, specialistType);
2103
- if (!existsSync4(runsDir)) {
2367
+ if (!existsSync5(runsDir)) {
2104
2368
  return [];
2105
2369
  }
2106
2370
  try {
2107
- const files = readdirSync3(runsDir).filter((f) => f.endsWith(".log")).map((f) => {
2371
+ const files = readdirSync4(runsDir).filter((f) => f.endsWith(".log")).map((f) => {
2108
2372
  const filePath = join5(runsDir, f);
2109
2373
  const stats = statSync2(filePath);
2110
2374
  const runId = basename3(f, ".log");
2111
- const content = readFileSync4(filePath, "utf-8");
2375
+ const content = readFileSync5(filePath, "utf-8");
2112
2376
  const metadata = parseLogMetadata(content);
2113
2377
  return {
2114
2378
  runId,
@@ -2181,7 +2445,7 @@ function isRunLogActive(projectKey, specialistType, runId) {
2181
2445
  }
2182
2446
  function getRunLogSize(projectKey, specialistType, runId) {
2183
2447
  const filePath = getRunLogPath(projectKey, specialistType, runId);
2184
- if (!existsSync4(filePath)) {
2448
+ if (!existsSync5(filePath)) {
2185
2449
  return null;
2186
2450
  }
2187
2451
  try {
@@ -2238,6 +2502,19 @@ var init_specialist_logs = __esm({
2238
2502
  });
2239
2503
 
2240
2504
  export {
2505
+ readTodayCosts,
2506
+ readIssueCosts,
2507
+ summarizeCosts,
2508
+ getDailySummary,
2509
+ getWeeklySummary,
2510
+ getMonthlySummary,
2511
+ createBudget,
2512
+ getAllBudgets,
2513
+ checkBudget,
2514
+ deleteBudget,
2515
+ generateReport,
2516
+ formatCost,
2517
+ init_cost,
2241
2518
  getProjectDirs,
2242
2519
  getSessionFiles,
2243
2520
  parseClaudeSession,
@@ -2316,4 +2593,4 @@ export {
2316
2593
  getFeedbackStats,
2317
2594
  init_specialists
2318
2595
  };
2319
- //# sourceMappingURL=chunk-OWHXCGVO.js.map
2596
+ //# sourceMappingURL=chunk-2V2DQ3IX.js.map