panopticon-cli 0.5.4 → 0.5.7
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-HNMF52RM.js → agents-QXVDAW2M.js} +12 -9
- package/dist/archive-planning-U3AZAKWI.js +16 -0
- package/dist/{chunk-KBHRXV5T.js → chunk-43F4LDZ4.js} +3 -3
- package/dist/{chunk-KY2E2Q3T.js → chunk-4XR62WWV.js} +105 -46
- package/dist/chunk-4XR62WWV.js.map +1 -0
- package/dist/chunk-6OYUJ4AJ.js +146 -0
- package/dist/chunk-6OYUJ4AJ.js.map +1 -0
- package/dist/{chunk-MOPGR3CL.js → chunk-AAP4G6U7.js} +1 -1
- package/dist/chunk-AAP4G6U7.js.map +1 -0
- package/dist/{chunk-4HST45MO.js → chunk-BYWVPPAZ.js} +19 -12
- package/dist/chunk-BYWVPPAZ.js.map +1 -0
- package/dist/{chunk-CFCUOV3Q.js → chunk-DMRTN432.js} +4 -1
- package/dist/chunk-DMRTN432.js.map +1 -0
- package/dist/{chunk-HOGYHJ2G.js → chunk-DW3PKGIS.js} +2 -2
- package/dist/chunk-GUV2EPBG.js +692 -0
- package/dist/chunk-GUV2EPBG.js.map +1 -0
- package/dist/{chunk-44EOY2ZL.js → chunk-HHL3AWXA.js} +46 -2
- package/dist/chunk-HHL3AWXA.js.map +1 -0
- package/dist/{chunk-6N2KBSJA.js → chunk-IZIXJYXZ.js} +40 -6
- package/dist/chunk-IZIXJYXZ.js.map +1 -0
- package/dist/chunk-MJXYTGK5.js +64 -0
- package/dist/chunk-MJXYTGK5.js.map +1 -0
- package/dist/chunk-OJF4QS3S.js +269 -0
- package/dist/chunk-OJF4QS3S.js.map +1 -0
- package/dist/{chunk-FQ66DECN.js → chunk-QAJAJBFW.js} +1 -1
- package/dist/chunk-QAJAJBFW.js.map +1 -0
- package/dist/chunk-R4KPLLRB.js +36 -0
- package/dist/chunk-R4KPLLRB.js.map +1 -0
- package/dist/{chunk-DFNVHK3N.js → chunk-SUM2WVPF.js} +4 -4
- package/dist/{chunk-ID4OYXVH.js → chunk-TFPJD2I2.js} +112 -45
- package/dist/chunk-TFPJD2I2.js.map +1 -0
- package/dist/{chunk-T7BBPDEJ.js → chunk-UKSGE6RH.js} +45 -15
- package/dist/chunk-UKSGE6RH.js.map +1 -0
- package/dist/chunk-W2OTF6OS.js +201 -0
- package/dist/chunk-W2OTF6OS.js.map +1 -0
- package/dist/chunk-WEQW3EAT.js +78 -0
- package/dist/chunk-WEQW3EAT.js.map +1 -0
- package/dist/chunk-YAAT66RT.js +70 -0
- package/dist/chunk-YAAT66RT.js.map +1 -0
- package/dist/{chunk-RLZQB7HS.js → chunk-ZMJFEHGF.js} +13 -1
- package/dist/chunk-ZMJFEHGF.js.map +1 -0
- package/dist/{chunk-HRU7S4TA.js → chunk-ZN5RHWGR.js} +18 -208
- package/dist/{chunk-HRU7S4TA.js.map → chunk-ZN5RHWGR.js.map} +1 -1
- package/dist/{chunk-ZTYHZMEC.js → chunk-ZWZNEA26.js} +2 -2
- package/dist/clean-planning-7Z5YY64X.js +9 -0
- package/dist/cli/index.js +1338 -2226
- package/dist/cli/index.js.map +1 -1
- package/dist/close-issue-CTZK777I.js +9 -0
- package/dist/compact-beads-72SHALOL.js +9 -0
- package/dist/{config-4CJNUE3O.js → config-FFTMBVHM.js} +2 -2
- package/dist/dashboard/public/assets/{index-DSvt5pPn.css → index-Bx4NCn9A.css} +1 -1
- package/dist/dashboard/public/assets/index-C7hJ5-o1.js +756 -0
- package/dist/dashboard/public/index.html +3 -2
- package/dist/dashboard/server.js +34720 -34297
- package/dist/{feedback-writer-T43PI5S2.js → feedback-writer-T2WCT6EZ.js} +2 -2
- package/dist/{hume-CKJJ3OUU.js → hume-GVTB5BKW.js} +3 -3
- package/dist/index.d.ts +24 -16
- package/dist/index.js +4 -4
- package/dist/label-cleanup-4HJVX6NP.js +103 -0
- package/dist/label-cleanup-4HJVX6NP.js.map +1 -0
- package/dist/merge-agent-O3TSBTLC.js +1725 -0
- package/dist/merge-agent-O3TSBTLC.js.map +1 -0
- package/dist/{projects-KVM3MN3Y.js → projects-3CRF57ZU.js} +2 -2
- package/dist/{rally-RKFSWC7E.js → rally-LBY24P4C.js} +2 -2
- package/dist/{remote-agents-ULPD6C5U.js → remote-agents-3NZPSHYG.js} +2 -3
- package/dist/{remote-workspace-XX6ARE6I.js → remote-workspace-M4IULGFZ.js} +24 -49
- package/dist/remote-workspace-M4IULGFZ.js.map +1 -0
- package/dist/{review-status-XKUKZF6J.js → review-status-J2YJGL3E.js} +2 -2
- package/dist/{specialist-context-C66TEMXS.js → specialist-context-IKG6VMNH.js} +7 -5
- package/dist/{specialist-context-C66TEMXS.js.map → specialist-context-IKG6VMNH.js.map} +1 -1
- package/dist/{specialist-logs-CJKXM3SR.js → specialist-logs-GFKUXCFG.js} +6 -4
- package/dist/{specialists-NXYD4Z62.js → specialists-XMFCFGYQ.js} +6 -4
- package/dist/specialists-XMFCFGYQ.js.map +1 -0
- package/dist/tmux-X2I5SAIJ.js +31 -0
- package/dist/tmux-X2I5SAIJ.js.map +1 -0
- package/dist/{traefik-5GL3Q7DJ.js → traefik-QXLZ4PO2.js} +4 -4
- package/dist/traefik-QXLZ4PO2.js.map +1 -0
- package/dist/{tunnel-BKC7KLBX.js → tunnel-7IOSRZVH.js} +3 -3
- package/dist/tunnel-7IOSRZVH.js.map +1 -0
- package/dist/{workspace-manager-ALBR62AS.js → workspace-manager-G6TTBPC3.js} +6 -6
- package/dist/workspace-manager-G6TTBPC3.js.map +1 -0
- package/package.json +2 -2
- package/scripts/build-cost-script.mjs +17 -0
- package/scripts/heartbeat-hook +28 -8
- package/scripts/record-cost-event.js +46 -7
- package/scripts/record-cost-event.ts +2 -1
- package/dist/chunk-44EOY2ZL.js.map +0 -1
- package/dist/chunk-4HST45MO.js.map +0 -1
- package/dist/chunk-565HZ6VV.js +0 -159
- package/dist/chunk-565HZ6VV.js.map +0 -1
- package/dist/chunk-6N2KBSJA.js.map +0 -1
- package/dist/chunk-CFCUOV3Q.js.map +0 -1
- package/dist/chunk-FQ66DECN.js.map +0 -1
- package/dist/chunk-ID4OYXVH.js.map +0 -1
- package/dist/chunk-KY2E2Q3T.js.map +0 -1
- package/dist/chunk-MOPGR3CL.js.map +0 -1
- package/dist/chunk-RLZQB7HS.js.map +0 -1
- package/dist/chunk-T7BBPDEJ.js.map +0 -1
- package/dist/chunk-ZDNQFWR5.js +0 -650
- package/dist/chunk-ZDNQFWR5.js.map +0 -1
- package/dist/dashboard/public/assets/index-DA6pnizT.js +0 -767
- package/dist/remote-workspace-XX6ARE6I.js.map +0 -1
- /package/dist/{agents-HNMF52RM.js.map → agents-QXVDAW2M.js.map} +0 -0
- /package/dist/{config-4CJNUE3O.js.map → archive-planning-U3AZAKWI.js.map} +0 -0
- /package/dist/{chunk-KBHRXV5T.js.map → chunk-43F4LDZ4.js.map} +0 -0
- /package/dist/{chunk-HOGYHJ2G.js.map → chunk-DW3PKGIS.js.map} +0 -0
- /package/dist/{chunk-DFNVHK3N.js.map → chunk-SUM2WVPF.js.map} +0 -0
- /package/dist/{chunk-ZTYHZMEC.js.map → chunk-ZWZNEA26.js.map} +0 -0
- /package/dist/{hume-CKJJ3OUU.js.map → clean-planning-7Z5YY64X.js.map} +0 -0
- /package/dist/{projects-KVM3MN3Y.js.map → close-issue-CTZK777I.js.map} +0 -0
- /package/dist/{rally-RKFSWC7E.js.map → compact-beads-72SHALOL.js.map} +0 -0
- /package/dist/{remote-agents-ULPD6C5U.js.map → config-FFTMBVHM.js.map} +0 -0
- /package/dist/{feedback-writer-T43PI5S2.js.map → feedback-writer-T2WCT6EZ.js.map} +0 -0
- /package/dist/{review-status-XKUKZF6J.js.map → hume-GVTB5BKW.js.map} +0 -0
- /package/dist/{specialist-logs-CJKXM3SR.js.map → projects-3CRF57ZU.js.map} +0 -0
- /package/dist/{specialists-NXYD4Z62.js.map → rally-LBY24P4C.js.map} +0 -0
- /package/dist/{traefik-5GL3Q7DJ.js.map → remote-agents-3NZPSHYG.js.map} +0 -0
- /package/dist/{tunnel-BKC7KLBX.js.map → review-status-J2YJGL3E.js.map} +0 -0
- /package/dist/{workspace-manager-ALBR62AS.js.map → specialist-logs-GFKUXCFG.js.map} +0 -0
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getRecentRunLogs,
|
|
3
3
|
init_specialist_logs
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-TFPJD2I2.js";
|
|
5
|
+
import "./chunk-JQBV3Q2W.js";
|
|
5
6
|
import {
|
|
6
7
|
getModelId,
|
|
7
8
|
init_work_type_router
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-JQBV3Q2W.js";
|
|
9
|
+
} from "./chunk-ZN5RHWGR.js";
|
|
10
10
|
import "./chunk-USYP2SBE.js";
|
|
11
|
+
import "./chunk-QAJAJBFW.js";
|
|
11
12
|
import {
|
|
12
13
|
getProject,
|
|
13
14
|
init_projects
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-ZMJFEHGF.js";
|
|
16
|
+
import "./chunk-W2OTF6OS.js";
|
|
15
17
|
import "./chunk-ZP6EWSZV.js";
|
|
16
18
|
import {
|
|
17
19
|
getPanopticonHome,
|
|
@@ -255,4 +257,4 @@ export {
|
|
|
255
257
|
regenerateContextDigest,
|
|
256
258
|
scheduleDigestGeneration
|
|
257
259
|
};
|
|
258
|
-
//# sourceMappingURL=specialist-context-
|
|
260
|
+
//# sourceMappingURL=specialist-context-IKG6VMNH.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/cloister/specialist-context.ts"],"sourcesContent":["/**\n * Specialist Context Management\n *\n * Generates and manages AI-powered context digests from recent specialist runs.\n * These digests seed new specialist sessions with learned patterns and expertise.\n *\n * Directory structure:\n * ~/.panopticon/specialists/{projectKey}/{specialistType}/context/latest-digest.md\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { getPanopticonHome } from '../paths.js';\nimport { getRecentRunLogs, type RunLogEntry } from './specialist-logs.js';\nimport { getProject } from '../projects.js';\nimport { getModelId } from '../work-type-router.js';\n\nconst execAsync = promisify(exec);\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getPanopticonHome(), 'specialists');\n}\n\n/**\n * Get the context directory for a project's specialist\n */\nexport function getContextDirectory(projectKey: string, specialistType: string): string {\n return join(getSpecialistsDir(), projectKey, specialistType, 'context');\n}\n\n/**\n * Get the path to the latest context digest file\n */\nexport function getContextDigestPath(projectKey: string, specialistType: string): string {\n const contextDir = getContextDirectory(projectKey, specialistType);\n return join(contextDir, 'latest-digest.md');\n}\n\n/**\n * Ensure context directory exists for a project's specialist\n */\nfunction ensureContextDirectory(projectKey: string, specialistType: string): void {\n const contextDir = getContextDirectory(projectKey, specialistType);\n if (!existsSync(contextDir)) {\n mkdirSync(contextDir, { recursive: true });\n }\n}\n\n/**\n * Load the context digest for a specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Context digest content or null if not found\n */\nexport function loadContextDigest(projectKey: string, specialistType: string): string | null {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return null;\n }\n\n try {\n return readFileSync(digestPath, 'utf-8');\n } catch (error) {\n console.error(`[specialist-context] Failed to load digest for ${projectKey}/${specialistType}:`, error);\n return null;\n }\n}\n\n/**\n * Get the number of recent runs to include in context\n *\n * Reads from project config or uses default.\n *\n * @param projectKey - Project identifier\n * @returns Number of runs to include (default: 5)\n */\nfunction getContextRunsCount(projectKey: string): number {\n const project = getProject(projectKey);\n return project?.specialists?.context_runs ?? 5;\n}\n\n/**\n * Get the model to use for digest generation\n *\n * Reads from project config or uses the same model as the specialist.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Model ID to use\n */\nfunction getDigestModel(projectKey: string, specialistType: string): string {\n const project = getProject(projectKey);\n\n // Check for explicit digest model in project config\n if (project?.specialists?.digest_model) {\n return project.specialists.digest_model;\n }\n\n // Fall back to specialist's model\n try {\n const workTypeId = `specialist-${specialistType}` as any;\n return getModelId(workTypeId);\n } catch (error) {\n // Default to Sonnet if can't resolve\n return 'claude-sonnet-4-6';\n }\n}\n\n/**\n * Generate a context digest from recent runs using AI\n *\n * Creates an AI-generated summary of recent specialist runs to provide\n * context for the next run. This includes patterns, learnings, and common issues.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param options - Generation options\n * @returns Generated digest or null if generation failed\n */\nexport async function generateContextDigest(\n projectKey: string,\n specialistType: string,\n options: {\n runCount?: number;\n model?: string;\n force?: boolean; // Generate even if no recent runs\n } = {}\n): Promise<string | null> {\n ensureContextDirectory(projectKey, specialistType);\n\n // Get recent runs\n const runCount = options.runCount ?? getContextRunsCount(projectKey);\n const recentRuns = getRecentRunLogs(projectKey, specialistType, runCount);\n\n if (recentRuns.length === 0 && !options.force) {\n console.log(`[specialist-context] No recent runs for ${projectKey}/${specialistType}, skipping digest generation`);\n return null;\n }\n\n // Build prompt for digest generation\n const prompt = buildDigestPrompt(projectKey, specialistType, recentRuns);\n const model = options.model ?? getDigestModel(projectKey, specialistType);\n\n try {\n console.log(`[specialist-context] Generating digest for ${projectKey}/${specialistType} using ${model}...`);\n\n // Use Claude Code CLI to generate digest\n // Write prompt to temp file to avoid shell escaping issues\n const tempDir = join(getPanopticonHome(), 'tmp');\n if (!existsSync(tempDir)) {\n mkdirSync(tempDir, { recursive: true });\n }\n\n const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);\n writeFileSync(promptFile, prompt, 'utf-8');\n\n // Run Claude Code with the prompt\n const { stdout, stderr } = await execAsync(\n `claude --dangerously-skip-permissions --model ${model} \"$(cat '${promptFile}')\"`,\n {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer\n timeout: 60000, // 60 second timeout\n }\n );\n\n // Clean up temp file\n try {\n unlinkSync(promptFile);\n } catch {\n // Ignore cleanup errors\n }\n\n if (stderr && !stderr.includes('warning')) {\n console.error(`[specialist-context] Claude stderr:`, stderr);\n }\n\n const digest = stdout.trim();\n\n if (!digest) {\n console.error(`[specialist-context] Empty digest generated`);\n return null;\n }\n\n // Save digest\n const digestPath = getContextDigestPath(projectKey, specialistType);\n writeFileSync(digestPath, digest, 'utf-8');\n\n console.log(`[specialist-context] Generated digest (${digest.length} chars)`);\n return digest;\n } catch (error: any) {\n console.error(`[specialist-context] Failed to generate digest:`, error.message);\n // Degrade gracefully - return null so specialist can continue without context\n return null;\n }\n}\n\n/**\n * Build the prompt for digest generation\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param recentRuns - Recent run logs\n * @returns Prompt for Claude\n */\nfunction buildDigestPrompt(\n projectKey: string,\n specialistType: string,\n recentRuns: RunLogEntry[]\n): string {\n const project = getProject(projectKey);\n const projectName = project?.name || projectKey;\n\n let prompt = `You are analyzing the recent history of a ${specialistType} specialist for the ${projectName} project.\n\nYour task is to generate a concise context digest that will be provided to the specialist at the start of their next run. This digest should help them understand:\n- Common patterns and practices observed in recent runs\n- Recurring issues or failure modes\n- Successful approaches and best practices\n- Any project-specific context that would be helpful\n\nGenerate a digest in markdown format. Keep it focused and actionable - aim for 200-400 words total.\n\n## Recent Runs\n\n`;\n\n if (recentRuns.length === 0) {\n prompt += `No recent runs available yet. This is the specialist's first run.\\n\\n`;\n prompt += `Generate a brief introduction for the specialist explaining their role and what to expect.\\n`;\n } else {\n recentRuns.forEach((run, index) => {\n prompt += `### Run ${index + 1}: ${run.metadata.issueId} (${run.metadata.status || 'unknown'})\\n`;\n prompt += `Started: ${run.metadata.startedAt}\\n`;\n if (run.metadata.finishedAt) {\n prompt += `Finished: ${run.metadata.finishedAt}\\n`;\n }\n if (run.metadata.duration) {\n const durationSec = Math.floor(run.metadata.duration / 1000);\n const minutes = Math.floor(durationSec / 60);\n const seconds = durationSec % 60;\n prompt += `Duration: ${minutes}m ${seconds}s\\n`;\n }\n if (run.metadata.notes) {\n prompt += `Notes: ${run.metadata.notes}\\n`;\n }\n\n // Include snippets from the log if available\n try {\n const logContent = readFileSync(run.filePath, 'utf-8');\n // Extract key sections (limit to avoid overwhelming the prompt)\n const maxChars = 500;\n const transcriptMatch = logContent.match(/## Session Transcript\\n([\\s\\S]+?)(?=\\n## |$)/);\n if (transcriptMatch) {\n let transcript = transcriptMatch[1].trim();\n if (transcript.length > maxChars) {\n transcript = transcript.substring(0, maxChars) + '... [truncated]';\n }\n prompt += `\\nTranscript excerpt:\\n${transcript}\\n`;\n }\n } catch (error) {\n // If we can't read the log, skip the excerpt\n }\n\n prompt += `\\n`;\n });\n }\n\n prompt += `\\n## Your Task\n\nGenerate a context digest that summarizes the key insights from these runs. Format it as:\n\n# Recent ${specialistType} History for ${projectName}\n\n## Summary\n[2-3 sentence overview of patterns and trends]\n\n## Common Patterns\n[Bulleted list of observed patterns]\n\n## Recent Notable Runs\n[Brief highlights of 2-3 most interesting runs]\n\n## Recommendations\n[Specific guidance for the next run based on this history]\n\nKeep it concise, actionable, and focused on helping the specialist be more effective.`;\n\n return prompt;\n}\n\n/**\n * Regenerate the context digest\n *\n * Forces regeneration even if a digest already exists.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Generated digest or null if generation failed\n */\nexport async function regenerateContextDigest(\n projectKey: string,\n specialistType: string\n): Promise<string | null> {\n return generateContextDigest(projectKey, specialistType, { force: true });\n}\n\n/**\n * Generate digest after a run completes (async, fire-and-forget)\n *\n * This is called after a specialist finishes a run to update the context\n * for the next run. It runs asynchronously and failures are logged but not thrown.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nexport function scheduleDigestGeneration(projectKey: string, specialistType: string): void {\n // Run async without awaiting\n generateContextDigest(projectKey, specialistType).catch((error) => {\n console.error(\n `[specialist-context] Background digest generation failed for ${projectKey}/${specialistType}:`,\n error\n );\n });\n}\n\n/**\n * Check if a context digest exists\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest file exists\n */\nexport function hasContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n return existsSync(digestPath);\n}\n\n/**\n * Delete the context digest\n *\n * Useful for forcing a fresh start or clearing stale context.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest was deleted, false if it didn't exist\n */\nexport function deleteContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return false;\n }\n\n try {\n unlinkSync(digestPath);\n return true;\n } catch (error) {\n console.error(`[specialist-context] Failed to delete digest:`, error);\n return false;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/cloister/specialist-context.ts"],"sourcesContent":["/**\n * Specialist Context Management\n *\n * Generates and manages AI-powered context digests from recent specialist runs.\n * These digests seed new specialist sessions with learned patterns and expertise.\n *\n * Directory structure:\n * ~/.panopticon/specialists/{projectKey}/{specialistType}/context/latest-digest.md\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { getPanopticonHome } from '../paths.js';\nimport { getRecentRunLogs, type RunLogEntry } from './specialist-logs.js';\nimport { getProject } from '../projects.js';\nimport { getModelId } from '../work-type-router.js';\n\nconst execAsync = promisify(exec);\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getPanopticonHome(), 'specialists');\n}\n\n/**\n * Get the context directory for a project's specialist\n */\nexport function getContextDirectory(projectKey: string, specialistType: string): string {\n return join(getSpecialistsDir(), projectKey, specialistType, 'context');\n}\n\n/**\n * Get the path to the latest context digest file\n */\nexport function getContextDigestPath(projectKey: string, specialistType: string): string {\n const contextDir = getContextDirectory(projectKey, specialistType);\n return join(contextDir, 'latest-digest.md');\n}\n\n/**\n * Ensure context directory exists for a project's specialist\n */\nfunction ensureContextDirectory(projectKey: string, specialistType: string): void {\n const contextDir = getContextDirectory(projectKey, specialistType);\n if (!existsSync(contextDir)) {\n mkdirSync(contextDir, { recursive: true });\n }\n}\n\n/**\n * Load the context digest for a specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Context digest content or null if not found\n */\nexport function loadContextDigest(projectKey: string, specialistType: string): string | null {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return null;\n }\n\n try {\n return readFileSync(digestPath, 'utf-8');\n } catch (error) {\n console.error(`[specialist-context] Failed to load digest for ${projectKey}/${specialistType}:`, error);\n return null;\n }\n}\n\n/**\n * Get the number of recent runs to include in context\n *\n * Reads from project config or uses default.\n *\n * @param projectKey - Project identifier\n * @returns Number of runs to include (default: 5)\n */\nfunction getContextRunsCount(projectKey: string): number {\n const project = getProject(projectKey);\n return project?.specialists?.context_runs ?? 5;\n}\n\n/**\n * Get the model to use for digest generation\n *\n * Reads from project config or uses the same model as the specialist.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Model ID to use\n */\nfunction getDigestModel(projectKey: string, specialistType: string): string {\n const project = getProject(projectKey);\n\n // Check for explicit digest model in project config\n if (project?.specialists?.digest_model) {\n return project.specialists.digest_model;\n }\n\n // Fall back to specialist's model\n try {\n const workTypeId = `specialist-${specialistType}` as any;\n return getModelId(workTypeId);\n } catch (error) {\n // Default to Sonnet if can't resolve\n return 'claude-sonnet-4-6';\n }\n}\n\n/**\n * Generate a context digest from recent runs using AI\n *\n * Creates an AI-generated summary of recent specialist runs to provide\n * context for the next run. This includes patterns, learnings, and common issues.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param options - Generation options\n * @returns Generated digest or null if generation failed\n */\nexport async function generateContextDigest(\n projectKey: string,\n specialistType: string,\n options: {\n runCount?: number;\n model?: string;\n force?: boolean; // Generate even if no recent runs\n } = {}\n): Promise<string | null> {\n ensureContextDirectory(projectKey, specialistType);\n\n // Get recent runs\n const runCount = options.runCount ?? getContextRunsCount(projectKey);\n const recentRuns = getRecentRunLogs(projectKey, specialistType, runCount);\n\n if (recentRuns.length === 0 && !options.force) {\n console.log(`[specialist-context] No recent runs for ${projectKey}/${specialistType}, skipping digest generation`);\n return null;\n }\n\n // Build prompt for digest generation\n const prompt = buildDigestPrompt(projectKey, specialistType, recentRuns);\n const model = options.model ?? getDigestModel(projectKey, specialistType);\n\n try {\n console.log(`[specialist-context] Generating digest for ${projectKey}/${specialistType} using ${model}...`);\n\n // Use Claude Code CLI to generate digest\n // Write prompt to temp file to avoid shell escaping issues\n const tempDir = join(getPanopticonHome(), 'tmp');\n if (!existsSync(tempDir)) {\n mkdirSync(tempDir, { recursive: true });\n }\n\n const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);\n writeFileSync(promptFile, prompt, 'utf-8');\n\n // Run Claude Code with the prompt\n const { stdout, stderr } = await execAsync(\n `claude --dangerously-skip-permissions --model ${model} \"$(cat '${promptFile}')\"`,\n {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer\n timeout: 60000, // 60 second timeout\n }\n );\n\n // Clean up temp file\n try {\n unlinkSync(promptFile);\n } catch {\n // Ignore cleanup errors\n }\n\n if (stderr && !stderr.includes('warning')) {\n console.error(`[specialist-context] Claude stderr:`, stderr);\n }\n\n const digest = stdout.trim();\n\n if (!digest) {\n console.error(`[specialist-context] Empty digest generated`);\n return null;\n }\n\n // Save digest\n const digestPath = getContextDigestPath(projectKey, specialistType);\n writeFileSync(digestPath, digest, 'utf-8');\n\n console.log(`[specialist-context] Generated digest (${digest.length} chars)`);\n return digest;\n } catch (error: any) {\n console.error(`[specialist-context] Failed to generate digest:`, error.message);\n // Degrade gracefully - return null so specialist can continue without context\n return null;\n }\n}\n\n/**\n * Build the prompt for digest generation\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param recentRuns - Recent run logs\n * @returns Prompt for Claude\n */\nfunction buildDigestPrompt(\n projectKey: string,\n specialistType: string,\n recentRuns: RunLogEntry[]\n): string {\n const project = getProject(projectKey);\n const projectName = project?.name || projectKey;\n\n let prompt = `You are analyzing the recent history of a ${specialistType} specialist for the ${projectName} project.\n\nYour task is to generate a concise context digest that will be provided to the specialist at the start of their next run. This digest should help them understand:\n- Common patterns and practices observed in recent runs\n- Recurring issues or failure modes\n- Successful approaches and best practices\n- Any project-specific context that would be helpful\n\nGenerate a digest in markdown format. Keep it focused and actionable - aim for 200-400 words total.\n\n## Recent Runs\n\n`;\n\n if (recentRuns.length === 0) {\n prompt += `No recent runs available yet. This is the specialist's first run.\\n\\n`;\n prompt += `Generate a brief introduction for the specialist explaining their role and what to expect.\\n`;\n } else {\n recentRuns.forEach((run, index) => {\n prompt += `### Run ${index + 1}: ${run.metadata.issueId} (${run.metadata.status || 'unknown'})\\n`;\n prompt += `Started: ${run.metadata.startedAt}\\n`;\n if (run.metadata.finishedAt) {\n prompt += `Finished: ${run.metadata.finishedAt}\\n`;\n }\n if (run.metadata.duration) {\n const durationSec = Math.floor(run.metadata.duration / 1000);\n const minutes = Math.floor(durationSec / 60);\n const seconds = durationSec % 60;\n prompt += `Duration: ${minutes}m ${seconds}s\\n`;\n }\n if (run.metadata.notes) {\n prompt += `Notes: ${run.metadata.notes}\\n`;\n }\n\n // Include snippets from the log if available\n try {\n const logContent = readFileSync(run.filePath, 'utf-8');\n // Extract key sections (limit to avoid overwhelming the prompt)\n const maxChars = 500;\n const transcriptMatch = logContent.match(/## Session Transcript\\n([\\s\\S]+?)(?=\\n## |$)/);\n if (transcriptMatch) {\n let transcript = transcriptMatch[1].trim();\n if (transcript.length > maxChars) {\n transcript = transcript.substring(0, maxChars) + '... [truncated]';\n }\n prompt += `\\nTranscript excerpt:\\n${transcript}\\n`;\n }\n } catch (error) {\n // If we can't read the log, skip the excerpt\n }\n\n prompt += `\\n`;\n });\n }\n\n prompt += `\\n## Your Task\n\nGenerate a context digest that summarizes the key insights from these runs. Format it as:\n\n# Recent ${specialistType} History for ${projectName}\n\n## Summary\n[2-3 sentence overview of patterns and trends]\n\n## Common Patterns\n[Bulleted list of observed patterns]\n\n## Recent Notable Runs\n[Brief highlights of 2-3 most interesting runs]\n\n## Recommendations\n[Specific guidance for the next run based on this history]\n\nKeep it concise, actionable, and focused on helping the specialist be more effective.`;\n\n return prompt;\n}\n\n/**\n * Regenerate the context digest\n *\n * Forces regeneration even if a digest already exists.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Generated digest or null if generation failed\n */\nexport async function regenerateContextDigest(\n projectKey: string,\n specialistType: string\n): Promise<string | null> {\n return generateContextDigest(projectKey, specialistType, { force: true });\n}\n\n/**\n * Generate digest after a run completes (async, fire-and-forget)\n *\n * This is called after a specialist finishes a run to update the context\n * for the next run. It runs asynchronously and failures are logged but not thrown.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nexport function scheduleDigestGeneration(projectKey: string, specialistType: string): void {\n // Run async without awaiting\n generateContextDigest(projectKey, specialistType).catch((error) => {\n console.error(\n `[specialist-context] Background digest generation failed for ${projectKey}/${specialistType}:`,\n error\n );\n });\n}\n\n/**\n * Check if a context digest exists\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest file exists\n */\nexport function hasContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n return existsSync(digestPath);\n}\n\n/**\n * Delete the context digest\n *\n * Useful for forcing a fresh start or clearing stale context.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest was deleted, false if it didn't exist\n */\nexport function deleteContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return false;\n }\n\n try {\n unlinkSync(digestPath);\n return true;\n } catch (error) {\n console.error(`[specialist-context] Failed to delete digest:`, error);\n return false;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -16,11 +16,13 @@ import {
|
|
|
16
16
|
isRunLogActive,
|
|
17
17
|
listRunLogs,
|
|
18
18
|
parseLogMetadata
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-HRU7S4TA.js";
|
|
19
|
+
} from "./chunk-TFPJD2I2.js";
|
|
21
20
|
import "./chunk-JQBV3Q2W.js";
|
|
21
|
+
import "./chunk-ZN5RHWGR.js";
|
|
22
22
|
import "./chunk-USYP2SBE.js";
|
|
23
|
-
import "./chunk-
|
|
23
|
+
import "./chunk-QAJAJBFW.js";
|
|
24
|
+
import "./chunk-ZMJFEHGF.js";
|
|
25
|
+
import "./chunk-W2OTF6OS.js";
|
|
24
26
|
import "./chunk-ZP6EWSZV.js";
|
|
25
27
|
import "./chunk-ZTFNYOC7.js";
|
|
26
28
|
import "./chunk-ZHC57RCV.js";
|
|
@@ -43,4 +45,4 @@ export {
|
|
|
43
45
|
listRunLogs,
|
|
44
46
|
parseLogMetadata
|
|
45
47
|
};
|
|
46
|
-
//# sourceMappingURL=specialist-logs-
|
|
48
|
+
//# sourceMappingURL=specialist-logs-GFKUXCFG.js.map
|
|
@@ -55,11 +55,13 @@ import {
|
|
|
55
55
|
wakeSpecialist,
|
|
56
56
|
wakeSpecialistOrQueue,
|
|
57
57
|
wakeSpecialistWithTask
|
|
58
|
-
} from "./chunk-
|
|
59
|
-
import "./chunk-HRU7S4TA.js";
|
|
58
|
+
} from "./chunk-TFPJD2I2.js";
|
|
60
59
|
import "./chunk-JQBV3Q2W.js";
|
|
60
|
+
import "./chunk-ZN5RHWGR.js";
|
|
61
61
|
import "./chunk-USYP2SBE.js";
|
|
62
|
-
import "./chunk-
|
|
62
|
+
import "./chunk-QAJAJBFW.js";
|
|
63
|
+
import "./chunk-ZMJFEHGF.js";
|
|
64
|
+
import "./chunk-W2OTF6OS.js";
|
|
63
65
|
import "./chunk-ZP6EWSZV.js";
|
|
64
66
|
import "./chunk-ZTFNYOC7.js";
|
|
65
67
|
import "./chunk-ZHC57RCV.js";
|
|
@@ -121,4 +123,4 @@ export {
|
|
|
121
123
|
wakeSpecialistOrQueue,
|
|
122
124
|
wakeSpecialistWithTask
|
|
123
125
|
};
|
|
124
|
-
//# sourceMappingURL=specialists-
|
|
126
|
+
//# sourceMappingURL=specialists-XMFCFGYQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
capturePane,
|
|
3
|
+
capturePaneAsync,
|
|
4
|
+
confirmDelivery,
|
|
5
|
+
createSession,
|
|
6
|
+
getAgentSessions,
|
|
7
|
+
init_tmux,
|
|
8
|
+
killSession,
|
|
9
|
+
listSessions,
|
|
10
|
+
sendKeys,
|
|
11
|
+
sendKeysAsync,
|
|
12
|
+
sessionExists,
|
|
13
|
+
waitForClaudePrompt
|
|
14
|
+
} from "./chunk-W2OTF6OS.js";
|
|
15
|
+
import "./chunk-ZTFNYOC7.js";
|
|
16
|
+
import "./chunk-ZHC57RCV.js";
|
|
17
|
+
init_tmux();
|
|
18
|
+
export {
|
|
19
|
+
capturePane,
|
|
20
|
+
capturePaneAsync,
|
|
21
|
+
confirmDelivery,
|
|
22
|
+
createSession,
|
|
23
|
+
getAgentSessions,
|
|
24
|
+
killSession,
|
|
25
|
+
listSessions,
|
|
26
|
+
sendKeys,
|
|
27
|
+
sendKeysAsync,
|
|
28
|
+
sessionExists,
|
|
29
|
+
waitForClaudePrompt
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=tmux-X2I5SAIJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
ensureProjectCerts,
|
|
5
5
|
generatePanopticonTraefikConfig,
|
|
6
6
|
generateTlsConfig
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-43F4LDZ4.js";
|
|
8
|
+
import "./chunk-QAJAJBFW.js";
|
|
9
|
+
import "./chunk-ZMJFEHGF.js";
|
|
10
10
|
import "./chunk-ZTFNYOC7.js";
|
|
11
11
|
import "./chunk-ZHC57RCV.js";
|
|
12
12
|
export {
|
|
@@ -16,4 +16,4 @@ export {
|
|
|
16
16
|
generatePanopticonTraefikConfig,
|
|
17
17
|
generateTlsConfig
|
|
18
18
|
};
|
|
19
|
-
//# sourceMappingURL=traefik-
|
|
19
|
+
//# sourceMappingURL=traefik-QXLZ4PO2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -2,12 +2,12 @@ import {
|
|
|
2
2
|
addTunnelIngress,
|
|
3
3
|
init_tunnel,
|
|
4
4
|
removeTunnelIngress
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-ZWZNEA26.js";
|
|
6
|
+
import "./chunk-AAP4G6U7.js";
|
|
7
7
|
import "./chunk-ZHC57RCV.js";
|
|
8
8
|
init_tunnel();
|
|
9
9
|
export {
|
|
10
10
|
addTunnelIngress,
|
|
11
11
|
removeTunnelIngress
|
|
12
12
|
};
|
|
13
|
-
//# sourceMappingURL=tunnel-
|
|
13
|
+
//# sourceMappingURL=tunnel-7IOSRZVH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
preTrustDirectory,
|
|
5
5
|
removeWorkspace,
|
|
6
6
|
stopWorkspaceDocker
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-BYWVPPAZ.js";
|
|
8
|
+
import "./chunk-JT4O4YVM.js";
|
|
9
|
+
import "./chunk-ZWZNEA26.js";
|
|
10
|
+
import "./chunk-DW3PKGIS.js";
|
|
11
|
+
import "./chunk-AAP4G6U7.js";
|
|
11
12
|
import "./chunk-AQXETQHW.js";
|
|
12
13
|
import "./chunk-ZTFNYOC7.js";
|
|
13
|
-
import "./chunk-JT4O4YVM.js";
|
|
14
14
|
import "./chunk-ZHC57RCV.js";
|
|
15
15
|
init_workspace_manager();
|
|
16
16
|
export {
|
|
@@ -19,4 +19,4 @@ export {
|
|
|
19
19
|
removeWorkspace,
|
|
20
20
|
stopWorkspaceDocker
|
|
21
21
|
};
|
|
22
|
-
//# sourceMappingURL=workspace-manager-
|
|
22
|
+
//# sourceMappingURL=workspace-manager-G6TTBPC3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "panopticon-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"description": "Multi-agent orchestration for AI coding assistants (Claude Code, Codex, Cursor, Gemini CLI)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"dev": "tsx watch src/cli/index.ts",
|
|
51
51
|
"build": "npm run build:cli && npm run build:scripts && npm run build:dashboard",
|
|
52
52
|
"build:cli": "tsup",
|
|
53
|
-
"build:scripts": "
|
|
53
|
+
"build:scripts": "node scripts/build-cost-script.mjs",
|
|
54
54
|
"build:dashboard": "npm run build:dashboard:frontend && npm run build:dashboard:server",
|
|
55
55
|
"build:dashboard:frontend": "cd src/dashboard/frontend && npm run build",
|
|
56
56
|
"build:dashboard:server": "cd src/dashboard/server && npm run build && mkdir -p ../../../dist/dashboard/prompts && cp ../../lib/cloister/prompts/*.md ../../../dist/dashboard/prompts/",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { build } from 'esbuild';
|
|
2
|
+
|
|
3
|
+
await build({
|
|
4
|
+
entryPoints: ['scripts/record-cost-event.ts'],
|
|
5
|
+
bundle: true,
|
|
6
|
+
platform: 'node',
|
|
7
|
+
format: 'esm',
|
|
8
|
+
outfile: 'scripts/record-cost-event.js',
|
|
9
|
+
// Do NOT externalize better-sqlite3 — this script runs standalone from ~/.panopticon/bin/
|
|
10
|
+
// where node_modules is not available. The createRequire banner handles CJS require() calls.
|
|
11
|
+
banner: {
|
|
12
|
+
js: `import { createRequire } from 'module';
|
|
13
|
+
const require = createRequire(import.meta.url);`
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
console.log('record-cost-event.js bundled successfully');
|
package/scripts/heartbeat-hook
CHANGED
|
@@ -105,28 +105,49 @@ if [ -n "$ACTIVITY_ENTRY" ]; then
|
|
|
105
105
|
fi
|
|
106
106
|
fi
|
|
107
107
|
|
|
108
|
+
# Extract Claude Code session ID from tool info (needed for runtime + cost tracking)
|
|
109
|
+
CLAUDE_SESSION_ID=$(echo "$TOOL_INFO" | jq -r '.session_id // "unknown"' 2>/dev/null || echo "unknown")
|
|
110
|
+
|
|
108
111
|
# Update agent runtime state in runtime.json (NOT state.json — prevents corrupting AgentState config)
|
|
109
112
|
RUNTIME_FILE="$AGENT_DIR/runtime.json"
|
|
110
113
|
TIMESTAMP="$(date -Iseconds)"
|
|
111
114
|
|
|
112
115
|
if [ -f "$RUNTIME_FILE" ]; then
|
|
113
|
-
# Update existing runtime state
|
|
116
|
+
# Update existing runtime state (include active Claude session ID)
|
|
114
117
|
TEMP_RUNTIME="$RUNTIME_FILE.tmp"
|
|
115
|
-
jq --arg ts "$TIMESTAMP" --arg tool "$TOOL_NAME" \
|
|
116
|
-
'.lastActivity = $ts | .currentTool = $tool | .state = "active"' \
|
|
118
|
+
jq --arg ts "$TIMESTAMP" --arg tool "$TOOL_NAME" --arg sid "$CLAUDE_SESSION_ID" \
|
|
119
|
+
'.lastActivity = $ts | .currentTool = $tool | .state = "active" | .claudeSessionId = $sid' \
|
|
117
120
|
"$RUNTIME_FILE" > "$TEMP_RUNTIME" 2>/dev/null && mv "$TEMP_RUNTIME" "$RUNTIME_FILE" 2>/dev/null || true
|
|
118
121
|
else
|
|
119
122
|
# Create initial runtime state
|
|
120
123
|
jq -n \
|
|
121
124
|
--arg ts "$TIMESTAMP" \
|
|
122
125
|
--arg tool "$TOOL_NAME" \
|
|
126
|
+
--arg sid "$CLAUDE_SESSION_ID" \
|
|
123
127
|
'{
|
|
124
128
|
state: "active",
|
|
125
129
|
lastActivity: $ts,
|
|
126
|
-
currentTool: $tool
|
|
130
|
+
currentTool: $tool,
|
|
131
|
+
claudeSessionId: $sid
|
|
127
132
|
}' > "$RUNTIME_FILE" 2>/dev/null || true
|
|
128
133
|
fi
|
|
129
134
|
|
|
135
|
+
# Maintain session history — append-only list of all Claude session UUIDs this agent has used
|
|
136
|
+
# The reconciler uses this to map transcript files back to agents
|
|
137
|
+
SESSIONS_FILE="$AGENT_DIR/sessions.json"
|
|
138
|
+
if [ "$CLAUDE_SESSION_ID" != "unknown" ]; then
|
|
139
|
+
if [ -f "$SESSIONS_FILE" ]; then
|
|
140
|
+
# Only append if not already in the list
|
|
141
|
+
if ! jq -e --arg sid "$CLAUDE_SESSION_ID" 'any(.[]; . == $sid)' "$SESSIONS_FILE" >/dev/null 2>&1; then
|
|
142
|
+
TEMP_SESSIONS="$SESSIONS_FILE.tmp"
|
|
143
|
+
jq --arg sid "$CLAUDE_SESSION_ID" '. + [$sid]' "$SESSIONS_FILE" > "$TEMP_SESSIONS" 2>/dev/null && \
|
|
144
|
+
mv "$TEMP_SESSIONS" "$SESSIONS_FILE" 2>/dev/null || true
|
|
145
|
+
fi
|
|
146
|
+
else
|
|
147
|
+
echo "[\"$CLAUDE_SESSION_ID\"]" > "$SESSIONS_FILE" 2>/dev/null || true
|
|
148
|
+
fi
|
|
149
|
+
fi
|
|
150
|
+
|
|
130
151
|
# Record cost event from tool usage (PAN-81)
|
|
131
152
|
# Find the record-cost-event.js script - check multiple locations
|
|
132
153
|
COST_SCRIPT=""
|
|
@@ -140,14 +161,14 @@ if [ -n "$COST_SCRIPT" ] && [ -f "$COST_SCRIPT" ]; then
|
|
|
140
161
|
# Infer issue ID from git branch if not set by Panopticon agent launcher
|
|
141
162
|
# Matches branches like: feature/pan-208, feature-min-665, pan-142
|
|
142
163
|
if [ -z "$PANOPTICON_ISSUE_ID" ]; then
|
|
143
|
-
if [[ "$GIT_BRANCH" =~ (pan|min|aud|PAN|MIN|AUD)[-]([0-9]+) ]]; then
|
|
164
|
+
if [[ "$GIT_BRANCH" =~ (pan|min|aud|krux|cli|PAN|MIN|AUD|KRUX|CLI)[-]([0-9]+) ]]; then
|
|
144
165
|
PANOPTICON_ISSUE_ID="${BASH_REMATCH[1]^^}-${BASH_REMATCH[2]}"
|
|
145
166
|
fi
|
|
146
167
|
fi
|
|
147
168
|
|
|
148
169
|
# Fallback: infer from workspace path (e.g., /workspaces/feature-pan-208/)
|
|
149
170
|
if [ -z "$PANOPTICON_ISSUE_ID" ]; then
|
|
150
|
-
if [[ "$WORKSPACE" =~ (pan|min|aud|PAN|MIN|AUD)[-]([0-9]+) ]]; then
|
|
171
|
+
if [[ "$WORKSPACE" =~ (pan|min|aud|krux|cli|PAN|MIN|AUD|KRUX|CLI)[-]([0-9]+) ]]; then
|
|
151
172
|
PANOPTICON_ISSUE_ID="${BASH_REMATCH[1]^^}-${BASH_REMATCH[2]}"
|
|
152
173
|
fi
|
|
153
174
|
fi
|
|
@@ -163,8 +184,7 @@ if [ -n "$COST_SCRIPT" ] && [ -f "$COST_SCRIPT" ]; then
|
|
|
163
184
|
# only one process reads/updates the offset at a time per session.
|
|
164
185
|
STATE_DIR="$HOME/.panopticon/costs/state"
|
|
165
186
|
mkdir -p "$STATE_DIR"
|
|
166
|
-
|
|
167
|
-
LOCK_FILE="$STATE_DIR/${SESSION_ID}.lock"
|
|
187
|
+
LOCK_FILE="$STATE_DIR/${CLAUDE_SESSION_ID}.lock"
|
|
168
188
|
|
|
169
189
|
{
|
|
170
190
|
flock -x -w 30 200
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
2
4
|
var __create = Object.create;
|
|
3
5
|
var __defProp = Object.defineProperty;
|
|
4
6
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -8232,7 +8234,7 @@ import { join as join3 } from "path";
|
|
|
8232
8234
|
import { existsSync, mkdirSync } from "fs";
|
|
8233
8235
|
|
|
8234
8236
|
// src/lib/database/schema.ts
|
|
8235
|
-
var SCHEMA_VERSION =
|
|
8237
|
+
var SCHEMA_VERSION = 3;
|
|
8236
8238
|
function initSchema(db) {
|
|
8237
8239
|
db.exec(`
|
|
8238
8240
|
-- ===== Cost Events =====
|
|
@@ -8250,6 +8252,7 @@ function initSchema(db) {
|
|
|
8250
8252
|
cache_write INTEGER NOT NULL DEFAULT 0,
|
|
8251
8253
|
cost REAL NOT NULL DEFAULT 0,
|
|
8252
8254
|
request_id TEXT,
|
|
8255
|
+
session_id TEXT, -- Claude Code session UUID (for reconciler offset tracking)
|
|
8253
8256
|
-- TLDR metrics
|
|
8254
8257
|
tldr_interceptions INTEGER,
|
|
8255
8258
|
tldr_bypasses INTEGER,
|
|
@@ -8271,6 +8274,9 @@ function initSchema(db) {
|
|
|
8271
8274
|
CREATE INDEX IF NOT EXISTS idx_cost_ts
|
|
8272
8275
|
ON cost_events(ts);
|
|
8273
8276
|
|
|
8277
|
+
CREATE INDEX IF NOT EXISTS idx_cost_session_id
|
|
8278
|
+
ON cost_events(session_id) WHERE session_id IS NOT NULL;
|
|
8279
|
+
|
|
8274
8280
|
-- ===== Review Status =====
|
|
8275
8281
|
CREATE TABLE IF NOT EXISTS review_status (
|
|
8276
8282
|
issue_id TEXT PRIMARY KEY,
|
|
@@ -8328,11 +8334,15 @@ function initSchema(db) {
|
|
|
8328
8334
|
CREATE INDEX IF NOT EXISTS idx_health_timestamp
|
|
8329
8335
|
ON health_events(timestamp);
|
|
8330
8336
|
|
|
8331
|
-
-- ===== Processed Sessions (for
|
|
8337
|
+
-- ===== Processed Sessions (for reconciler offset tracking) =====
|
|
8332
8338
|
CREATE TABLE IF NOT EXISTS processed_sessions (
|
|
8333
|
-
session_id
|
|
8334
|
-
|
|
8335
|
-
|
|
8339
|
+
session_id TEXT PRIMARY KEY,
|
|
8340
|
+
agent_id TEXT,
|
|
8341
|
+
issue_id TEXT,
|
|
8342
|
+
transcript_path TEXT, -- full path to the .jsonl file
|
|
8343
|
+
byte_offset INTEGER NOT NULL DEFAULT 0, -- bytes consumed so far
|
|
8344
|
+
processed_at TEXT NOT NULL,
|
|
8345
|
+
event_count INTEGER NOT NULL DEFAULT 0
|
|
8336
8346
|
);
|
|
8337
8347
|
|
|
8338
8348
|
-- ===== API Cache =====
|
|
@@ -8374,6 +8384,32 @@ function runMigrations(db) {
|
|
|
8374
8384
|
ON status_history(issue_id, type, status, timestamp);
|
|
8375
8385
|
`);
|
|
8376
8386
|
}
|
|
8387
|
+
if (currentVersion < 3) {
|
|
8388
|
+
try {
|
|
8389
|
+
db.exec(`ALTER TABLE cost_events ADD COLUMN session_id TEXT`);
|
|
8390
|
+
} catch {
|
|
8391
|
+
}
|
|
8392
|
+
db.exec(`
|
|
8393
|
+
CREATE INDEX IF NOT EXISTS idx_cost_session_id
|
|
8394
|
+
ON cost_events(session_id) WHERE session_id IS NOT NULL;
|
|
8395
|
+
`);
|
|
8396
|
+
try {
|
|
8397
|
+
db.exec(`ALTER TABLE processed_sessions ADD COLUMN agent_id TEXT`);
|
|
8398
|
+
} catch {
|
|
8399
|
+
}
|
|
8400
|
+
try {
|
|
8401
|
+
db.exec(`ALTER TABLE processed_sessions ADD COLUMN issue_id TEXT`);
|
|
8402
|
+
} catch {
|
|
8403
|
+
}
|
|
8404
|
+
try {
|
|
8405
|
+
db.exec(`ALTER TABLE processed_sessions ADD COLUMN transcript_path TEXT`);
|
|
8406
|
+
} catch {
|
|
8407
|
+
}
|
|
8408
|
+
try {
|
|
8409
|
+
db.exec(`ALTER TABLE processed_sessions ADD COLUMN byte_offset INTEGER NOT NULL DEFAULT 0`);
|
|
8410
|
+
} catch {
|
|
8411
|
+
}
|
|
8412
|
+
}
|
|
8377
8413
|
db.pragma(`user_version = ${SCHEMA_VERSION}`);
|
|
8378
8414
|
}
|
|
8379
8415
|
|
|
@@ -8407,9 +8443,10 @@ function insertCostEvent(event2, sourceFile) {
|
|
|
8407
8443
|
INSERT OR IGNORE INTO cost_events (
|
|
8408
8444
|
ts, agent_id, issue_id, session_type, provider, model,
|
|
8409
8445
|
input, output, cache_read, cache_write, cost, request_id,
|
|
8446
|
+
session_id,
|
|
8410
8447
|
tldr_interceptions, tldr_bypasses, tldr_tokens_saved, tldr_bypass_reasons,
|
|
8411
8448
|
source_file
|
|
8412
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
8449
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
8413
8450
|
`).run(
|
|
8414
8451
|
event2.ts,
|
|
8415
8452
|
event2.agentId,
|
|
@@ -8423,6 +8460,7 @@ function insertCostEvent(event2, sourceFile) {
|
|
|
8423
8460
|
event2.cacheWrite,
|
|
8424
8461
|
event2.cost,
|
|
8425
8462
|
event2.requestId ?? null,
|
|
8463
|
+
event2.sessionId ?? null,
|
|
8426
8464
|
event2.tldrInterceptions ?? null,
|
|
8427
8465
|
event2.tldrBypasses ?? null,
|
|
8428
8466
|
event2.tldrTokensSaved ?? null,
|
|
@@ -8678,7 +8716,7 @@ if (!issueId || issueId === "UNKNOWN") {
|
|
|
8678
8716
|
timeout: 2e3,
|
|
8679
8717
|
stdio: ["pipe", "pipe", "pipe"]
|
|
8680
8718
|
}).trim();
|
|
8681
|
-
const branchMatch = branch.match(/(pan|min|aud)[-](\d+)/i);
|
|
8719
|
+
const branchMatch = branch.match(/(pan|min|aud|krux|cli)[-](\d+)/i);
|
|
8682
8720
|
if (branchMatch) {
|
|
8683
8721
|
issueId = `${branchMatch[1].toUpperCase()}-${branchMatch[2]}`;
|
|
8684
8722
|
}
|
|
@@ -8762,6 +8800,7 @@ for (const line of lines) {
|
|
|
8762
8800
|
cacheWrite: cacheWriteTokens,
|
|
8763
8801
|
cost,
|
|
8764
8802
|
...requestId ? { requestId } : {},
|
|
8803
|
+
sessionId,
|
|
8765
8804
|
...tldrFields
|
|
8766
8805
|
});
|
|
8767
8806
|
} catch {
|
|
@@ -127,7 +127,7 @@ if (!issueId || issueId === 'UNKNOWN') {
|
|
|
127
127
|
timeout: 2000,
|
|
128
128
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
129
129
|
}).trim();
|
|
130
|
-
const branchMatch = branch.match(/(pan|min|aud)[-](\d+)/i);
|
|
130
|
+
const branchMatch = branch.match(/(pan|min|aud|krux|cli)[-](\d+)/i);
|
|
131
131
|
if (branchMatch) {
|
|
132
132
|
issueId = `${branchMatch[1].toUpperCase()}-${branchMatch[2]}`;
|
|
133
133
|
}
|
|
@@ -241,6 +241,7 @@ for (const line of lines) {
|
|
|
241
241
|
cacheWrite: cacheWriteTokens,
|
|
242
242
|
cost,
|
|
243
243
|
...(requestId ? { requestId } : {}),
|
|
244
|
+
sessionId,
|
|
244
245
|
...tldrFields,
|
|
245
246
|
});
|
|
246
247
|
} catch {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/remote/workspace-metadata.ts"],"sourcesContent":["/**\n * Workspace Metadata Management\n *\n * Shared module for loading, saving, and listing workspace metadata.\n * Used by both workspace.ts and work/issue.ts for remote workspace support.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, readdirSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { parse, stringify } from 'yaml';\nimport type { RemoteWorkspaceMetadata } from './interface.js';\n\n// Path for workspace metadata\nexport const WORKSPACES_DIR = join(homedir(), '.panopticon', 'workspaces');\n\n/**\n * Save workspace metadata to ~/.panopticon/workspaces/{issueId}.yaml\n */\nexport function saveWorkspaceMetadata(metadata: RemoteWorkspaceMetadata): void {\n if (!existsSync(WORKSPACES_DIR)) {\n mkdirSync(WORKSPACES_DIR, { recursive: true });\n }\n\n const filename = join(WORKSPACES_DIR, `${metadata.id}.yaml`);\n writeFileSync(filename, stringify(metadata), 'utf-8');\n}\n\n/**\n * Load workspace metadata from ~/.panopticon/workspaces/{issueId}.yaml\n */\nexport function loadWorkspaceMetadata(issueId: string): RemoteWorkspaceMetadata | null {\n const normalizedId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n const filename = join(WORKSPACES_DIR, `${normalizedId}.yaml`);\n\n if (!existsSync(filename)) {\n return null;\n }\n\n try {\n const content = readFileSync(filename, 'utf-8');\n return parse(content) as RemoteWorkspaceMetadata;\n } catch {\n return null;\n }\n}\n\n/**\n * List all workspace metadata files\n */\nexport function listWorkspaceMetadata(): RemoteWorkspaceMetadata[] {\n if (!existsSync(WORKSPACES_DIR)) {\n return [];\n }\n\n const files = readdirSync(WORKSPACES_DIR).filter(f => f.endsWith('.yaml'));\n const workspaces: RemoteWorkspaceMetadata[] = [];\n\n for (const file of files) {\n try {\n const content = readFileSync(join(WORKSPACES_DIR, file), 'utf-8');\n workspaces.push(parse(content) as RemoteWorkspaceMetadata);\n } catch {\n // Skip invalid files\n }\n }\n\n return workspaces;\n}\n\n/**\n * Check if a workspace exists (local or remote)\n * Returns metadata if remote workspace exists, null otherwise\n */\nexport function findRemoteWorkspaceMetadata(issueId: string): RemoteWorkspaceMetadata | null {\n return loadWorkspaceMetadata(issueId);\n}\n\n/**\n * Delete workspace metadata\n */\nexport function deleteWorkspaceMetadata(issueId: string): boolean {\n const normalizedId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n const filename = join(WORKSPACES_DIR, `${normalizedId}.yaml`);\n\n if (!existsSync(filename)) {\n return false;\n }\n\n try {\n const { unlinkSync } = require('fs');\n unlinkSync(filename);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;AAAA;AAOA,SAAS,YAAY,WAAW,eAAe,aAAa,oBAAoB;AAChF,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,OAAO,iBAAiB;AAI1B,IAAM,iBAAiB,KAAK,QAAQ,GAAG,eAAe,YAAY;AAKlE,SAAS,sBAAsB,UAAyC;AAC7E,MAAI,CAAC,WAAW,cAAc,GAAG;AAC/B,cAAU,gBAAgB,EAAE,WAAW,KAAK,CAAC;AAAA,EAC/C;AAEA,QAAM,WAAW,KAAK,gBAAgB,GAAG,SAAS,EAAE,OAAO;AAC3D,gBAAc,UAAU,UAAU,QAAQ,GAAG,OAAO;AACtD;AAKO,SAAS,sBAAsB,SAAiD;AACrF,QAAM,eAAe,QAAQ,YAAY,EAAE,QAAQ,eAAe,GAAG;AACrE,QAAM,WAAW,KAAK,gBAAgB,GAAG,YAAY,OAAO;AAE5D,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAU,aAAa,UAAU,OAAO;AAC9C,WAAO,MAAM,OAAO;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AA6BO,SAAS,4BAA4B,SAAiD;AAC3F,SAAO,sBAAsB,OAAO;AACtC;AAKO,SAAS,wBAAwB,SAA0B;AAChE,QAAM,eAAe,QAAQ,YAAY,EAAE,QAAQ,eAAe,GAAG;AACrE,QAAM,WAAW,KAAK,gBAAgB,GAAG,YAAY,OAAO;AAE5D,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,EAAE,WAAW,IAAI,UAAQ,IAAI;AACnC,eAAW,QAAQ;AACnB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
|