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.
Files changed (119) hide show
  1. package/dist/{agents-HNMF52RM.js → agents-QXVDAW2M.js} +12 -9
  2. package/dist/archive-planning-U3AZAKWI.js +16 -0
  3. package/dist/{chunk-KBHRXV5T.js → chunk-43F4LDZ4.js} +3 -3
  4. package/dist/{chunk-KY2E2Q3T.js → chunk-4XR62WWV.js} +105 -46
  5. package/dist/chunk-4XR62WWV.js.map +1 -0
  6. package/dist/chunk-6OYUJ4AJ.js +146 -0
  7. package/dist/chunk-6OYUJ4AJ.js.map +1 -0
  8. package/dist/{chunk-MOPGR3CL.js → chunk-AAP4G6U7.js} +1 -1
  9. package/dist/chunk-AAP4G6U7.js.map +1 -0
  10. package/dist/{chunk-4HST45MO.js → chunk-BYWVPPAZ.js} +19 -12
  11. package/dist/chunk-BYWVPPAZ.js.map +1 -0
  12. package/dist/{chunk-CFCUOV3Q.js → chunk-DMRTN432.js} +4 -1
  13. package/dist/chunk-DMRTN432.js.map +1 -0
  14. package/dist/{chunk-HOGYHJ2G.js → chunk-DW3PKGIS.js} +2 -2
  15. package/dist/chunk-GUV2EPBG.js +692 -0
  16. package/dist/chunk-GUV2EPBG.js.map +1 -0
  17. package/dist/{chunk-44EOY2ZL.js → chunk-HHL3AWXA.js} +46 -2
  18. package/dist/chunk-HHL3AWXA.js.map +1 -0
  19. package/dist/{chunk-6N2KBSJA.js → chunk-IZIXJYXZ.js} +40 -6
  20. package/dist/chunk-IZIXJYXZ.js.map +1 -0
  21. package/dist/chunk-MJXYTGK5.js +64 -0
  22. package/dist/chunk-MJXYTGK5.js.map +1 -0
  23. package/dist/chunk-OJF4QS3S.js +269 -0
  24. package/dist/chunk-OJF4QS3S.js.map +1 -0
  25. package/dist/{chunk-FQ66DECN.js → chunk-QAJAJBFW.js} +1 -1
  26. package/dist/chunk-QAJAJBFW.js.map +1 -0
  27. package/dist/chunk-R4KPLLRB.js +36 -0
  28. package/dist/chunk-R4KPLLRB.js.map +1 -0
  29. package/dist/{chunk-DFNVHK3N.js → chunk-SUM2WVPF.js} +4 -4
  30. package/dist/{chunk-ID4OYXVH.js → chunk-TFPJD2I2.js} +112 -45
  31. package/dist/chunk-TFPJD2I2.js.map +1 -0
  32. package/dist/{chunk-T7BBPDEJ.js → chunk-UKSGE6RH.js} +45 -15
  33. package/dist/chunk-UKSGE6RH.js.map +1 -0
  34. package/dist/chunk-W2OTF6OS.js +201 -0
  35. package/dist/chunk-W2OTF6OS.js.map +1 -0
  36. package/dist/chunk-WEQW3EAT.js +78 -0
  37. package/dist/chunk-WEQW3EAT.js.map +1 -0
  38. package/dist/chunk-YAAT66RT.js +70 -0
  39. package/dist/chunk-YAAT66RT.js.map +1 -0
  40. package/dist/{chunk-RLZQB7HS.js → chunk-ZMJFEHGF.js} +13 -1
  41. package/dist/chunk-ZMJFEHGF.js.map +1 -0
  42. package/dist/{chunk-HRU7S4TA.js → chunk-ZN5RHWGR.js} +18 -208
  43. package/dist/{chunk-HRU7S4TA.js.map → chunk-ZN5RHWGR.js.map} +1 -1
  44. package/dist/{chunk-ZTYHZMEC.js → chunk-ZWZNEA26.js} +2 -2
  45. package/dist/clean-planning-7Z5YY64X.js +9 -0
  46. package/dist/cli/index.js +1338 -2226
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/close-issue-CTZK777I.js +9 -0
  49. package/dist/compact-beads-72SHALOL.js +9 -0
  50. package/dist/{config-4CJNUE3O.js → config-FFTMBVHM.js} +2 -2
  51. package/dist/dashboard/public/assets/{index-DSvt5pPn.css → index-Bx4NCn9A.css} +1 -1
  52. package/dist/dashboard/public/assets/index-C7hJ5-o1.js +756 -0
  53. package/dist/dashboard/public/index.html +3 -2
  54. package/dist/dashboard/server.js +34720 -34297
  55. package/dist/{feedback-writer-T43PI5S2.js → feedback-writer-T2WCT6EZ.js} +2 -2
  56. package/dist/{hume-CKJJ3OUU.js → hume-GVTB5BKW.js} +3 -3
  57. package/dist/index.d.ts +24 -16
  58. package/dist/index.js +4 -4
  59. package/dist/label-cleanup-4HJVX6NP.js +103 -0
  60. package/dist/label-cleanup-4HJVX6NP.js.map +1 -0
  61. package/dist/merge-agent-O3TSBTLC.js +1725 -0
  62. package/dist/merge-agent-O3TSBTLC.js.map +1 -0
  63. package/dist/{projects-KVM3MN3Y.js → projects-3CRF57ZU.js} +2 -2
  64. package/dist/{rally-RKFSWC7E.js → rally-LBY24P4C.js} +2 -2
  65. package/dist/{remote-agents-ULPD6C5U.js → remote-agents-3NZPSHYG.js} +2 -3
  66. package/dist/{remote-workspace-XX6ARE6I.js → remote-workspace-M4IULGFZ.js} +24 -49
  67. package/dist/remote-workspace-M4IULGFZ.js.map +1 -0
  68. package/dist/{review-status-XKUKZF6J.js → review-status-J2YJGL3E.js} +2 -2
  69. package/dist/{specialist-context-C66TEMXS.js → specialist-context-IKG6VMNH.js} +7 -5
  70. package/dist/{specialist-context-C66TEMXS.js.map → specialist-context-IKG6VMNH.js.map} +1 -1
  71. package/dist/{specialist-logs-CJKXM3SR.js → specialist-logs-GFKUXCFG.js} +6 -4
  72. package/dist/{specialists-NXYD4Z62.js → specialists-XMFCFGYQ.js} +6 -4
  73. package/dist/specialists-XMFCFGYQ.js.map +1 -0
  74. package/dist/tmux-X2I5SAIJ.js +31 -0
  75. package/dist/tmux-X2I5SAIJ.js.map +1 -0
  76. package/dist/{traefik-5GL3Q7DJ.js → traefik-QXLZ4PO2.js} +4 -4
  77. package/dist/traefik-QXLZ4PO2.js.map +1 -0
  78. package/dist/{tunnel-BKC7KLBX.js → tunnel-7IOSRZVH.js} +3 -3
  79. package/dist/tunnel-7IOSRZVH.js.map +1 -0
  80. package/dist/{workspace-manager-ALBR62AS.js → workspace-manager-G6TTBPC3.js} +6 -6
  81. package/dist/workspace-manager-G6TTBPC3.js.map +1 -0
  82. package/package.json +2 -2
  83. package/scripts/build-cost-script.mjs +17 -0
  84. package/scripts/heartbeat-hook +28 -8
  85. package/scripts/record-cost-event.js +46 -7
  86. package/scripts/record-cost-event.ts +2 -1
  87. package/dist/chunk-44EOY2ZL.js.map +0 -1
  88. package/dist/chunk-4HST45MO.js.map +0 -1
  89. package/dist/chunk-565HZ6VV.js +0 -159
  90. package/dist/chunk-565HZ6VV.js.map +0 -1
  91. package/dist/chunk-6N2KBSJA.js.map +0 -1
  92. package/dist/chunk-CFCUOV3Q.js.map +0 -1
  93. package/dist/chunk-FQ66DECN.js.map +0 -1
  94. package/dist/chunk-ID4OYXVH.js.map +0 -1
  95. package/dist/chunk-KY2E2Q3T.js.map +0 -1
  96. package/dist/chunk-MOPGR3CL.js.map +0 -1
  97. package/dist/chunk-RLZQB7HS.js.map +0 -1
  98. package/dist/chunk-T7BBPDEJ.js.map +0 -1
  99. package/dist/chunk-ZDNQFWR5.js +0 -650
  100. package/dist/chunk-ZDNQFWR5.js.map +0 -1
  101. package/dist/dashboard/public/assets/index-DA6pnizT.js +0 -767
  102. package/dist/remote-workspace-XX6ARE6I.js.map +0 -1
  103. /package/dist/{agents-HNMF52RM.js.map → agents-QXVDAW2M.js.map} +0 -0
  104. /package/dist/{config-4CJNUE3O.js.map → archive-planning-U3AZAKWI.js.map} +0 -0
  105. /package/dist/{chunk-KBHRXV5T.js.map → chunk-43F4LDZ4.js.map} +0 -0
  106. /package/dist/{chunk-HOGYHJ2G.js.map → chunk-DW3PKGIS.js.map} +0 -0
  107. /package/dist/{chunk-DFNVHK3N.js.map → chunk-SUM2WVPF.js.map} +0 -0
  108. /package/dist/{chunk-ZTYHZMEC.js.map → chunk-ZWZNEA26.js.map} +0 -0
  109. /package/dist/{hume-CKJJ3OUU.js.map → clean-planning-7Z5YY64X.js.map} +0 -0
  110. /package/dist/{projects-KVM3MN3Y.js.map → close-issue-CTZK777I.js.map} +0 -0
  111. /package/dist/{rally-RKFSWC7E.js.map → compact-beads-72SHALOL.js.map} +0 -0
  112. /package/dist/{remote-agents-ULPD6C5U.js.map → config-FFTMBVHM.js.map} +0 -0
  113. /package/dist/{feedback-writer-T43PI5S2.js.map → feedback-writer-T2WCT6EZ.js.map} +0 -0
  114. /package/dist/{review-status-XKUKZF6J.js.map → hume-GVTB5BKW.js.map} +0 -0
  115. /package/dist/{specialist-logs-CJKXM3SR.js.map → projects-3CRF57ZU.js.map} +0 -0
  116. /package/dist/{specialists-NXYD4Z62.js.map → rally-LBY24P4C.js.map} +0 -0
  117. /package/dist/{traefik-5GL3Q7DJ.js.map → remote-agents-3NZPSHYG.js.map} +0 -0
  118. /package/dist/{tunnel-BKC7KLBX.js.map → review-status-J2YJGL3E.js.map} +0 -0
  119. /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-ID4OYXVH.js";
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-HRU7S4TA.js";
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-RLZQB7HS.js";
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-C66TEMXS.js.map
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-ID4OYXVH.js";
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-RLZQB7HS.js";
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-CJKXM3SR.js.map
48
+ //# sourceMappingURL=specialist-logs-GFKUXCFG.js.map
@@ -55,11 +55,13 @@ import {
55
55
  wakeSpecialist,
56
56
  wakeSpecialistOrQueue,
57
57
  wakeSpecialistWithTask
58
- } from "./chunk-ID4OYXVH.js";
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-RLZQB7HS.js";
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-NXYD4Z62.js.map
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-KBHRXV5T.js";
8
- import "./chunk-FQ66DECN.js";
9
- import "./chunk-RLZQB7HS.js";
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-5GL3Q7DJ.js.map
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-ZTYHZMEC.js";
6
- import "./chunk-MOPGR3CL.js";
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-BKC7KLBX.js.map
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-4HST45MO.js";
8
- import "./chunk-ZTYHZMEC.js";
9
- import "./chunk-HOGYHJ2G.js";
10
- import "./chunk-MOPGR3CL.js";
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-ALBR62AS.js.map
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.4",
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": "esbuild scripts/record-cost-event.ts --bundle --platform=node --format=esm --outfile=scripts/record-cost-event.js",
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');
@@ -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
- SESSION_ID=$(echo "$TOOL_INFO" | jq -r '.session_id // "unknown"' 2>/dev/null || echo "unknown")
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 = 2;
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 cost migration dedup) =====
8337
+ -- ===== Processed Sessions (for reconciler offset tracking) =====
8332
8338
  CREATE TABLE IF NOT EXISTS processed_sessions (
8333
- session_id TEXT PRIMARY KEY,
8334
- processed_at TEXT NOT NULL,
8335
- event_count INTEGER NOT NULL DEFAULT 0
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":[]}