panopticon-cli 0.4.30 → 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.
Files changed (41) hide show
  1. package/dist/{agents-ZG4JIPO2.js → agents-BDFHF4T3.js} +4 -3
  2. package/dist/{chunk-42O6XJWZ.js → chunk-2NIAOCIC.js} +14 -14
  3. package/dist/{chunk-SIAUVHVO.js → chunk-3XAB4IXF.js} +4 -2
  4. package/dist/{chunk-SIAUVHVO.js.map → chunk-3XAB4IXF.js.map} +1 -1
  5. package/dist/chunk-ELK6Q7QI.js +545 -0
  6. package/dist/chunk-ELK6Q7QI.js.map +1 -0
  7. package/dist/chunk-LYSBSZYV.js +1523 -0
  8. package/dist/chunk-LYSBSZYV.js.map +1 -0
  9. package/dist/{chunk-VTMXR7JF.js → chunk-VU4FLXV5.js} +47 -40
  10. package/dist/{chunk-VTMXR7JF.js.map → chunk-VU4FLXV5.js.map} +1 -1
  11. package/dist/{chunk-VH27COUW.js → chunk-XP2DXWYP.js} +49 -10
  12. package/dist/chunk-XP2DXWYP.js.map +1 -0
  13. package/dist/cli/index.js +52 -29
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/{config-QWTS63TU.js → config-BOAMSKTF.js} +4 -2
  16. package/dist/dashboard/public/assets/{index-Dhwz2I7n.js → index-ClYqpcAJ.js} +84 -84
  17. package/dist/dashboard/public/index.html +1 -1
  18. package/dist/dashboard/server.js +93512 -23114
  19. package/dist/index.js +16 -14
  20. package/dist/index.js.map +1 -1
  21. package/dist/{remote-workspace-FNXLMNBG.js → remote-workspace-2G6V2KNP.js} +7 -5
  22. package/dist/{remote-workspace-FNXLMNBG.js.map → remote-workspace-2G6V2KNP.js.map} +1 -1
  23. package/dist/{specialist-context-QVRSHPYN.js → specialist-context-N32QBNNQ.js} +2 -2
  24. package/dist/{specialist-logs-7TQMHCUN.js → specialist-logs-GF3YV4KL.js} +2 -2
  25. package/dist/{specialists-UUXB5KFV.js → specialists-JBIW6MP4.js} +2 -2
  26. package/dist/{traefik-7OLLXUD7.js → traefik-CUJM6K5Z.js} +3 -3
  27. package/package.json +3 -2
  28. package/scripts/record-cost-event.js +243 -79
  29. package/scripts/record-cost-event.ts +128 -68
  30. package/dist/chunk-J3J32DIR.js +0 -279
  31. package/dist/chunk-J3J32DIR.js.map +0 -1
  32. package/dist/chunk-SUMIHS2B.js +0 -1714
  33. package/dist/chunk-SUMIHS2B.js.map +0 -1
  34. package/dist/chunk-VH27COUW.js.map +0 -1
  35. /package/dist/{agents-ZG4JIPO2.js.map → agents-BDFHF4T3.js.map} +0 -0
  36. /package/dist/{chunk-42O6XJWZ.js.map → chunk-2NIAOCIC.js.map} +0 -0
  37. /package/dist/{config-QWTS63TU.js.map → config-BOAMSKTF.js.map} +0 -0
  38. /package/dist/{specialist-context-QVRSHPYN.js.map → specialist-context-N32QBNNQ.js.map} +0 -0
  39. /package/dist/{specialist-logs-7TQMHCUN.js.map → specialist-logs-GF3YV4KL.js.map} +0 -0
  40. /package/dist/{specialists-UUXB5KFV.js.map → specialists-JBIW6MP4.js.map} +0 -0
  41. /package/dist/{traefik-7OLLXUD7.js.map → traefik-CUJM6K5Z.js.map} +0 -0
@@ -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
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  cleanupTemplateFiles,
4
4
  generatePanopticonTraefikConfig
