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.
- package/dist/{agents-5OPQKM5K.js → agents-DMPT32H7.js} +7 -6
- package/dist/{chunk-OWHXCGVO.js → chunk-2V2DQ3IX.js} +378 -101
- package/dist/chunk-2V2DQ3IX.js.map +1 -0
- package/dist/{chunk-F5555J3A.js → chunk-4HST45MO.js} +13 -27
- package/dist/chunk-4HST45MO.js.map +1 -0
- package/dist/{chunk-YLPSQAM2.js → chunk-565HZ6VV.js} +2 -2
- package/dist/chunk-6N2KBSJA.js +452 -0
- package/dist/chunk-6N2KBSJA.js.map +1 -0
- package/dist/{chunk-VHKSS7QX.js → chunk-D67AQTHF.js} +21 -19
- package/dist/chunk-D67AQTHF.js.map +1 -0
- package/dist/{chunk-4YSYJ4HM.js → chunk-DFNVHK3N.js} +2 -2
- package/dist/{chunk-7SN4L4PH.js → chunk-HOGYHJ2G.js} +2 -2
- package/dist/{chunk-FTCPTHIJ.js → chunk-HRU7S4TA.js} +24 -7
- package/dist/chunk-HRU7S4TA.js.map +1 -0
- package/dist/{chunk-NLQRED36.js → chunk-KBHRXV5T.js} +3 -3
- package/dist/chunk-KBHRXV5T.js.map +1 -0
- package/dist/{chunk-CWELWPWQ.js → chunk-MOPGR3CL.js} +1 -1
- package/dist/chunk-MOPGR3CL.js.map +1 -0
- package/dist/{chunk-2V4NF7J2.js → chunk-RLZQB7HS.js} +2 -2
- package/dist/chunk-RLZQB7HS.js.map +1 -0
- package/dist/{chunk-76F6DSVS.js → chunk-T7BBPDEJ.js} +11 -7
- package/dist/chunk-T7BBPDEJ.js.map +1 -0
- package/dist/chunk-USYP2SBE.js +317 -0
- package/dist/chunk-USYP2SBE.js.map +1 -0
- package/dist/{chunk-JM6V62LT.js → chunk-ZDNQFWR5.js} +2 -2
- package/dist/{chunk-JM6V62LT.js.map → chunk-ZDNQFWR5.js.map} +1 -1
- package/dist/{chunk-HJSM6E6U.js → chunk-ZP6EWSZV.js} +29 -322
- package/dist/chunk-ZP6EWSZV.js.map +1 -0
- package/dist/{chunk-PELXV435.js → chunk-ZTYHZMEC.js} +2 -2
- package/dist/chunk-ZTYHZMEC.js.map +1 -0
- package/dist/cli/index.js +1381 -777
- package/dist/cli/index.js.map +1 -1
- package/dist/config-yaml-OVZLKFMA.js +18 -0
- package/dist/dashboard/prompts/merge-agent.md +7 -5
- package/dist/dashboard/prompts/review-agent.md +12 -1
- package/dist/dashboard/prompts/test-agent.md +3 -1
- package/dist/dashboard/public/assets/index-BJKEp64j.css +32 -0
- package/dist/dashboard/public/assets/index-CgJjqjAV.js +767 -0
- package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-BflQw4A9.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-DjKNqYRj.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-DxxdqCpr.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-VcznFIpX.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-D6zpsUhD.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-DUi7WF5p.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
- package/dist/dashboard/public/index.html +5 -3
- package/dist/dashboard/server.js +4647 -2723
- package/dist/{feedback-writer-VRMMWWTW.js → feedback-writer-T43PI5S2.js} +2 -2
- package/dist/{hume-WMAUBBV2.js → hume-CKJJ3OUU.js} +3 -3
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/{projects-CFX3RTDL.js → projects-KVM3MN3Y.js} +2 -2
- package/dist/{remote-agents-TFSMW7GN.js → remote-agents-ULPD6C5U.js} +3 -3
- package/dist/{remote-workspace-7FPGF2RM.js → remote-workspace-XX6ARE6I.js} +3 -3
- package/dist/{review-status-TDPSOU5J.js → review-status-XKUKZF6J.js} +3 -2
- package/dist/{specialist-context-WGUUYDWY.js → specialist-context-53AWO6AE.js} +6 -5
- package/dist/{specialist-context-WGUUYDWY.js.map → specialist-context-53AWO6AE.js.map} +1 -1
- package/dist/{specialist-logs-XJB5TCKJ.js → specialist-logs-QREUJ4HN.js} +6 -5
- package/dist/{specialists-5LBRHYFA.js → specialists-2DBBXRCK.js} +6 -5
- package/dist/{traefik-WFMQX2LY.js → traefik-5GL3Q7DJ.js} +3 -3
- package/dist/{tunnel-W2GZBLEV.js → tunnel-BKC7KLBX.js} +3 -3
- package/dist/{workspace-manager-E434Z45T.js → workspace-manager-ALBR62AS.js} +5 -5
- package/dist/workspace-manager-ALBR62AS.js.map +1 -0
- package/package.json +1 -1
- package/scripts/record-cost-event.js +8424 -42
- package/scripts/work-agent-stop-hook +221 -24
- package/dist/chunk-2V4NF7J2.js.map +0 -1
- package/dist/chunk-76F6DSVS.js.map +0 -1
- package/dist/chunk-CWELWPWQ.js.map +0 -1
- package/dist/chunk-F5555J3A.js.map +0 -1
- package/dist/chunk-FTCPTHIJ.js.map +0 -1
- package/dist/chunk-HJSM6E6U.js.map +0 -1
- package/dist/chunk-NLQRED36.js.map +0 -1
- package/dist/chunk-OWHXCGVO.js.map +0 -1
- package/dist/chunk-PELXV435.js.map +0 -1
- package/dist/chunk-VHKSS7QX.js.map +0 -1
- package/dist/chunk-YGJ54GW2.js +0 -96
- package/dist/chunk-YGJ54GW2.js.map +0 -1
- package/dist/dashboard/public/assets/index-Ce6q21Fm.js +0 -743
- package/dist/dashboard/public/assets/index-NzpI0ItZ.css +0 -32
- package/dist/git-utils-I2UDKNZH.js +0 -131
- package/dist/git-utils-I2UDKNZH.js.map +0 -1
- /package/dist/{agents-5OPQKM5K.js.map → agents-DMPT32H7.js.map} +0 -0
- /package/dist/{chunk-YLPSQAM2.js.map → chunk-565HZ6VV.js.map} +0 -0
- /package/dist/{chunk-4YSYJ4HM.js.map → chunk-DFNVHK3N.js.map} +0 -0
- /package/dist/{chunk-7SN4L4PH.js.map → chunk-HOGYHJ2G.js.map} +0 -0
- /package/dist/{hume-WMAUBBV2.js.map → config-yaml-OVZLKFMA.js.map} +0 -0
- /package/dist/{feedback-writer-VRMMWWTW.js.map → feedback-writer-T43PI5S2.js.map} +0 -0
- /package/dist/{projects-CFX3RTDL.js.map → hume-CKJJ3OUU.js.map} +0 -0
- /package/dist/{remote-agents-TFSMW7GN.js.map → projects-KVM3MN3Y.js.map} +0 -0
- /package/dist/{review-status-TDPSOU5J.js.map → remote-agents-ULPD6C5U.js.map} +0 -0
- /package/dist/{remote-workspace-7FPGF2RM.js.map → remote-workspace-XX6ARE6I.js.map} +0 -0
- /package/dist/{specialist-logs-XJB5TCKJ.js.map → review-status-XKUKZF6J.js.map} +0 -0
- /package/dist/{specialists-5LBRHYFA.js.map → specialist-logs-QREUJ4HN.js.map} +0 -0
- /package/dist/{traefik-WFMQX2LY.js.map → specialists-2DBBXRCK.js.map} +0 -0
- /package/dist/{tunnel-W2GZBLEV.js.map → traefik-5GL3Q7DJ.js.map} +0 -0
- /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-
|
|
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-
|
|
26
|
+
} from "./chunk-USYP2SBE.js";
|
|
27
27
|
import {
|
|
28
28
|
init_projects,
|
|
29
29
|
projects_exports
|
|
30
|
-
} from "./chunk-
|
|
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 (!
|
|
330
|
+
if (!existsSync2(CLAUDE_PROJECTS_DIR)) {
|
|
118
331
|
return [];
|
|
119
332
|
}
|
|
120
|
-
return
|
|
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 (!
|
|
342
|
+
if (!existsSync2(projectDir)) {
|
|
130
343
|
return [];
|
|
131
344
|
}
|
|
132
|
-
return
|
|
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 (!
|
|
399
|
+
if (!existsSync2(sessionFile)) {
|
|
187
400
|
return null;
|
|
188
401
|
}
|
|
189
|
-
const content =
|
|
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
|
|
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 (!
|
|
305
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
619
|
+
if (existsSync4(join4(workspace, ".git"))) {
|
|
407
620
|
gitDirs.push(workspace);
|
|
408
621
|
} else {
|
|
409
622
|
try {
|
|
410
|
-
const entries =
|
|
623
|
+
const entries = readdirSync3(workspace, { withFileTypes: true });
|
|
411
624
|
for (const entry of entries) {
|
|
412
|
-
if (entry.isDirectory() &&
|
|
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 (!
|
|
454
|
-
|
|
666
|
+
if (!existsSync4(SPECIALISTS_DIR)) {
|
|
667
|
+
mkdirSync3(SPECIALISTS_DIR, { recursive: true });
|
|
455
668
|
}
|
|
456
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
529
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
557
|
-
const
|
|
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
|
-
|
|
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 (!
|
|
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-
|
|
844
|
+
const { loadContextDigest } = await import("./specialist-context-53AWO6AE.js");
|
|
626
845
|
const contextDigest = loadContextDigest(projectKey, specialistType);
|
|
627
|
-
const { createRunLog: createRunLog2 } = await import("./specialist-logs-
|
|
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 =
|
|
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
|
-
|
|
899
|
+
writeFileSync2(promptFile, taskPrompt);
|
|
660
900
|
const launcherScript = join4(agentDir, "launcher.sh");
|
|
661
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
896
|
-
const { getSpecialistRetention } = await import("./projects-
|
|
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 (!
|
|
915
|
-
|
|
1154
|
+
if (!existsSync4(runsDir)) {
|
|
1155
|
+
mkdirSync3(runsDir, { recursive: true });
|
|
916
1156
|
}
|
|
917
|
-
if (!
|
|
918
|
-
|
|
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 =
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
1409
|
+
writeFileSync2(promptFile, identityPrompt);
|
|
1160
1410
|
const newSessionId = randomUUID();
|
|
1161
|
-
|
|
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 (!
|
|
1294
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 (!
|
|
1790
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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 (!
|
|
2148
|
+
if (!existsSync4(FEEDBACK_LOG)) {
|
|
1885
2149
|
return [];
|
|
1886
2150
|
}
|
|
1887
2151
|
try {
|
|
1888
|
-
const content =
|
|
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 (!
|
|
2171
|
+
if (!existsSync4(FEEDBACK_LOG)) {
|
|
1908
2172
|
return stats;
|
|
1909
2173
|
}
|
|
1910
2174
|
try {
|
|
1911
|
-
const content =
|
|
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
|
|
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 (!
|
|
2009
|
-
|
|
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
|
-
|
|
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 (!
|
|
2297
|
+
if (!existsSync5(filePath)) {
|
|
2034
2298
|
throw new Error(`Run log not found: ${filePath}`);
|
|
2035
2299
|
}
|
|
2036
|
-
|
|
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 (!
|
|
2304
|
+
if (!existsSync5(filePath)) {
|
|
2041
2305
|
throw new Error(`Run log not found: ${filePath}`);
|
|
2042
2306
|
}
|
|
2043
|
-
const content =
|
|
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
|
-
|
|
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 (!
|
|
2328
|
+
if (!existsSync5(filePath)) {
|
|
2065
2329
|
return null;
|
|
2066
2330
|
}
|
|
2067
2331
|
try {
|
|
2068
|
-
return
|
|
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 (!
|
|
2367
|
+
if (!existsSync5(runsDir)) {
|
|
2104
2368
|
return [];
|
|
2105
2369
|
}
|
|
2106
2370
|
try {
|
|
2107
|
-
const files =
|
|
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 =
|
|
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 (!
|
|
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-
|
|
2596
|
+
//# sourceMappingURL=chunk-2V2DQ3IX.js.map
|