panopticon-cli 0.4.31 → 0.4.32
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-GQDAKTEQ.js → agents-BDFHF4T3.js} +2 -2
- package/dist/{chunk-TMXN7THF.js → chunk-2NIAOCIC.js} +14 -14
- package/dist/{chunk-HNEWTIR3.js → chunk-XP2DXWYP.js} +13 -6
- package/dist/chunk-XP2DXWYP.js.map +1 -0
- package/dist/cli/index.js +15 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/public/assets/{index-izWbAt7V.js → index-ClYqpcAJ.js} +77 -77
- package/dist/dashboard/public/index.html +1 -1
- package/dist/dashboard/server.js +96 -27
- package/dist/{specialist-context-6SE5VRRC.js → specialist-context-N32QBNNQ.js} +2 -2
- package/dist/{specialist-logs-EXLOQHQ2.js → specialist-logs-GF3YV4KL.js} +2 -2
- package/dist/{specialists-BRUHPAXE.js → specialists-JBIW6MP4.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-HNEWTIR3.js.map +0 -1
- /package/dist/{agents-GQDAKTEQ.js.map → agents-BDFHF4T3.js.map} +0 -0
- /package/dist/{chunk-TMXN7THF.js.map → chunk-2NIAOCIC.js.map} +0 -0
- /package/dist/{specialist-context-6SE5VRRC.js.map → specialist-context-N32QBNNQ.js.map} +0 -0
- /package/dist/{specialist-logs-EXLOQHQ2.js.map → specialist-logs-GF3YV4KL.js.map} +0 -0
- /package/dist/{specialists-BRUHPAXE.js.map → specialists-JBIW6MP4.js.map} +0 -0
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
saveSessionId,
|
|
18
18
|
spawnAgent,
|
|
19
19
|
stopAgent
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-XP2DXWYP.js";
|
|
21
21
|
import "./chunk-VIWUCJ4V.js";
|
|
22
22
|
import "./chunk-LYSBSZYV.js";
|
|
23
23
|
import "./chunk-BBCUK6N2.js";
|
|
@@ -44,4 +44,4 @@ export {
|
|
|
44
44
|
spawnAgent,
|
|
45
45
|
stopAgent
|
|
46
46
|
};
|
|
47
|
-
//# sourceMappingURL=agents-
|
|
47
|
+
//# sourceMappingURL=agents-BDFHF4T3.js.map
|
|
@@ -553,9 +553,9 @@ function recordWake(name, sessionId) {
|
|
|
553
553
|
}
|
|
554
554
|
async function spawnEphemeralSpecialist(projectKey, specialistType, task) {
|
|
555
555
|
ensureProjectSpecialistDir(projectKey, specialistType);
|
|
556
|
-
const { loadContextDigest } = await import("./specialist-context-
|
|
556
|
+
const { loadContextDigest } = await import("./specialist-context-N32QBNNQ.js");
|
|
557
557
|
const contextDigest = loadContextDigest(projectKey, specialistType);
|
|
558
|
-
const { createRunLog: createRunLog2 } = await import("./specialist-logs-
|
|
558
|
+
const { createRunLog: createRunLog2 } = await import("./specialist-logs-GF3YV4KL.js");
|
|
559
559
|
const { runId, filePath: logFilePath } = createRunLog2(
|
|
560
560
|
projectKey,
|
|
561
561
|
specialistType,
|
|
@@ -596,7 +596,7 @@ echo "## Specialist completed task"
|
|
|
596
596
|
`tmux new-session -d -s "${tmuxSession}" "bash '${launcherScript}'"`,
|
|
597
597
|
{ encoding: "utf-8" }
|
|
598
598
|
);
|
|
599
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
599
|
+
const { saveAgentRuntimeState } = await import("./agents-BDFHF4T3.js");
|
|
600
600
|
saveAgentRuntimeState(tmuxSession, {
|
|
601
601
|
state: "active",
|
|
602
602
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -781,7 +781,7 @@ async function terminateSpecialist(projectKey, specialistType) {
|
|
|
781
781
|
console.error(`[specialist] Failed to kill tmux session ${tmuxSession}:`, error);
|
|
782
782
|
}
|
|
783
783
|
if (metadata.currentRun) {
|
|
784
|
-
const { finalizeRunLog: finalizeRunLog2 } = await import("./specialist-logs-
|
|
784
|
+
const { finalizeRunLog: finalizeRunLog2 } = await import("./specialist-logs-GF3YV4KL.js");
|
|
785
785
|
try {
|
|
786
786
|
finalizeRunLog2(projectKey, specialistType, metadata.currentRun, {
|
|
787
787
|
status: metadata.lastRunStatus || "incomplete",
|
|
@@ -794,19 +794,19 @@ async function terminateSpecialist(projectKey, specialistType) {
|
|
|
794
794
|
}
|
|
795
795
|
const key = `${projectKey}-${specialistType}`;
|
|
796
796
|
gracePeriodStates.delete(key);
|
|
797
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
797
|
+
const { saveAgentRuntimeState } = await import("./agents-BDFHF4T3.js");
|
|
798
798
|
saveAgentRuntimeState(tmuxSession, {
|
|
799
799
|
state: "suspended",
|
|
800
800
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
801
801
|
});
|
|
802
|
-
const { scheduleDigestGeneration } = await import("./specialist-context-
|
|
802
|
+
const { scheduleDigestGeneration } = await import("./specialist-context-N32QBNNQ.js");
|
|
803
803
|
scheduleDigestGeneration(projectKey, specialistType);
|
|
804
804
|
scheduleLogCleanup(projectKey, specialistType);
|
|
805
805
|
}
|
|
806
806
|
function scheduleLogCleanup(projectKey, specialistType) {
|
|
807
807
|
Promise.resolve().then(async () => {
|
|
808
808
|
try {
|
|
809
|
-
const { cleanupOldLogs: cleanupOldLogs2 } = await import("./specialist-logs-
|
|
809
|
+
const { cleanupOldLogs: cleanupOldLogs2 } = await import("./specialist-logs-GF3YV4KL.js");
|
|
810
810
|
const { getSpecialistRetention } = await import("./projects-VXRUCMLM.js");
|
|
811
811
|
const retention = getSpecialistRetention(projectKey);
|
|
812
812
|
const deleted = cleanupOldLogs2(projectKey, specialistType, { maxDays: retention.max_days, maxRuns: retention.max_runs });
|
|
@@ -985,7 +985,7 @@ async function getSpecialistStatus(name, projectKey) {
|
|
|
985
985
|
const sessionId = getSessionId(name);
|
|
986
986
|
const running = await isRunning(name, projectKey);
|
|
987
987
|
const contextTokens = countContextTokens(name);
|
|
988
|
-
const { getAgentRuntimeState } = await import("./agents-
|
|
988
|
+
const { getAgentRuntimeState } = await import("./agents-BDFHF4T3.js");
|
|
989
989
|
const tmuxSession = getTmuxSessionName(name, projectKey);
|
|
990
990
|
const runtimeState = getAgentRuntimeState(tmuxSession);
|
|
991
991
|
let state;
|
|
@@ -1177,7 +1177,7 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
|
|
|
1177
1177
|
await sendKeysAsync(tmuxSession, taskPrompt);
|
|
1178
1178
|
}
|
|
1179
1179
|
recordWake(name, sessionId || void 0);
|
|
1180
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1180
|
+
const { saveAgentRuntimeState } = await import("./agents-BDFHF4T3.js");
|
|
1181
1181
|
saveAgentRuntimeState(tmuxSession, {
|
|
1182
1182
|
state: "active",
|
|
1183
1183
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1247,7 +1247,7 @@ Use the send-feedback-to-agent skill to report findings back to the issue agent.
|
|
|
1247
1247
|
});
|
|
1248
1248
|
console.log(`[specialist] review-agent: auto-passed ${task.issueId} (stale branch)`);
|
|
1249
1249
|
const tmuxSession = getTmuxSessionName("review-agent");
|
|
1250
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1250
|
+
const { saveAgentRuntimeState } = await import("./agents-BDFHF4T3.js");
|
|
1251
1251
|
saveAgentRuntimeState(tmuxSession, {
|
|
1252
1252
|
state: "idle",
|
|
1253
1253
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1414,7 +1414,7 @@ IMPORTANT: Do NOT hand off to merge-agent. Human clicks Merge button when ready.
|
|
|
1414
1414
|
async function wakeSpecialistOrQueue(name, task, options = {}) {
|
|
1415
1415
|
const { priority = "normal", source = "handoff" } = options;
|
|
1416
1416
|
const running = await isRunning(name);
|
|
1417
|
-
const { getAgentRuntimeState } = await import("./agents-
|
|
1417
|
+
const { getAgentRuntimeState } = await import("./agents-BDFHF4T3.js");
|
|
1418
1418
|
const tmuxSession = getTmuxSessionName(name);
|
|
1419
1419
|
const runtimeState = getAgentRuntimeState(tmuxSession);
|
|
1420
1420
|
const idle = runtimeState?.state === "idle" || runtimeState?.state === "suspended";
|
|
@@ -1445,7 +1445,7 @@ async function wakeSpecialistOrQueue(name, task, options = {}) {
|
|
|
1445
1445
|
};
|
|
1446
1446
|
}
|
|
1447
1447
|
}
|
|
1448
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1448
|
+
const { saveAgentRuntimeState } = await import("./agents-BDFHF4T3.js");
|
|
1449
1449
|
saveAgentRuntimeState(tmuxSession, {
|
|
1450
1450
|
state: "active",
|
|
1451
1451
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1564,7 +1564,7 @@ async function sendFeedbackToAgent(feedback) {
|
|
|
1564
1564
|
return false;
|
|
1565
1565
|
}
|
|
1566
1566
|
try {
|
|
1567
|
-
const { messageAgent } = await import("./agents-
|
|
1567
|
+
const { messageAgent } = await import("./agents-BDFHF4T3.js");
|
|
1568
1568
|
const msg = `SPECIALIST FEEDBACK: ${fromSpecialist} reported ${feedback.feedbackType.toUpperCase()} for ${toIssueId}.
|
|
1569
1569
|
Read and address: ${fileResult.relativePath}`;
|
|
1570
1570
|
await messageAgent(agentSession, msg);
|
|
@@ -2055,4 +2055,4 @@ export {
|
|
|
2055
2055
|
getFeedbackStats,
|
|
2056
2056
|
init_specialists
|
|
2057
2057
|
};
|
|
2058
|
-
//# sourceMappingURL=chunk-
|
|
2058
|
+
//# sourceMappingURL=chunk-2NIAOCIC.js.map
|
|
@@ -351,6 +351,12 @@ import { join as join3 } from "path";
|
|
|
351
351
|
import { homedir } from "os";
|
|
352
352
|
import { exec } from "child_process";
|
|
353
353
|
import { promisify } from "util";
|
|
354
|
+
function normalizeAgentId(agentId) {
|
|
355
|
+
if (AGENT_PREFIXES.some((p) => agentId.startsWith(p))) {
|
|
356
|
+
return agentId;
|
|
357
|
+
}
|
|
358
|
+
return `agent-${agentId.toLowerCase()}`;
|
|
359
|
+
}
|
|
354
360
|
function getProviderEnvForModel(model) {
|
|
355
361
|
const provider = getProviderForModel(model);
|
|
356
362
|
if (provider.name === "anthropic") return {};
|
|
@@ -633,7 +639,7 @@ function listRunningAgents() {
|
|
|
633
639
|
return agents;
|
|
634
640
|
}
|
|
635
641
|
function stopAgent(agentId) {
|
|
636
|
-
const normalizedId =
|
|
642
|
+
const normalizedId = normalizeAgentId(agentId);
|
|
637
643
|
if (sessionExists(normalizedId)) {
|
|
638
644
|
try {
|
|
639
645
|
const output = capturePane(normalizedId, 5e3);
|
|
@@ -654,7 +660,7 @@ function stopAgent(agentId) {
|
|
|
654
660
|
}
|
|
655
661
|
}
|
|
656
662
|
async function messageAgent(agentId, message) {
|
|
657
|
-
const normalizedId =
|
|
663
|
+
const normalizedId = normalizeAgentId(agentId);
|
|
658
664
|
const runtimeState = getAgentRuntimeState(normalizedId);
|
|
659
665
|
if (runtimeState?.state === "suspended") {
|
|
660
666
|
console.log(`[agents] Auto-resuming suspended agent ${normalizedId} to deliver message`);
|
|
@@ -697,7 +703,7 @@ ${message}
|
|
|
697
703
|
);
|
|
698
704
|
}
|
|
699
705
|
async function resumeAgent(agentId, message) {
|
|
700
|
-
const normalizedId =
|
|
706
|
+
const normalizedId = normalizeAgentId(agentId);
|
|
701
707
|
const runtimeState = getAgentRuntimeState(normalizedId);
|
|
702
708
|
if (!runtimeState || runtimeState.state !== "suspended") {
|
|
703
709
|
return {
|
|
@@ -768,7 +774,7 @@ function detectCrashedAgents() {
|
|
|
768
774
|
);
|
|
769
775
|
}
|
|
770
776
|
function recoverAgent(agentId) {
|
|
771
|
-
const normalizedId =
|
|
777
|
+
const normalizedId = normalizeAgentId(agentId);
|
|
772
778
|
const state = getAgentState(normalizedId);
|
|
773
779
|
if (!state) {
|
|
774
780
|
return null;
|
|
@@ -900,7 +906,7 @@ function writeTaskCache(agentId, issueId) {
|
|
|
900
906
|
}, null, 2)
|
|
901
907
|
);
|
|
902
908
|
}
|
|
903
|
-
var execAsync;
|
|
909
|
+
var execAsync, AGENT_PREFIXES;
|
|
904
910
|
var init_agents = __esm({
|
|
905
911
|
"src/lib/agents.ts"() {
|
|
906
912
|
init_esm_shims();
|
|
@@ -915,6 +921,7 @@ var init_agents = __esm({
|
|
|
915
921
|
init_config();
|
|
916
922
|
init_factory();
|
|
917
923
|
execAsync = promisify(exec);
|
|
924
|
+
AGENT_PREFIXES = ["agent-", "planning-"];
|
|
918
925
|
}
|
|
919
926
|
});
|
|
920
927
|
|
|
@@ -945,4 +952,4 @@ export {
|
|
|
945
952
|
autoRecoverAgents,
|
|
946
953
|
init_agents
|
|
947
954
|
};
|
|
948
|
-
//# sourceMappingURL=chunk-
|
|
955
|
+
//# sourceMappingURL=chunk-XP2DXWYP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/cv.ts","../src/lib/cloister/config.ts","../src/lib/agents.ts"],"sourcesContent":["/**\n * Agent CV (Work History) System\n *\n * Tracks agent performance over time to enable capability-based routing.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { AGENTS_DIR } from './paths.js';\n\nexport interface WorkEntry {\n issueId: string;\n startedAt: string;\n completedAt?: string;\n outcome: 'success' | 'failed' | 'abandoned' | 'in_progress';\n duration?: number; // minutes\n skills?: string[];\n failureReason?: string;\n commits?: number;\n linesChanged?: number;\n}\n\nexport interface AgentCV {\n agentId: string;\n createdAt: string;\n lastActive: string;\n runtime: string;\n model: string;\n stats: {\n totalIssues: number;\n successCount: number;\n failureCount: number;\n abandonedCount: number;\n avgDuration: number; // minutes\n successRate: number; // 0-1\n };\n skillsUsed: string[];\n recentWork: WorkEntry[];\n}\n\nfunction getCVFile(agentId: string): string {\n return join(AGENTS_DIR, agentId, 'cv.json');\n}\n\n/**\n * Get or create an agent's CV\n */\nexport function getAgentCV(agentId: string): AgentCV {\n const cvFile = getCVFile(agentId);\n\n if (existsSync(cvFile)) {\n try {\n return JSON.parse(readFileSync(cvFile, 'utf-8'));\n } catch {}\n }\n\n // Create new CV\n const cv: AgentCV = {\n agentId,\n createdAt: new Date().toISOString(),\n lastActive: new Date().toISOString(),\n runtime: 'claude',\n model: 'sonnet',\n stats: {\n totalIssues: 0,\n successCount: 0,\n failureCount: 0,\n abandonedCount: 0,\n avgDuration: 0,\n successRate: 0,\n },\n skillsUsed: [],\n recentWork: [],\n };\n\n saveAgentCV(cv);\n return cv;\n}\n\n/**\n * Save an agent's CV\n */\nexport function saveAgentCV(cv: AgentCV): void {\n const dir = join(AGENTS_DIR, cv.agentId);\n mkdirSync(dir, { recursive: true });\n writeFileSync(getCVFile(cv.agentId), JSON.stringify(cv, null, 2));\n}\n\n/**\n * Start tracking work for an agent\n */\nexport function startWork(agentId: string, issueId: string, skills?: string[]): void {\n const cv = getAgentCV(agentId);\n\n const entry: WorkEntry = {\n issueId,\n startedAt: new Date().toISOString(),\n outcome: 'in_progress',\n skills,\n };\n\n cv.recentWork.unshift(entry);\n cv.stats.totalIssues++;\n cv.lastActive = new Date().toISOString();\n\n // Track skills\n if (skills) {\n for (const skill of skills) {\n if (!cv.skillsUsed.includes(skill)) {\n cv.skillsUsed.push(skill);\n }\n }\n }\n\n // Keep only last 50 entries\n if (cv.recentWork.length > 50) {\n cv.recentWork = cv.recentWork.slice(0, 50);\n }\n\n saveAgentCV(cv);\n}\n\n/**\n * Complete work for an agent\n */\nexport function completeWork(\n agentId: string,\n issueId: string,\n outcome: 'success' | 'failed' | 'abandoned',\n details?: { commits?: number; linesChanged?: number; failureReason?: string }\n): void {\n const cv = getAgentCV(agentId);\n\n // Find the work entry\n const entry = cv.recentWork.find(\n (w) => w.issueId === issueId && w.outcome === 'in_progress'\n );\n\n if (entry) {\n entry.outcome = outcome;\n entry.completedAt = new Date().toISOString();\n entry.duration = Math.round(\n (new Date().getTime() - new Date(entry.startedAt).getTime()) / (1000 * 60)\n );\n if (details?.commits) entry.commits = details.commits;\n if (details?.linesChanged) entry.linesChanged = details.linesChanged;\n if (details?.failureReason) entry.failureReason = details.failureReason;\n }\n\n // Update stats\n if (outcome === 'success') {\n cv.stats.successCount++;\n } else if (outcome === 'failed') {\n cv.stats.failureCount++;\n } else if (outcome === 'abandoned') {\n cv.stats.abandonedCount++;\n }\n\n // Calculate success rate\n const completed = cv.stats.successCount + cv.stats.failureCount + cv.stats.abandonedCount;\n cv.stats.successRate = completed > 0 ? cv.stats.successCount / completed : 0;\n\n // Calculate average duration (only from completed work)\n const completedEntries = cv.recentWork.filter(\n (w) => w.duration !== undefined && w.outcome !== 'in_progress'\n );\n if (completedEntries.length > 0) {\n const totalDuration = completedEntries.reduce((sum, w) => sum + (w.duration || 0), 0);\n cv.stats.avgDuration = Math.round(totalDuration / completedEntries.length);\n }\n\n cv.lastActive = new Date().toISOString();\n saveAgentCV(cv);\n}\n\n/**\n * Get agent rankings by success rate\n */\nexport function getAgentRankings(): Array<{\n agentId: string;\n successRate: number;\n totalIssues: number;\n avgDuration: number;\n}> {\n const rankings: Array<{\n agentId: string;\n successRate: number;\n totalIssues: number;\n avgDuration: number;\n }> = [];\n\n if (!existsSync(AGENTS_DIR)) return rankings;\n\n const dirs = readdirSync(AGENTS_DIR, { withFileTypes: true }).filter(\n (d) => d.isDirectory()\n );\n\n for (const dir of dirs) {\n const cv = getAgentCV(dir.name);\n if (cv.stats.totalIssues > 0) {\n rankings.push({\n agentId: dir.name,\n successRate: cv.stats.successRate,\n totalIssues: cv.stats.totalIssues,\n avgDuration: cv.stats.avgDuration,\n });\n }\n }\n\n // Sort by success rate, then by total issues\n rankings.sort((a, b) => {\n if (b.successRate !== a.successRate) {\n return b.successRate - a.successRate;\n }\n return b.totalIssues - a.totalIssues;\n });\n\n return rankings;\n}\n\n/**\n * Format CV for display\n */\nexport function formatCV(cv: AgentCV): string {\n const lines: string[] = [\n `# Agent CV: ${cv.agentId}`,\n '',\n `Runtime: ${cv.runtime} (${cv.model})`,\n `Created: ${cv.createdAt}`,\n `Last Active: ${cv.lastActive}`,\n '',\n '## Statistics',\n '',\n `- Total Issues: ${cv.stats.totalIssues}`,\n `- Success Rate: ${(cv.stats.successRate * 100).toFixed(1)}%`,\n `- Successes: ${cv.stats.successCount}`,\n `- Failures: ${cv.stats.failureCount}`,\n `- Abandoned: ${cv.stats.abandonedCount}`,\n `- Avg Duration: ${cv.stats.avgDuration} minutes`,\n '',\n ];\n\n if (cv.skillsUsed.length > 0) {\n lines.push('## Skills Used');\n lines.push('');\n lines.push(cv.skillsUsed.join(', '));\n lines.push('');\n }\n\n if (cv.recentWork.length > 0) {\n lines.push('## Recent Work');\n lines.push('');\n\n for (const work of cv.recentWork.slice(0, 10)) {\n const statusIcon = {\n success: '✓',\n failed: '✗',\n abandoned: '⊘',\n in_progress: '●',\n }[work.outcome];\n\n const duration = work.duration ? ` (${work.duration}m)` : '';\n lines.push(`${statusIcon} ${work.issueId}${duration}`);\n\n if (work.failureReason) {\n lines.push(` Reason: ${work.failureReason}`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n","/**\n * Cloister Configuration\n *\n * Loads and manages Cloister configuration from ~/.panopticon/cloister.toml\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { parse, stringify } from '@iarna/toml';\nimport { join } from 'path';\nimport { PANOPTICON_HOME } from '../paths.js';\n\nconst CLOISTER_CONFIG_FILE = join(PANOPTICON_HOME, 'cloister.toml');\n\n/**\n * Health threshold configuration (in minutes)\n */\nexport interface HealthThresholds {\n stale: number;\n warning: number;\n stuck: number;\n}\n\n/**\n * Automatic action configuration\n */\nexport interface AutoActions {\n poke_on_warning: boolean;\n kill_on_stuck: boolean;\n restart_on_kill: boolean;\n}\n\n/**\n * Monitoring configuration\n */\nexport interface MonitoringConfig {\n check_interval: number; // seconds between health checks\n heartbeat_sources: ('jsonl_mtime' | 'tmux_activity' | 'git_activity' | 'active_heartbeat')[];\n}\n\n/**\n * Startup configuration\n */\nexport interface StartupConfig {\n auto_start: boolean; // Start Cloister when dashboard starts\n}\n\n/**\n * Notification configuration (future feature)\n */\nexport interface NotificationConfig {\n slack_webhook?: string;\n email?: string;\n}\n\n/**\n * Specialist agent configuration\n */\nexport interface SpecialistConfig {\n enabled: boolean;\n auto_wake: boolean;\n}\n\n/**\n * Test agent specific configuration\n */\nexport interface TestAgentConfig extends SpecialistConfig {\n test_command?: string; // Optional test command override (e.g., \"npm test\", \"pytest\", etc.)\n}\n\n/**\n * All specialist agents configuration\n */\nexport interface SpecialistsConfig {\n merge_agent?: SpecialistConfig;\n review_agent?: SpecialistConfig;\n test_agent?: TestAgentConfig;\n}\n\n/**\n * Model selection configuration\n */\nexport interface ModelSelectionConfig {\n default_model: 'opus' | 'sonnet' | 'haiku';\n complexity_routing: {\n trivial: 'opus' | 'sonnet' | 'haiku';\n simple: 'opus' | 'sonnet' | 'haiku';\n medium: 'opus' | 'sonnet' | 'haiku';\n complex: 'opus' | 'sonnet' | 'haiku';\n expert: 'opus' | 'sonnet' | 'haiku';\n };\n specialist_models: {\n merge_agent: 'opus' | 'sonnet' | 'haiku';\n review_agent: 'opus' | 'sonnet' | 'haiku';\n test_agent: 'opus' | 'sonnet' | 'haiku';\n planning_agent: 'opus' | 'sonnet' | 'haiku';\n };\n}\n\n/**\n * Handoff trigger configuration\n */\nexport interface HandoffTriggersConfig {\n planning_complete?: {\n enabled: boolean;\n from_model: 'opus' | 'sonnet' | 'haiku';\n to_model: 'opus' | 'sonnet' | 'haiku';\n };\n stuck_escalation?: {\n enabled: boolean;\n haiku_to_sonnet_minutes: number;\n sonnet_to_opus_minutes: number;\n };\n test_failure?: {\n enabled: boolean;\n from_model: 'opus' | 'sonnet' | 'haiku';\n to_model: 'opus' | 'sonnet' | 'haiku';\n trigger_on: 'any_failure' | '2_consecutive';\n };\n implementation_complete?: {\n enabled: boolean;\n to_specialist: string; // e.g., 'test-agent'\n };\n}\n\n/**\n * Handoff configuration\n */\nexport interface HandoffConfig {\n auto_triggers: HandoffTriggersConfig;\n}\n\n/**\n * Cost tracking configuration\n */\nexport interface CostTrackingConfig {\n display_enabled: boolean;\n log_to_jsonl: boolean;\n}\n\n/**\n * Auto-restart configuration\n */\nexport interface AutoRestartConfig {\n enabled: boolean;\n max_retries: number;\n backoff_seconds: number[]; // Array of backoff delays (e.g., [30, 60, 120])\n}\n\n/**\n * Cost limits configuration\n */\nexport interface CostLimitsConfig {\n per_agent_usd: number;\n per_issue_usd: number;\n daily_total_usd: number;\n alert_threshold: number; // Fraction (0.0-1.0) at which to start alerting\n}\n\n/**\n * Retention policy configuration\n */\nexport interface RetentionConfig {\n agent_state_days: number; // Days to keep agent state dirs (default: 30)\n health_staleness_hours: number; // Hours before hiding stale agents in health API (default: 24)\n}\n\n/**\n * Complete Cloister configuration\n */\nexport interface CloisterConfig {\n startup: StartupConfig;\n thresholds: HealthThresholds;\n auto_actions: AutoActions;\n monitoring: MonitoringConfig;\n notifications?: NotificationConfig;\n specialists?: SpecialistsConfig;\n model_selection?: ModelSelectionConfig;\n handoffs?: HandoffConfig;\n cost_tracking?: CostTrackingConfig;\n auto_restart?: AutoRestartConfig;\n cost_limits?: CostLimitsConfig;\n retention?: RetentionConfig;\n}\n\n/**\n * Default Cloister configuration\n */\nexport const DEFAULT_CLOISTER_CONFIG: CloisterConfig = {\n startup: {\n auto_start: true,\n },\n thresholds: {\n stale: 5,\n warning: 15,\n stuck: 30,\n },\n auto_actions: {\n poke_on_warning: true,\n kill_on_stuck: false, // Manual by default for safety\n restart_on_kill: false,\n },\n monitoring: {\n check_interval: 60, // 1 minute\n heartbeat_sources: ['jsonl_mtime', 'tmux_activity', 'git_activity'],\n },\n notifications: {\n slack_webhook: undefined,\n email: undefined,\n },\n specialists: {\n merge_agent: {\n enabled: true,\n auto_wake: false, // Only wake on explicit \"Approve & Merge\" click\n },\n review_agent: {\n enabled: true,\n auto_wake: false, // Only wake on explicit request\n },\n test_agent: {\n enabled: false, // Not yet implemented\n auto_wake: false,\n },\n },\n model_selection: {\n default_model: 'sonnet',\n complexity_routing: {\n trivial: 'haiku',\n simple: 'haiku',\n medium: 'sonnet',\n complex: 'sonnet',\n expert: 'opus',\n },\n specialist_models: {\n merge_agent: 'sonnet',\n review_agent: 'sonnet',\n test_agent: 'haiku',\n planning_agent: 'opus',\n },\n },\n handoffs: {\n auto_triggers: {\n planning_complete: {\n enabled: true,\n from_model: 'opus',\n to_model: 'sonnet',\n },\n stuck_escalation: {\n enabled: true,\n haiku_to_sonnet_minutes: 10,\n sonnet_to_opus_minutes: 20,\n },\n test_failure: {\n enabled: true,\n from_model: 'haiku',\n to_model: 'sonnet',\n trigger_on: 'any_failure',\n },\n implementation_complete: {\n enabled: true, // Auto-handoff to test-agent when implementation done\n to_specialist: 'test-agent',\n },\n },\n },\n cost_tracking: {\n display_enabled: true,\n log_to_jsonl: true,\n },\n auto_restart: {\n enabled: true,\n max_retries: 3,\n backoff_seconds: [30, 60, 120], // 30s, 1m, 2m\n },\n cost_limits: {\n per_agent_usd: 10.0,\n per_issue_usd: 25.0,\n daily_total_usd: 100.0,\n alert_threshold: 0.8, // Alert at 80%\n },\n retention: {\n agent_state_days: 30,\n health_staleness_hours: 24,\n },\n};\n\n/**\n * Deep merge utility that recursively merges objects.\n * - Recursively merges nested objects\n * - Arrays in overrides replace defaults (not concatenated)\n * - User values take precedence over defaults\n */\nfunction deepMerge<T extends object>(defaults: T, overrides: Partial<T>): T {\n const result = { ...defaults };\n\n for (const key of Object.keys(overrides) as (keyof T)[]) {\n const defaultVal = defaults[key];\n const overrideVal = overrides[key];\n\n // Skip undefined values in overrides\n if (overrideVal === undefined) continue;\n\n // Deep merge if both values are non-array objects\n if (\n typeof defaultVal === 'object' &&\n defaultVal !== null &&\n !Array.isArray(defaultVal) &&\n typeof overrideVal === 'object' &&\n overrideVal !== null &&\n !Array.isArray(overrideVal)\n ) {\n result[key] = deepMerge(defaultVal as any, overrideVal as any);\n } else {\n // Direct override for primitives and arrays\n result[key] = overrideVal as T[keyof T];\n }\n }\n\n return result;\n}\n\n/**\n * Load Cloister configuration\n *\n * Reads from ~/.panopticon/cloister.toml and merges with defaults.\n * Creates default config file if it doesn't exist.\n */\nexport function loadCloisterConfig(): CloisterConfig {\n // Ensure panopticon home exists\n if (!existsSync(PANOPTICON_HOME)) {\n mkdirSync(PANOPTICON_HOME, { recursive: true });\n }\n\n // If config file doesn't exist, create it with defaults\n if (!existsSync(CLOISTER_CONFIG_FILE)) {\n saveCloisterConfig(DEFAULT_CLOISTER_CONFIG);\n return DEFAULT_CLOISTER_CONFIG;\n }\n\n try {\n const content = readFileSync(CLOISTER_CONFIG_FILE, 'utf-8');\n const parsed = parse(content) as unknown as Partial<CloisterConfig>;\n\n // Deep merge with defaults\n return deepMerge(DEFAULT_CLOISTER_CONFIG, parsed);\n } catch (error) {\n console.error('Failed to load Cloister config:', error);\n console.error('Using default configuration');\n return DEFAULT_CLOISTER_CONFIG;\n }\n}\n\n/**\n * Save Cloister configuration\n *\n * Writes configuration to ~/.panopticon/cloister.toml\n */\nexport function saveCloisterConfig(config: CloisterConfig): void {\n // Ensure panopticon home exists\n if (!existsSync(PANOPTICON_HOME)) {\n mkdirSync(PANOPTICON_HOME, { recursive: true });\n }\n\n try {\n const content = stringify(config as any);\n writeFileSync(CLOISTER_CONFIG_FILE, content, 'utf-8');\n } catch (error) {\n console.error('Failed to save Cloister config:', error);\n throw error;\n }\n}\n\n/**\n * Update Cloister configuration\n *\n * Merges partial config updates with existing config.\n */\nexport function updateCloisterConfig(updates: Partial<CloisterConfig>): CloisterConfig {\n const current = loadCloisterConfig();\n const updated = deepMerge(current, updates);\n saveCloisterConfig(updated);\n return updated;\n}\n\n/**\n * Get the path to the Cloister config file\n */\nexport function getCloisterConfigPath(): string {\n return CLOISTER_CONFIG_FILE;\n}\n\n/**\n * Check if Cloister should auto-start\n */\nexport function shouldAutoStart(): boolean {\n const config = loadCloisterConfig();\n return config.startup.auto_start;\n}\n\n/**\n * Get health thresholds in milliseconds\n */\nexport function getHealthThresholdsMs(): {\n stale: number;\n warning: number;\n stuck: number;\n} {\n const config = loadCloisterConfig();\n return {\n stale: config.thresholds.stale * 60 * 1000,\n warning: config.thresholds.warning * 60 * 1000,\n stuck: config.thresholds.stuck * 60 * 1000,\n };\n}\n","import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, appendFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { AGENTS_DIR } from './paths.js';\nimport { createSession, killSession, sendKeys, sendKeysAsync, sessionExists, getAgentSessions, capturePane } from './tmux.js';\nimport { initHook, checkHook, generateFixedPointPrompt } from './hooks.js';\nimport { startWork, completeWork, getAgentCV } from './cv.js';\nimport type { ComplexityLevel } from './cloister/complexity.js';\nimport { loadCloisterConfig } from './cloister/config.js';\nimport { loadSettings, type ModelId } from './settings.js';\nimport { getModelId, WorkTypeId } from './work-type-router.js';\nimport { getProviderForModel, getProviderEnv, requiresRouter } from './providers.js';\nimport { loadConfig } from './config.js';\nimport { createTrackerFromConfig } from './tracker/factory.js';\nimport type { TrackerType } from './tracker/interface.js';\n\nconst execAsync = promisify(exec);\n\n/** Known agent ID prefixes — IDs with these prefixes are already normalized */\nconst AGENT_PREFIXES = ['agent-', 'planning-'];\n\n/** Normalize agent ID: preserve known prefixes, add 'agent-' for bare issue IDs */\nfunction normalizeAgentId(agentId: string): string {\n if (AGENT_PREFIXES.some(p => agentId.startsWith(p))) {\n return agentId;\n }\n return `agent-${agentId.toLowerCase()}`;\n}\n\n/**\n * Get provider-specific env vars (BASE_URL, AUTH_TOKEN) for a model.\n * Reads the current API key from settings so resumed/recovered agents\n * always use the latest key.\n */\nfunction getProviderEnvForModel(model: string): Record<string, string> {\n const provider = getProviderForModel(model as ModelId);\n if (provider.name === 'anthropic') return {};\n\n const settings = loadSettings();\n const apiKey = settings.api_keys?.[provider.name as keyof typeof settings.api_keys];\n if (apiKey) {\n return getProviderEnv(provider, apiKey);\n }\n console.warn(`Warning: No API key configured for ${provider.displayName}, falling back to Anthropic`);\n return {};\n}\n\n// ============================================================================\n// Ready Signal Management (PAN-87)\n// ============================================================================\n\n/**\n * Get path to agent's ready signal file (written by SessionStart hook)\n */\nfunction getReadySignalPath(agentId: string): string {\n return join(getAgentDir(agentId), 'ready.json');\n}\n\n/**\n * Clear ready signal before spawning (clean slate)\n */\nfunction clearReadySignal(agentId: string): void {\n const readyPath = getReadySignalPath(agentId);\n if (existsSync(readyPath)) {\n try {\n unlinkSync(readyPath);\n } catch {\n // Ignore errors - non-critical\n }\n }\n}\n\n/**\n * Wait for SessionStart hook to signal ready (async - non-blocking)\n * Returns true if ready signal received, false if timeout\n */\nasync function waitForReadySignal(agentId: string, timeoutSeconds = 30): Promise<boolean> {\n const readyPath = getReadySignalPath(agentId);\n\n for (let i = 0; i < timeoutSeconds; i++) {\n await new Promise(resolve => setTimeout(resolve, 1000)); // Non-blocking sleep\n\n if (existsSync(readyPath)) {\n try {\n const content = readFileSync(readyPath, 'utf-8');\n const signal = JSON.parse(content);\n if (signal.ready === true) {\n return true;\n }\n } catch {\n // File exists but invalid - keep waiting\n }\n }\n }\n\n return false;\n}\n\nexport interface AgentState {\n id: string;\n issueId: string;\n workspace: string;\n runtime: string;\n model: string;\n status: 'starting' | 'running' | 'stopped' | 'error';\n startedAt: string;\n lastActivity?: string;\n branch?: string; // Git branch name for this agent\n\n // Model routing & handoffs (Phase 4)\n complexity?: ComplexityLevel;\n handoffCount?: number;\n costSoFar?: number;\n sessionId?: string; // For resuming sessions after handoff\n\n // Work type system (PAN-118)\n phase?: 'exploration' | 'planning' | 'implementation' | 'testing' | 'documentation' | 'review-response';\n workType?: WorkTypeId; // Current work type ID\n}\n\nexport function getAgentDir(agentId: string): string {\n return join(AGENTS_DIR, agentId);\n}\n\nexport function getAgentState(agentId: string): AgentState | null {\n const stateFile = join(getAgentDir(agentId), 'state.json');\n if (!existsSync(stateFile)) return null;\n\n const content = readFileSync(stateFile, 'utf8');\n return JSON.parse(content);\n}\n\nexport function saveAgentState(state: AgentState): void {\n const dir = getAgentDir(state.id);\n mkdirSync(dir, { recursive: true });\n\n writeFileSync(\n join(dir, 'state.json'),\n JSON.stringify(state, null, 2)\n );\n}\n\n// ============================================================================\n// Hook-based State Management (PAN-80)\n// ============================================================================\n\n/**\n * Agent runtime state (hook-based tracking)\n */\nexport interface AgentRuntimeState {\n state: 'active' | 'idle' | 'suspended' | 'uninitialized';\n lastActivity: string;\n currentTool?: string;\n sessionId?: string;\n suspendedAt?: string;\n resumedAt?: string;\n currentIssue?: string; // Issue ID the agent is currently working on\n}\n\n/**\n * Activity log entry\n */\nexport interface ActivityEntry {\n ts: string;\n tool: string;\n action?: string;\n state?: 'active' | 'idle';\n}\n\n/**\n * Get agent runtime state (from hooks)\n */\nexport function getAgentRuntimeState(agentId: string): AgentRuntimeState | null {\n const stateFile = join(getAgentDir(agentId), 'state.json');\n\n // If file doesn't exist, agent is uninitialized\n if (!existsSync(stateFile)) {\n return {\n state: 'uninitialized',\n lastActivity: new Date().toISOString(),\n };\n }\n\n try {\n const content = readFileSync(stateFile, 'utf8');\n return JSON.parse(content) as AgentRuntimeState;\n } catch {\n return null;\n }\n}\n\n/**\n * Save agent runtime state\n */\nexport function saveAgentRuntimeState(agentId: string, state: Partial<AgentRuntimeState>): void {\n const dir = getAgentDir(agentId);\n mkdirSync(dir, { recursive: true });\n\n // Merge with existing state\n const existing = getAgentRuntimeState(agentId);\n const merged: AgentRuntimeState = {\n ...(existing || { state: 'uninitialized', lastActivity: new Date().toISOString() }),\n ...state,\n };\n\n writeFileSync(\n join(dir, 'state.json'),\n JSON.stringify(merged, null, 2)\n );\n}\n\n/**\n * Append to activity log with automatic pruning to 100 entries\n */\nexport function appendActivity(agentId: string, entry: ActivityEntry): void {\n const dir = getAgentDir(agentId);\n mkdirSync(dir, { recursive: true });\n\n const activityFile = join(dir, 'activity.jsonl');\n\n // Append entry\n appendFileSync(activityFile, JSON.stringify(entry) + '\\n');\n\n // Prune to last 100 entries\n if (existsSync(activityFile)) {\n try {\n const lines = readFileSync(activityFile, 'utf8').trim().split('\\n');\n if (lines.length > 100) {\n const trimmed = lines.slice(-100);\n writeFileSync(activityFile, trimmed.join('\\n') + '\\n');\n }\n } catch (error) {\n // Ignore pruning errors - activity log is non-critical\n }\n }\n}\n\n/**\n * Read activity log (last N entries)\n */\nexport function getActivity(agentId: string, limit = 100): ActivityEntry[] {\n const activityFile = join(getAgentDir(agentId), 'activity.jsonl');\n\n if (!existsSync(activityFile)) {\n return [];\n }\n\n try {\n const lines = readFileSync(activityFile, 'utf8').trim().split('\\n');\n const entries = lines\n .filter(line => line.trim())\n .map(line => JSON.parse(line) as ActivityEntry)\n .slice(-limit);\n\n return entries;\n } catch {\n return [];\n }\n}\n\n/**\n * Save Claude session ID for later resume\n */\nexport function saveSessionId(agentId: string, sessionId: string): void {\n const dir = getAgentDir(agentId);\n mkdirSync(dir, { recursive: true });\n\n writeFileSync(join(dir, 'session.id'), sessionId);\n}\n\n/**\n * Get saved Claude session ID\n */\nexport function getSessionId(agentId: string): string | null {\n const sessionFile = join(getAgentDir(agentId), 'session.id');\n\n if (!existsSync(sessionFile)) {\n return null;\n }\n\n try {\n return readFileSync(sessionFile, 'utf8').trim();\n } catch {\n return null;\n }\n}\n\nexport interface SpawnOptions {\n issueId: string;\n workspace: string;\n runtime?: string;\n model?: string;\n prompt?: string;\n difficulty?: ComplexityLevel;\n agentType?: 'review-agent' | 'test-agent' | 'merge-agent' | 'planning-agent' | 'work-agent';\n\n // Work type system (PAN-118)\n phase?: 'exploration' | 'planning' | 'implementation' | 'testing' | 'documentation' | 'review-response';\n workType?: WorkTypeId; // Explicit work type ID (overrides phase-based detection)\n}\n\n/**\n * Determine which model to use for an agent based on configuration\n *\n * New Priority (PAN-118):\n * 1. Explicitly provided model (options.model)\n * 2. Explicit work type ID (options.workType)\n * 3. Work type from phase (options.phase → issue-agent:{phase})\n * 4. Specialist work type (options.agentType → specialist-{type})\n * 5. Complexity-based routing (LEGACY - deprecated)\n * 6. Default fallback (claude-sonnet-4-5)\n */\nfunction determineModel(options: SpawnOptions): string {\n console.log(`[DEBUG] determineModel called with:`, { model: options.model, workType: options.workType, phase: options.phase, agentType: options.agentType, difficulty: options.difficulty });\n\n // Explicit model always wins\n if (options.model) {\n console.log(`[DEBUG] Using explicit model: ${options.model}`);\n return options.model;\n }\n\n try {\n // Use work type router if work type or phase specified\n if (options.workType) {\n return getModelId(options.workType);\n }\n\n // Map phase to work type ID\n if (options.phase) {\n const workType: WorkTypeId = `issue-agent:${options.phase}` as WorkTypeId;\n return getModelId(workType);\n }\n\n // Map specialist agent type to work type ID\n if (options.agentType && options.agentType !== 'work-agent') {\n if (options.agentType === 'planning-agent') {\n return getModelId('planning-agent');\n }\n // Specialists: review-agent, test-agent, merge-agent\n const workType: WorkTypeId = `specialist-${options.agentType}` as WorkTypeId;\n return getModelId(workType);\n }\n\n // LEGACY: Complexity-based routing (deprecated but kept for backward compat)\n if (options.difficulty) {\n const settings = loadSettings();\n if (settings.models.complexity[options.difficulty]) {\n console.warn(`Using legacy complexity-based routing for ${options.difficulty}. Consider migrating to work types.`);\n return settings.models.complexity[options.difficulty];\n }\n }\n\n // Fall back to default model from Cloister config or claude-sonnet-4-5\n try {\n const cloisterConfig = loadCloisterConfig();\n const defaultModel = cloisterConfig.model_selection?.default_model || 'sonnet';\n const modelMap: Record<string, string> = {\n 'opus': 'claude-opus-4-6',\n 'sonnet': 'claude-sonnet-4-5',\n 'haiku': 'claude-haiku-4-5',\n };\n return modelMap[defaultModel] || 'claude-sonnet-4-5';\n } catch {\n return 'claude-sonnet-4-5';\n }\n } catch (error) {\n // If work type router fails, fall back to default\n console.warn('Warning: Could not resolve model using work type router, using default');\n return options.model || 'claude-sonnet-4-5';\n }\n}\n\n/**\n * Transition an issue to \"in_progress\" in its tracker.\n * Tries each configured tracker until one succeeds.\n */\nasync function transitionIssueToInProgress(issueId: string): Promise<void> {\n const config = loadConfig();\n const trackersConfig = config.trackers;\n\n // Try primary tracker first, then secondary\n const trackerTypes: TrackerType[] = [trackersConfig.primary];\n if (trackersConfig.secondary) {\n trackerTypes.push(trackersConfig.secondary);\n }\n\n for (const trackerType of trackerTypes) {\n try {\n const tracker = createTrackerFromConfig(trackersConfig, trackerType);\n await tracker.transitionIssue(issueId, 'in_progress');\n console.log(`[agents] Transitioned ${issueId} to in_progress via ${trackerType}`);\n return;\n } catch {\n // Issue not found in this tracker or transition failed, try next\n }\n }\n}\n\nexport async function spawnAgent(options: SpawnOptions): Promise<AgentState> {\n const agentId = `agent-${options.issueId.toLowerCase()}`;\n\n // Check if already running\n if (sessionExists(agentId)) {\n throw new Error(`Agent ${agentId} already running. Use 'pan work tell' to message it.`);\n }\n\n // Initialize hook for this agent (FPP support)\n initHook(agentId);\n\n // Determine model based on configuration\n const selectedModel = determineModel(options);\n console.log(`[DEBUG] Selected model: ${selectedModel}`);\n\n // Create state\n const state: AgentState = {\n id: agentId,\n issueId: options.issueId,\n workspace: options.workspace,\n runtime: options.runtime || 'claude',\n model: selectedModel,\n status: 'starting',\n startedAt: new Date().toISOString(),\n // Initialize Phase 4 fields (legacy)\n complexity: options.difficulty,\n handoffCount: 0,\n costSoFar: 0,\n // Work type system (PAN-118)\n phase: options.phase,\n workType: options.workType,\n };\n\n saveAgentState(state);\n\n // Build prompt with FPP work if available\n let prompt = options.prompt || '';\n\n // FPP: Check for pending work on hook\n const { hasWork, items } = checkHook(agentId);\n if (hasWork) {\n const fixedPointPrompt = generateFixedPointPrompt(agentId);\n if (fixedPointPrompt) {\n prompt = fixedPointPrompt + '\\n\\n---\\n\\n' + prompt;\n }\n }\n\n // Write prompt to file for complex prompts (avoids shell escaping issues)\n const promptFile = join(getAgentDir(agentId), 'initial-prompt.md');\n if (prompt) {\n writeFileSync(promptFile, prompt);\n }\n\n // Auto-setup hooks if not configured\n checkAndSetupHooks();\n\n // Write initial task cache for heartbeat hook\n writeTaskCache(agentId, options.issueId);\n\n // Clear ready signal before spawning (clean slate for PAN-87 fix)\n clearReadySignal(agentId);\n\n // Get provider-specific environment variables (BASE_URL, AUTH_TOKEN)\n const providerEnv = getProviderEnvForModel(selectedModel);\n\n // Create tmux session and start claude\n // For prompts with special shell characters, use a launcher script to safely pass the prompt\n // The script reads the file into a variable, which bash then safely expands\n let claudeCmd: string;\n if (prompt) {\n const launcherScript = join(getAgentDir(agentId), 'launcher.sh');\n const launcherContent = `#!/bin/bash\nprompt=$(cat \"${promptFile}\")\nexec claude --dangerously-skip-permissions --model ${state.model} \"\\$prompt\"\n`;\n writeFileSync(launcherScript, launcherContent, { mode: 0o755 });\n claudeCmd = `bash \"${launcherScript}\"`;\n } else {\n claudeCmd = `claude --dangerously-skip-permissions --model ${state.model}`;\n }\n\n createSession(agentId, options.workspace, claudeCmd, {\n env: {\n PANOPTICON_AGENT_ID: agentId,\n PANOPTICON_ISSUE_ID: options.issueId,\n PANOPTICON_SESSION_TYPE: options.phase || 'implementation',\n ...providerEnv // Add provider-specific env vars (BASE_URL, AUTH_TOKEN, etc.)\n }\n });\n\n // Update status\n state.status = 'running';\n saveAgentState(state);\n\n // Track work in CV\n startWork(agentId, options.issueId);\n\n // Transition issue tracker to \"in progress\" (best-effort, don't block agent spawn)\n // Only for work agents, not planning/specialist agents\n if (!options.agentType || options.agentType === 'work-agent') {\n transitionIssueToInProgress(options.issueId).catch((err) => {\n console.warn(`[agents] Could not transition ${options.issueId} to in_progress: ${err.message}`);\n });\n }\n\n return state;\n}\n\nexport function listRunningAgents(): (AgentState & { tmuxActive: boolean })[] {\n const tmuxSessions = getAgentSessions();\n const tmuxNames = new Set(tmuxSessions.map(s => s.name));\n\n const agents: (AgentState & { tmuxActive: boolean })[] = [];\n\n // Read all agent states\n if (!existsSync(AGENTS_DIR)) return agents;\n\n const dirs = readdirSync(AGENTS_DIR, { withFileTypes: true })\n .filter(d => d.isDirectory());\n\n for (const dir of dirs) {\n const state = getAgentState(dir.name);\n if (state) {\n agents.push({\n ...state,\n tmuxActive: tmuxNames.has(state.id),\n });\n }\n }\n\n return agents;\n}\n\nexport function stopAgent(agentId: string): void {\n const normalizedId = normalizeAgentId(agentId);\n\n if (sessionExists(normalizedId)) {\n // Capture tmux output before killing so logs remain viewable after stop\n try {\n const output = capturePane(normalizedId, 5000);\n if (output) {\n const agentDir = getAgentDir(normalizedId);\n mkdirSync(agentDir, { recursive: true });\n writeFileSync(join(agentDir, 'output.log'), output);\n }\n } catch {\n // Non-fatal — best effort log capture\n }\n\n killSession(normalizedId);\n }\n\n const state = getAgentState(normalizedId);\n if (state) {\n // Ensure id is set — runtime state files may lack it (PAN-150)\n if (!state.id) state.id = normalizedId;\n state.status = 'stopped';\n saveAgentState(state);\n }\n}\n\nexport async function messageAgent(agentId: string, message: string): Promise<void> {\n const normalizedId = normalizeAgentId(agentId);\n\n // Check if agent is suspended - auto-resume if so (PAN-80)\n const runtimeState = getAgentRuntimeState(normalizedId);\n if (runtimeState?.state === 'suspended') {\n console.log(`[agents] Auto-resuming suspended agent ${normalizedId} to deliver message`);\n const result = await resumeAgent(normalizedId, message);\n if (!result.success) {\n throw new Error(`Failed to auto-resume agent: ${result.error}`);\n }\n // Message already sent during resume\n return;\n }\n\n // Check if this is a remote agent\n const { loadRemoteAgentState, sendToRemoteAgent } = await import('./remote/remote-agents.js');\n const remoteState = loadRemoteAgentState(normalizedId);\n if (remoteState && remoteState.vmName) {\n console.log(`[agents] Sending message to remote agent ${normalizedId} on ${remoteState.vmName}`);\n await sendToRemoteAgent(normalizedId, remoteState.vmName, message);\n\n // Also save to mail queue for persistence\n const mailDir = join(getAgentDir(normalizedId), 'mail');\n mkdirSync(mailDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n writeFileSync(\n join(mailDir, `${timestamp}.md`),\n `# Message\\n\\n${message}\\n`\n );\n return;\n }\n\n if (!sessionExists(normalizedId)) {\n throw new Error(`Agent ${normalizedId} not running`);\n }\n\n await sendKeysAsync(normalizedId, message);\n\n // Also save to mail queue\n const mailDir = join(getAgentDir(normalizedId), 'mail');\n mkdirSync(mailDir, { recursive: true });\n\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n writeFileSync(\n join(mailDir, `${timestamp}.md`),\n `# Message\\n\\n${message}\\n`\n );\n}\n\n/**\n * Resume a suspended agent (PAN-80)\n *\n * Reads saved session ID and creates new tmux session with --resume flag.\n * Optionally sends a message after resuming.\n *\n * Auto-resume triggers:\n * - Specialists: When queued work arrives\n * - Work agents: When message is sent via /work-tell\n */\nexport async function resumeAgent(agentId: string, message?: string): Promise<{ success: boolean; error?: string }> {\n const normalizedId = normalizeAgentId(agentId);\n\n // Check runtime state\n const runtimeState = getAgentRuntimeState(normalizedId);\n if (!runtimeState || runtimeState.state !== 'suspended') {\n return {\n success: false,\n error: `Cannot resume agent in state: ${runtimeState?.state || 'unknown'}`\n };\n }\n\n // Get saved session ID\n const sessionId = getSessionId(normalizedId);\n if (!sessionId) {\n return {\n success: false,\n error: 'No saved session ID found'\n };\n }\n\n // Get agent state for workspace info\n const agentState = getAgentState(normalizedId);\n if (!agentState) {\n return {\n success: false,\n error: 'Agent state not found'\n };\n }\n\n // Check if session already exists (shouldn't happen for suspended agents)\n if (sessionExists(normalizedId)) {\n return {\n success: false,\n error: 'Agent session already exists'\n };\n }\n\n try {\n // Clear ready signal before resuming (clean slate for PAN-87 fix)\n clearReadySignal(normalizedId);\n\n // Get provider env for the agent's model (reads latest API key from settings)\n const providerEnv = agentState.model ? getProviderEnvForModel(agentState.model) : {};\n\n // Create new tmux session with resume command\n const claudeCmd = `claude --resume \"${sessionId}\" --dangerously-skip-permissions`;\n createSession(normalizedId, agentState.workspace, claudeCmd, {\n env: {\n PANOPTICON_AGENT_ID: normalizedId,\n ...providerEnv\n }\n });\n\n // If there's a message, wait for ready signal then send\n if (message) {\n // Wait for SessionStart hook to signal ready (PAN-87: reliable message delivery)\n const ready = await waitForReadySignal(normalizedId, 30);\n\n if (ready) {\n // Send message\n await sendKeysAsync(normalizedId, message);\n } else {\n console.error('Claude SessionStart hook did not fire during resume, message not sent');\n }\n }\n\n // Update runtime state\n saveAgentRuntimeState(normalizedId, {\n state: 'active',\n resumedAt: new Date().toISOString(),\n });\n\n // Update agent state\n if (agentState) {\n agentState.status = 'running';\n agentState.lastActivity = new Date().toISOString();\n saveAgentState(agentState);\n }\n\n return { success: true };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n success: false,\n error: `Failed to resume agent: ${msg}`\n };\n }\n}\n\n/**\n * Detect crashed agents (state shows running but tmux session is gone)\n */\nexport function detectCrashedAgents(): AgentState[] {\n const agents = listRunningAgents();\n return agents.filter(\n (agent) => agent.status === 'running' && !agent.tmuxActive\n );\n}\n\n/**\n * Recover a crashed agent by restarting it with context\n */\nexport function recoverAgent(agentId: string): AgentState | null {\n const normalizedId = normalizeAgentId(agentId);\n const state = getAgentState(normalizedId);\n\n if (!state) {\n return null;\n }\n\n // Runtime state files may lack required fields (PAN-150)\n if (!state.id) state.id = normalizedId;\n if (!state.workspace || !state.model) {\n console.error(`[agents] Cannot recover ${normalizedId}: state.json missing workspace or model`);\n return null;\n }\n\n // Check if already running\n if (sessionExists(normalizedId)) {\n return state;\n }\n\n // Update crash count in health file\n const healthFile = join(getAgentDir(normalizedId), 'health.json');\n let health = { consecutiveFailures: 0, killCount: 0, recoveryCount: 0 };\n if (existsSync(healthFile)) {\n try {\n health = { ...health, ...JSON.parse(readFileSync(healthFile, 'utf-8')) };\n } catch {}\n }\n health.recoveryCount = (health.recoveryCount || 0) + 1;\n writeFileSync(healthFile, JSON.stringify(health, null, 2));\n\n // Build recovery prompt\n const recoveryPrompt = generateRecoveryPrompt(state);\n\n // Get provider env for the agent's model (reads latest API key from settings)\n const providerEnv = state.model ? getProviderEnvForModel(state.model) : {};\n\n // Restart the agent with recovery context (YOLO mode - skip permissions)\n const claudeCmd = `claude --dangerously-skip-permissions --model ${state.model} \"${recoveryPrompt.replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n')}\"`;\n createSession(normalizedId, state.workspace, claudeCmd, {\n env: {\n PANOPTICON_AGENT_ID: normalizedId,\n ...providerEnv\n }\n });\n\n // Update state\n state.status = 'running';\n state.lastActivity = new Date().toISOString();\n saveAgentState(state);\n\n return state;\n}\n\n/**\n * Generate a recovery prompt for a crashed agent\n */\nfunction generateRecoveryPrompt(state: AgentState): string {\n const lines: string[] = [\n '# Agent Recovery',\n '',\n '⚠️ This agent session was recovered after a crash.',\n '',\n '## Previous Context',\n `- Issue: ${state.issueId}`,\n `- Workspace: ${state.workspace}`,\n `- Started: ${state.startedAt}`,\n '',\n '## Recovery Steps',\n '1. Check beads for context: `bd show ' + state.issueId + '`',\n '2. Review recent git commits: `git log --oneline -10`',\n '3. Check hook for pending work: `pan work hook check`',\n '4. Resume from last known state',\n '',\n '## FPP Reminder',\n '> \"Any runnable action is a fixed point and must resolve before the system can rest.\"',\n '',\n ];\n\n // Add FPP work if available\n const { hasWork } = checkHook(state.id);\n if (hasWork) {\n const fixedPointPrompt = generateFixedPointPrompt(state.id);\n if (fixedPointPrompt) {\n lines.push('---');\n lines.push('');\n lines.push(fixedPointPrompt);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Auto-recover all crashed agents\n */\nexport function autoRecoverAgents(): { recovered: string[]; failed: string[] } {\n const crashed = detectCrashedAgents();\n const recovered: string[] = [];\n const failed: string[] = [];\n\n for (const agent of crashed) {\n try {\n const result = recoverAgent(agent.id);\n if (result) {\n recovered.push(agent.id);\n } else {\n failed.push(agent.id);\n }\n } catch (error) {\n failed.push(agent.id);\n }\n }\n\n return { recovered, failed };\n}\n\n/**\n * Check if Panopticon hooks are configured, and auto-setup if not\n */\nfunction checkAndSetupHooks(): void {\n const settingsPath = join(homedir(), '.claude', 'settings.json');\n const hookPath = join(homedir(), '.panopticon', 'bin', 'heartbeat-hook');\n\n // Check if settings.json exists and has heartbeat hook configured\n if (existsSync(settingsPath)) {\n try {\n const settingsContent = readFileSync(settingsPath, 'utf-8');\n const settings = JSON.parse(settingsContent);\n const postToolUse = settings?.hooks?.PostToolUse || [];\n\n const hookConfigured = postToolUse.some((hookConfig: any) =>\n hookConfig.hooks?.some((hook: any) =>\n hook.command === hookPath ||\n hook.command?.includes('panopticon') ||\n hook.command?.includes('heartbeat-hook')\n )\n );\n\n if (hookConfigured) {\n return; // Already configured\n }\n } catch {\n // Ignore errors, will attempt setup\n }\n }\n\n // Hooks not configured - run setup silently\n try {\n console.log('Configuring Panopticon heartbeat hooks...');\n // Note: This runs during spawn which is now async, so we can use execAsync\n // But this is called from a sync context in checkAndSetupHooks, so we use fire-and-forget\n exec('pan setup hooks', (error: Error | null) => {\n if (error) {\n console.warn('⚠ Failed to auto-configure hooks. Run `pan setup hooks` manually.');\n } else {\n console.log('✓ Heartbeat hooks configured');\n }\n });\n } catch (error) {\n console.warn('⚠ Failed to auto-configure hooks. Run `pan setup hooks` manually.');\n }\n}\n\n/**\n * Write task cache for heartbeat hook to use\n */\nfunction writeTaskCache(agentId: string, issueId: string): void {\n const cacheDir = join(getAgentDir(agentId));\n mkdirSync(cacheDir, { recursive: true });\n\n const cacheFile = join(cacheDir, 'current-task.json');\n writeFileSync(\n cacheFile,\n JSON.stringify({\n id: issueId,\n title: `Working on ${issueId}`,\n updated_at: new Date().toISOString()\n }, null, 2)\n );\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","existsSync","mkdirSync","join","init_config","existsSync","mkdirSync","writeFileSync","readFileSync","readdirSync","join","mailDir","timestamp","init_config"]}
|
package/dist/cli/index.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
recordWake,
|
|
26
26
|
wakeSpecialistOrQueue,
|
|
27
27
|
wakeSpecialistWithTask
|
|
28
|
-
} from "../chunk-
|
|
28
|
+
} from "../chunk-2NIAOCIC.js";
|
|
29
29
|
import {
|
|
30
30
|
addAlias,
|
|
31
31
|
cleanOldBackups,
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
saveSessionId,
|
|
68
68
|
spawnAgent,
|
|
69
69
|
stopAgent
|
|
70
|
-
} from "../chunk-
|
|
70
|
+
} from "../chunk-XP2DXWYP.js";
|
|
71
71
|
import {
|
|
72
72
|
checkHook,
|
|
73
73
|
clearHook,
|
|
@@ -5582,7 +5582,12 @@ async function createWorkspace(options) {
|
|
|
5582
5582
|
await execAsync4(`docker compose -f "${traefikPath}" up -d`, { cwd: projectConfig.path });
|
|
5583
5583
|
result.steps.push("Started Traefik");
|
|
5584
5584
|
} catch (error) {
|
|
5585
|
-
|
|
5585
|
+
const msg = error?.message || String(error);
|
|
5586
|
+
if (msg.includes("port is already allocated") || msg.includes("address already in use")) {
|
|
5587
|
+
result.steps.push("Traefik already running (port in use)");
|
|
5588
|
+
} else {
|
|
5589
|
+
result.errors.push(`Failed to start Traefik: ${error}`);
|
|
5590
|
+
}
|
|
5586
5591
|
}
|
|
5587
5592
|
}
|
|
5588
5593
|
}
|
|
@@ -9758,7 +9763,7 @@ async function runPatrol() {
|
|
|
9758
9763
|
if (nextTask) {
|
|
9759
9764
|
console.log(`[deacon] Auto-resuming suspended ${specialist.name} for queued task: ${nextTask.payload.issueId}`);
|
|
9760
9765
|
try {
|
|
9761
|
-
const { resumeAgent } = await import("../agents-
|
|
9766
|
+
const { resumeAgent } = await import("../agents-BDFHF4T3.js");
|
|
9762
9767
|
const message = `# Queued Work
|
|
9763
9768
|
|
|
9764
9769
|
Processing queued task: ${nextTask.payload.issueId}`;
|
|
@@ -11583,7 +11588,7 @@ import { promisify as promisify12 } from "util";
|
|
|
11583
11588
|
var execAsync12 = promisify12(exec12);
|
|
11584
11589
|
async function listLogsCommand(project2, type, options) {
|
|
11585
11590
|
try {
|
|
11586
|
-
const { listRunLogs } = await import("../specialist-logs-
|
|
11591
|
+
const { listRunLogs } = await import("../specialist-logs-GF3YV4KL.js");
|
|
11587
11592
|
const limit = options.limit ? parseInt(options.limit) : 10;
|
|
11588
11593
|
const runs = listRunLogs(project2, type, { limit });
|
|
11589
11594
|
if (options.json) {
|
|
@@ -11628,7 +11633,7 @@ View a specific run: pan specialists logs ${project2} ${type} <runId>
|
|
|
11628
11633
|
}
|
|
11629
11634
|
async function viewLogCommand(project2, type, runId, options) {
|
|
11630
11635
|
try {
|
|
11631
|
-
const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-
|
|
11636
|
+
const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-GF3YV4KL.js");
|
|
11632
11637
|
const content = getRunLog(project2, type, runId);
|
|
11633
11638
|
if (!content) {
|
|
11634
11639
|
console.error(`\u274C Run log not found: ${runId}`);
|
|
@@ -11652,8 +11657,8 @@ async function viewLogCommand(project2, type, runId, options) {
|
|
|
11652
11657
|
}
|
|
11653
11658
|
async function tailLogCommand(project2, type) {
|
|
11654
11659
|
try {
|
|
11655
|
-
const { getRunLogPath } = await import("../specialist-logs-
|
|
11656
|
-
const { getProjectSpecialistMetadata } = await import("../specialists-
|
|
11660
|
+
const { getRunLogPath } = await import("../specialist-logs-GF3YV4KL.js");
|
|
11661
|
+
const { getProjectSpecialistMetadata } = await import("../specialists-JBIW6MP4.js");
|
|
11657
11662
|
const metadata = getProjectSpecialistMetadata(project2, type);
|
|
11658
11663
|
if (!metadata.currentRun) {
|
|
11659
11664
|
console.error(`\u274C No active run for ${project2}/${type}`);
|
|
@@ -11722,7 +11727,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
|
|
|
11722
11727
|
console.log(" Use --force to confirm.");
|
|
11723
11728
|
process.exit(1);
|
|
11724
11729
|
}
|
|
11725
|
-
const { cleanupAllLogs } = await import("../specialist-logs-
|
|
11730
|
+
const { cleanupAllLogs } = await import("../specialist-logs-GF3YV4KL.js");
|
|
11726
11731
|
console.log("\u{1F9F9} Cleaning up old logs for all projects...\n");
|
|
11727
11732
|
const results = cleanupAllLogs();
|
|
11728
11733
|
console.log(`
|
|
@@ -11749,7 +11754,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
|
|
|
11749
11754
|
console.log(" Use --force to confirm.");
|
|
11750
11755
|
process.exit(1);
|
|
11751
11756
|
}
|
|
11752
|
-
const { cleanupOldLogs } = await import("../specialist-logs-
|
|
11757
|
+
const { cleanupOldLogs } = await import("../specialist-logs-GF3YV4KL.js");
|
|
11753
11758
|
const { getSpecialistRetention } = await import("../projects-VXRUCMLM.js");
|
|
11754
11759
|
const retention = getSpecialistRetention(projectOrAll);
|
|
11755
11760
|
console.log(`\u{1F9F9} Cleaning up old logs for ${projectOrAll}/${type}...`);
|