5
- } from "../chunk-SIAUVHVO.js";
5
+ } from "../chunk-3XAB4IXF.js";
6
6
  import {
7
7
  checkSpecialistQueue,
8
8
  clearSessionId,
@@ -25,12 +25,11 @@ import {
25
25
  recordWake,
26
26
  wakeSpecialistOrQueue,
27
27
  wakeSpecialistWithTask
28
- } from "../chunk-42O6XJWZ.js";
28
+ } from "../chunk-2NIAOCIC.js";
29
29
  import {
30
30
  addAlias,
31
31
  cleanOldBackups,
32
32
  createBackup,
33
- createTracker,
34
33
  detectShell,
35
34
  executeSync,
36
35
  getAliasInstructions,
@@ -40,7 +39,7 @@ import {
40
39
  planSync,
41
40
  restoreBackup,
42
41
  syncHooks
43
- } from "../chunk-SUMIHS2B.js";
42
+ } from "../chunk-ELK6Q7QI.js";
44
43
  import {
45
44
  init_remote_agents,
46
45
  isRemoteAgentRunning,
@@ -57,7 +56,7 @@ import {
57
56
  getAgentState,
58
57
  getHealthThresholdsMs,
59
58
  init_agents,
60
- init_config,
59
+ init_config as init_config2,
61
60
  init_cv,
62
61
  listRunningAgents,
63
62
  loadCloisterConfig,
@@ -68,7 +67,7 @@ import {
68
67
  saveSessionId,
69
68
  spawnAgent,
70
69
  stopAgent
71
- } from "../chunk-VH27COUW.js";
70
+ } from "../chunk-XP2DXWYP.js";
72
71
  import {
73
72
  checkHook,
74
73
  clearHook,
@@ -87,9 +86,10 @@ import {
87
86
  sessionExists
88
87
  } from "../chunk-VIWUCJ4V.js";
89
88
  import {
89
+ createTracker,
90
90
  init_settings,
91
91
  loadSettings
92
- } from "../chunk-J3J32DIR.js";
92
+ } from "../chunk-LYSBSZYV.js";
93
93
  import {
94
94
  init_config_yaml,
95
95
  loadConfig as loadConfig2
@@ -101,12 +101,6 @@ import {
101
101
  loadWorkspaceMetadata,
102
102
  saveWorkspaceMetadata
103
103
  } from "../chunk-44EOY2ZL.js";
104
- import {
105
- getDashboardApiUrl,
106
- getDefaultConfig,
107
- loadConfig,
108
- saveConfig
109
- } from "../chunk-VTMXR7JF.js";
110
104
  import {
111
105
  PROJECTS_CONFIG_FILE,
112
106
  extractTeamPrefix,
@@ -125,6 +119,13 @@ import {
125
119
  createExeProvider,
126
120
  init_exe_provider
127
121
  } from "../chunk-JM6V62LT.js";
122
+ import {
123
+ getDashboardApiUrl,
124
+ getDefaultConfig,
125
+ init_config,
126
+ loadConfig,
127
+ saveConfig
128
+ } from "../chunk-VU4FLXV5.js";
128
129
  import {
129
130
  AGENTS_DIR,
130
131
  CERTS_DIR,
@@ -167,6 +168,7 @@ import chalk56 from "chalk";
167
168
  // src/cli/commands/init.ts
168
169
  init_esm_shims();
169
170
  init_paths();
171
+ init_config();
170
172
  import { existsSync, mkdirSync, readdirSync, cpSync } from "fs";
171
173
  import { join, dirname } from "path";
172
174
  import { fileURLToPath } from "url";
@@ -276,6 +278,7 @@ async function initCommand() {
276
278
 
277
279
  // src/cli/commands/sync.ts
278
280
  init_esm_shims();
281
+ init_config();
279
282
  import chalk2 from "chalk";
280
283
  import ora2 from "ora";
281
284
  import { execSync } from "child_process";
@@ -502,6 +505,7 @@ init_esm_shims();
502
505
  import chalk3 from "chalk";
503
506
  import ora3 from "ora";
504
507
  import inquirer from "inquirer";
508
+ init_config();
505
509
  init_paths();
506
510
  async function restoreCommand(timestamp) {
507
511
  const backups = listBackups();
@@ -869,6 +873,9 @@ function shouldSkipTrackerUpdate(issueId, cliFlag, trackerType = "linear") {
869
873
  });
870
874
  }
871
875
 
876
+ // src/cli/commands/work/issue.ts
877
+ init_config();
878
+
872
879
  // src/lib/remote/index.ts
873
880
  init_esm_shims();
874
881
  init_exe_provider();
@@ -1315,7 +1322,7 @@ async function handleRemoteWorkspace(issueId, options, spinner) {
1315
1322
  if (!remoteMetadata) {
1316
1323
  spinner.text = "Remote workspace not found, creating...";
1317
1324
  try {
1318
- const { createRemoteWorkspace: createRemoteWorkspace2 } = await import("../remote-workspace-FNXLMNBG.js");
1325
+ const { createRemoteWorkspace: createRemoteWorkspace2 } = await import("../remote-workspace-2G6V2KNP.js");
1319
1326
  remoteMetadata = await createRemoteWorkspace2(issueId, { spinner });
1320
1327
  } catch (error) {
1321
1328
  spinner.fail(`Failed to create remote workspace: ${error.message}`);
@@ -2098,7 +2105,7 @@ async function doneCommand(id, options = {}) {
2098
2105
  console.log(chalk12.dim(` pan work approve ${issueId}`));
2099
2106
  console.log("");
2100
2107
  try {
2101
- const { getDashboardApiUrl: getDashboardApiUrl2 } = await import("../config-QWTS63TU.js");
2108
+ const { getDashboardApiUrl: getDashboardApiUrl2 } = await import("../config-BOAMSKTF.js");
2102
2109
  const dashboardUrl = getDashboardApiUrl2();
2103
2110
  const http = await import("http");
2104
2111
  const checkDashboard = () => new Promise((resolve2) => {
@@ -2774,6 +2781,7 @@ async function planCommand(id, options = {}) {
2774
2781
 
2775
2782
  // src/cli/commands/work/list.ts
2776
2783
  init_esm_shims();
2784
+ init_config();
2777
2785
  import chalk14 from "chalk";
2778
2786
  import ora8 from "ora";
2779
2787
  var PRIORITY_LABELS = {
@@ -2942,6 +2950,7 @@ ${tracker.toUpperCase()} (${issues.length} shadowed issues)
2942
2950
 
2943
2951
  // src/cli/commands/work/triage.ts
2944
2952
  init_esm_shims();
2953
+ init_config();
2945
2954
  import chalk15 from "chalk";
2946
2955
  import ora9 from "ora";
2947
2956
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
@@ -4315,6 +4324,7 @@ Previous state: ${issue.state}`
4315
4324
 
4316
4325
  // src/cli/commands/work/request-review.ts
4317
4326
  init_esm_shims();
4327
+ init_config();
4318
4328
  import chalk22 from "chalk";
4319
4329
  var DASHBOARD_URL = getDashboardApiUrl();
4320
4330
  async function requestReviewCommand(id, options) {
@@ -5572,7 +5582,12 @@ async function createWorkspace(options) {
5572
5582
  await execAsync4(`docker compose -f "${traefikPath}" up -d`, { cwd: projectConfig.path });
5573
5583
  result.steps.push("Started Traefik");
5574
5584
  } catch (error) {
5575
- result.errors.push(`Failed to start Traefik: ${error}`);
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
+ }
5576
5591
  }
5577
5592
  }
5578
5593
  }
@@ -5717,6 +5732,7 @@ async function removeWorkspace(options) {
5717
5732
  }
5718
5733
 
5719
5734
  // src/cli/commands/workspace.ts
5735
+ init_config();
5720
5736
  import { exec as exec5 } from "child_process";
5721
5737
  import { promisify as promisify5 } from "util";
5722
5738
  import { homedir as homedir12 } from "os";
@@ -7008,6 +7024,7 @@ ${config2.name || key}`));
7008
7024
  // src/cli/commands/install.ts
7009
7025
  init_esm_shims();
7010
7026
  init_paths();
7027
+ init_config();
7011
7028
  import chalk30 from "chalk";
7012
7029
  import ora17 from "ora";
7013
7030
  import inquirer5 from "inquirer";
@@ -7471,11 +7488,11 @@ import chalk31 from "chalk";
7471
7488
 
7472
7489
  // src/lib/cloister/service.ts
7473
7490
  init_esm_shims();
7474
- init_config();
7491
+ init_config2();
7475
7492
 
7476
7493
  // src/lib/cloister/health.ts
7477
7494
  init_esm_shims();
7478
- init_config();
7495
+ init_config2();
7479
7496
  function evaluateHealthState(timeSinceActivityMs, thresholds) {
7480
7497
  if (timeSinceActivityMs < thresholds.stale) {
7481
7498
  return "active";
@@ -8044,7 +8061,7 @@ init_agents();
8044
8061
 
8045
8062
  // src/lib/cloister/triggers.ts
8046
8063
  init_esm_shims();
8047
- init_config();
8064
+ init_config2();
8048
8065
  import { existsSync as existsSync27 } from "fs";
8049
8066
  import { join as join26 } from "path";
8050
8067
  import { exec as exec7 } from "child_process";
@@ -8792,7 +8809,7 @@ function clearOldViolations(hoursOld = 24) {
8792
8809
  // src/lib/cloister/cost-monitor.ts
8793
8810
  init_esm_shims();
8794
8811
  init_paths();
8795
- init_config();
8812
+ init_config2();
8796
8813
  import { readFileSync as readFileSync26, existsSync as existsSync32, writeFileSync as writeFileSync18, mkdirSync as mkdirSync21, unlinkSync as unlinkSync3 } from "fs";
8797
8814
  import { join as join31, dirname as dirname10 } from "path";
8798
8815
  var COST_DATA_FILE = join31(PANOPTICON_HOME, "cost-data.json");
@@ -9145,7 +9162,7 @@ async function checkAndRotateIfNeeded(specialistName, workingDir) {
9145
9162
  // src/lib/cloister/deacon.ts
9146
9163
  init_esm_shims();
9147
9164
  init_paths();
9148
- init_config();
9165
+ init_config2();
9149
9166
  init_specialists();
9150
9167
  init_agents();
9151
9168
  init_tmux();
@@ -9746,7 +9763,7 @@ async function runPatrol() {
9746
9763
  if (nextTask) {
9747
9764
  console.log(`[deacon] Auto-resuming suspended ${specialist.name} for queued task: ${nextTask.payload.issueId}`);
9748
9765
  try {
9749
- const { resumeAgent } = await import("../agents-ZG4JIPO2.js");
9766
+ const { resumeAgent } = await import("../agents-BDFHF4T3.js");
9750
9767
  const message = `# Queued Work
9751
9768
 
9752
9769
  Processing queued task: ${nextTask.payload.issueId}`;
@@ -11571,7 +11588,7 @@ import { promisify as promisify12 } from "util";
11571
11588
  var execAsync12 = promisify12(exec12);
11572
11589
  async function listLogsCommand(project2, type, options) {
11573
11590
  try {
11574
- const { listRunLogs } = await import("../specialist-logs-7TQMHCUN.js");
11591
+ const { listRunLogs } = await import("../specialist-logs-GF3YV4KL.js");
11575
11592
  const limit = options.limit ? parseInt(options.limit) : 10;
11576
11593
  const runs = listRunLogs(project2, type, { limit });
11577
11594
  if (options.json) {
@@ -11616,7 +11633,7 @@ View a specific run: pan specialists logs ${project2} ${type} <runId>
11616
11633
  }
11617
11634
  async function viewLogCommand(project2, type, runId, options) {
11618
11635
  try {
11619
- const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-7TQMHCUN.js");
11636
+ const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-GF3YV4KL.js");
11620
11637
  const content = getRunLog(project2, type, runId);
11621
11638
  if (!content) {
11622
11639
  console.error(`\u274C Run log not found: ${runId}`);
@@ -11640,8 +11657,8 @@ async function viewLogCommand(project2, type, runId, options) {
11640
11657
  }
11641
11658
  async function tailLogCommand(project2, type) {
11642
11659
  try {
11643
- const { getRunLogPath } = await import("../specialist-logs-7TQMHCUN.js");
11644
- const { getProjectSpecialistMetadata } = await import("../specialists-UUXB5KFV.js");
11660
+ const { getRunLogPath } = await import("../specialist-logs-GF3YV4KL.js");
11661
+ const { getProjectSpecialistMetadata } = await import("../specialists-JBIW6MP4.js");
11645
11662
  const metadata = getProjectSpecialistMetadata(project2, type);
11646
11663
  if (!metadata.currentRun) {
11647
11664
  console.error(`\u274C No active run for ${project2}/${type}`);
@@ -11710,7 +11727,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
11710
11727
  console.log(" Use --force to confirm.");
11711
11728
  process.exit(1);
11712
11729
  }
11713
- const { cleanupAllLogs } = await import("../specialist-logs-7TQMHCUN.js");
11730
+ const { cleanupAllLogs } = await import("../specialist-logs-GF3YV4KL.js");
11714
11731
  console.log("\u{1F9F9} Cleaning up old logs for all projects...\n");
11715
11732
  const results = cleanupAllLogs();
11716
11733
  console.log(`
@@ -11737,7 +11754,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
11737
11754
  console.log(" Use --force to confirm.");
11738
11755
  process.exit(1);
11739
11756
  }
11740
- const { cleanupOldLogs } = await import("../specialist-logs-7TQMHCUN.js");
11757
+ const { cleanupOldLogs } = await import("../specialist-logs-GF3YV4KL.js");
11741
11758
  const { getSpecialistRetention } = await import("../projects-VXRUCMLM.js");
11742
11759
  const retention = getSpecialistRetention(projectOrAll);
11743
11760
  console.log(`\u{1F9F9} Cleaning up old logs for ${projectOrAll}/${type}...`);
@@ -12859,6 +12876,7 @@ async function doctorCommand() {
12859
12876
 
12860
12877
  // src/cli/commands/update.ts
12861
12878
  init_esm_shims();
12879
+ init_config();
12862
12880
  import { execSync as execSync9 } from "child_process";
12863
12881
  import chalk47 from "chalk";
12864
12882
  import { readFileSync as readFileSync37 } from "fs";
@@ -13859,6 +13877,7 @@ init_esm_shims();
13859
13877
 
13860
13878
  // src/cli/commands/remote/status.ts
13861
13879
  init_esm_shims();
13880
+ init_config();
13862
13881
  import chalk51 from "chalk";
13863
13882
  import ora23 from "ora";
13864
13883
  async function statusCommand5(options) {
@@ -13938,6 +13957,7 @@ async function statusCommand5(options) {
13938
13957
 
13939
13958
  // src/cli/commands/remote/init.ts
13940
13959
  init_esm_shims();
13960
+ init_config();
13941
13961
  import chalk52 from "chalk";
13942
13962
  import ora24 from "ora";
13943
13963
  async function initCommand2(options) {
@@ -14099,6 +14119,7 @@ COMPOSE_EOF`);
14099
14119
 
14100
14120
  // src/cli/commands/remote/resources.ts
14101
14121
  init_esm_shims();
14122
+ init_config();
14102
14123
  import chalk53 from "chalk";
14103
14124
  import ora25 from "ora";
14104
14125
  async function resourcesCommand(options) {
@@ -14227,6 +14248,7 @@ async function resourcesCommand(options) {
14227
14248
 
14228
14249
  // src/cli/commands/remote/setup.ts
14229
14250
  init_esm_shims();
14251
+ init_config();
14230
14252
  import chalk54 from "chalk";
14231
14253
  import ora26 from "ora";
14232
14254
  import { exec as exec16 } from "child_process";
@@ -14377,6 +14399,7 @@ function registerRemoteCommands(program2) {
14377
14399
 
14378
14400
  // src/cli/commands/config.ts
14379
14401
  init_esm_shims();
14402
+ init_config();
14380
14403
  import chalk55 from "chalk";
14381
14404
 
14382
14405
  // src/lib/env-loader.ts
@@ -14549,7 +14572,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
14549
14572
  console.log(chalk56.bold("Starting Panopticon...\n"));
14550
14573
  if (traefikEnabled && !options.skipTraefik) {
14551
14574
  try {
14552
- const { generatePanopticonTraefikConfig: generatePanopticonTraefikConfig2 } = await import("../traefik-7OLLXUD7.js");
14575
+ const { generatePanopticonTraefikConfig: generatePanopticonTraefikConfig2 } = await import("../traefik-CUJM6K5Z.js");
14553
14576
  if (generatePanopticonTraefikConfig2()) {
14554
14577
  console.log(chalk56.dim(" Regenerated Traefik config from template"));
14555
14578
  }