panopticon-cli 0.5.4 → 0.5.6

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-5HWTDR4S.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-6OYUJ4AJ.js +146 -0
  5. package/dist/chunk-6OYUJ4AJ.js.map +1 -0
  6. package/dist/{chunk-MOPGR3CL.js → chunk-AAP4G6U7.js} +1 -1
  7. package/dist/chunk-AAP4G6U7.js.map +1 -0
  8. package/dist/{chunk-4HST45MO.js → chunk-BYWVPPAZ.js} +19 -12
  9. package/dist/chunk-BYWVPPAZ.js.map +1 -0
  10. package/dist/{chunk-CFCUOV3Q.js → chunk-DMRTN432.js} +4 -1
  11. package/dist/chunk-DMRTN432.js.map +1 -0
  12. package/dist/{chunk-HOGYHJ2G.js → chunk-DW3PKGIS.js} +2 -2
  13. package/dist/{chunk-KY2E2Q3T.js → chunk-FUUP55PE.js} +104 -46
  14. package/dist/chunk-FUUP55PE.js.map +1 -0
  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-T7BBPDEJ.js → chunk-UKSGE6RH.js} +45 -15
  31. package/dist/chunk-UKSGE6RH.js.map +1 -0
  32. package/dist/chunk-W2OTF6OS.js +201 -0
  33. package/dist/chunk-W2OTF6OS.js.map +1 -0
  34. package/dist/chunk-WEQW3EAT.js +78 -0
  35. package/dist/chunk-WEQW3EAT.js.map +1 -0
  36. package/dist/{chunk-ID4OYXVH.js → chunk-WJJ3ZIQ6.js} +112 -45
  37. package/dist/chunk-WJJ3ZIQ6.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 +1301 -2142
  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-Db9NOz4z.js +756 -0
  53. package/dist/dashboard/public/index.html +3 -2
  54. package/dist/dashboard/server.js +34714 -34296
  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-WM7ZKUET.js +1725 -0
  62. package/dist/merge-agent-WM7ZKUET.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-74RQF5SR.js} +7 -5
  70. package/dist/{specialist-context-C66TEMXS.js.map → specialist-context-74RQF5SR.js.map} +1 -1
  71. package/dist/{specialist-logs-CJKXM3SR.js → specialist-logs-T5GW7CSU.js} +6 -4
  72. package/dist/{specialists-NXYD4Z62.js → specialists-HTYYFXHQ.js} +6 -4
  73. package/dist/specialists-HTYYFXHQ.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-5HWTDR4S.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-T5GW7CSU.js.map} +0 -0
@@ -0,0 +1,201 @@
1
+ import {
2
+ PANOPTICON_HOME,
3
+ init_paths
4
+ } from "./chunk-ZTFNYOC7.js";
5
+ import {
6
+ __esm,
7
+ init_esm_shims
8
+ } from "./chunk-ZHC57RCV.js";
9
+
10
+ // src/lib/tmux.ts
11
+ import { execSync, exec } from "child_process";
12
+ import { promisify } from "util";
13
+ import { writeFileSync, chmodSync, appendFileSync, mkdirSync, existsSync, unlinkSync } from "fs";
14
+ import { join } from "path";
15
+ function ensureLogDir() {
16
+ const logDir = join(PANOPTICON_HOME, "logs");
17
+ if (!existsSync(logDir)) {
18
+ mkdirSync(logDir, { recursive: true });
19
+ }
20
+ }
21
+ function logSendKeys(sessionName, keys, caller) {
22
+ try {
23
+ ensureLogDir();
24
+ const stack = new Error().stack || "";
25
+ const stackLines = stack.split("\n").slice(3, 6);
26
+ const callerInfo = caller || stackLines.map((l) => l.trim()).join(" <- ");
27
+ const entry = {
28
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
29
+ sessionName,
30
+ keysLength: keys.length,
31
+ keysPreview: keys.length > 200 ? keys.slice(0, 200) + "..." : keys,
32
+ caller: callerInfo,
33
+ pid: process.pid
34
+ };
35
+ appendFileSync(SENDKEYS_LOG_FILE, JSON.stringify(entry) + "\n", "utf-8");
36
+ } catch {
37
+ }
38
+ }
39
+ function listSessions() {
40
+ try {
41
+ const output = execSync('tmux list-sessions -F "#{session_name}|#{session_created}|#{session_attached}|#{session_windows}"', {
42
+ encoding: "utf8"
43
+ });
44
+ return output.trim().split("\n").filter(Boolean).map((line) => {
45
+ const [name, created, attached, windows] = line.split("|");
46
+ return {
47
+ name,
48
+ created: new Date(parseInt(created) * 1e3),
49
+ attached: attached === "1",
50
+ windows: parseInt(windows)
51
+ };
52
+ });
53
+ } catch {
54
+ return [];
55
+ }
56
+ }
57
+ function sessionExists(name) {
58
+ try {
59
+ execSync(`tmux has-session -t ${name} 2>/dev/null`);
60
+ return true;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+ function createSession(name, cwd, initialCommand, options) {
66
+ const escapedCwd = cwd.replace(/"/g, '\\"');
67
+ let envFlags = "";
68
+ if (options?.env) {
69
+ for (const [key, value] of Object.entries(options.env)) {
70
+ envFlags += ` -e ${key}="${value.replace(/"/g, '\\"')}"`;
71
+ }
72
+ }
73
+ if (initialCommand && (initialCommand.includes("`") || initialCommand.includes("\n") || initialCommand.length > 500)) {
74
+ execSync(`tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags}`);
75
+ execSync("sleep 0.5");
76
+ const tmpFile = `/tmp/pan-cmd-${name}.sh`;
77
+ writeFileSync(tmpFile, initialCommand);
78
+ chmodSync(tmpFile, "755");
79
+ execSync(`tmux send-keys -t ${name} "bash ${tmpFile}"`);
80
+ execSync(`tmux send-keys -t ${name} C-m`);
81
+ } else if (initialCommand) {
82
+ const cmd = `tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags} "${initialCommand.replace(/"/g, '\\"')}"`;
83
+ execSync(cmd);
84
+ } else {
85
+ execSync(`tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags}`);
86
+ }
87
+ }
88
+ function killSession(name) {
89
+ execSync(`tmux kill-session -t ${name}`);
90
+ }
91
+ async function sendKeysAsync(sessionName, keys, caller) {
92
+ logSendKeys(sessionName, keys, caller);
93
+ const bufferName = `pan-${process.pid}-${Date.now()}`;
94
+ const tmpFile = `/tmp/pan-sendkeys-${bufferName}.txt`;
95
+ try {
96
+ writeFileSync(tmpFile, keys);
97
+ await execAsync(`tmux load-buffer -b ${bufferName} ${tmpFile}`);
98
+ await execAsync(`tmux paste-buffer -b ${bufferName} -t ${sessionName} -d`);
99
+ await new Promise((r) => setTimeout(r, 300));
100
+ await execAsync(`tmux send-keys -t ${sessionName} C-m`);
101
+ } finally {
102
+ try {
103
+ unlinkSync(tmpFile);
104
+ } catch {
105
+ }
106
+ try {
107
+ await execAsync(`tmux delete-buffer -b ${bufferName} 2>/dev/null`);
108
+ } catch {
109
+ }
110
+ }
111
+ }
112
+ function sendKeys(sessionName, keys, caller) {
113
+ logSendKeys(sessionName, keys, caller);
114
+ const tmpFile = `/tmp/pan-sendkeys-${process.pid}-${Date.now()}.txt`;
115
+ try {
116
+ writeFileSync(tmpFile, keys);
117
+ execSync(`tmux load-buffer ${tmpFile}`);
118
+ execSync(`tmux paste-buffer -t ${sessionName}`);
119
+ execSync(`sleep 0.3`);
120
+ execSync(`tmux send-keys -t ${sessionName} C-m`);
121
+ } finally {
122
+ try {
123
+ unlinkSync(tmpFile);
124
+ } catch {
125
+ }
126
+ }
127
+ }
128
+ function capturePane(sessionName, lines = 50) {
129
+ try {
130
+ return execSync(`tmux capture-pane -t ${sessionName} -p -S -${lines}`, {
131
+ encoding: "utf8"
132
+ });
133
+ } catch {
134
+ return "";
135
+ }
136
+ }
137
+ async function capturePaneAsync(sessionName, lines = 50) {
138
+ try {
139
+ const { stdout } = await execAsync(`tmux capture-pane -t ${sessionName} -p -S -${lines}`, {
140
+ encoding: "utf-8"
141
+ });
142
+ return stdout;
143
+ } catch {
144
+ return "";
145
+ }
146
+ }
147
+ async function waitForClaudePrompt(sessionName, timeoutMs = 15e3) {
148
+ const start = Date.now();
149
+ const POLL = 500;
150
+ while (Date.now() - start < timeoutMs) {
151
+ const output = await capturePaneAsync(sessionName, 10);
152
+ const lines = output.split("\n").filter((l) => l.trim());
153
+ const lastLine = lines[lines.length - 1] || "";
154
+ if (lastLine.includes("\u276F")) return true;
155
+ await new Promise((r) => setTimeout(r, POLL));
156
+ }
157
+ return false;
158
+ }
159
+ async function confirmDelivery(sessionName, outputBefore, timeoutMs = 1e4) {
160
+ const start = Date.now();
161
+ const POLL = 1e3;
162
+ const beforeLineCount = outputBefore.split("\n").filter((l) => l.trim()).length;
163
+ while (Date.now() - start < timeoutMs) {
164
+ await new Promise((r) => setTimeout(r, POLL));
165
+ const after = await capturePaneAsync(sessionName, 50);
166
+ const afterLines = after.split("\n").filter((l) => l.trim());
167
+ const afterLineCount = afterLines.length;
168
+ if (afterLineCount > beforeLineCount + 1) return true;
169
+ const newOutput = afterLines.slice(beforeLineCount).join("\n");
170
+ if (newOutput.includes("\u25CF") || newOutput.includes("\u23BF") || newOutput.includes("Read") || newOutput.includes("\u273B") || newOutput.includes("\xB7") || newOutput.includes("\u2736") || newOutput.includes("\u273D") || newOutput.includes("\u2722") || newOutput.includes("Generating") || newOutput.includes("thinking") || newOutput.includes("thought for")) return true;
171
+ }
172
+ return false;
173
+ }
174
+ function getAgentSessions() {
175
+ return listSessions().filter((s) => s.name.startsWith("agent-"));
176
+ }
177
+ var SENDKEYS_LOG_FILE, execAsync;
178
+ var init_tmux = __esm({
179
+ "src/lib/tmux.ts"() {
180
+ init_esm_shims();
181
+ init_paths();
182
+ SENDKEYS_LOG_FILE = join(PANOPTICON_HOME, "logs", "sendkeys.jsonl");
183
+ execAsync = promisify(exec);
184
+ }
185
+ });
186
+
187
+ export {
188
+ listSessions,
189
+ sessionExists,
190
+ createSession,
191
+ killSession,
192
+ sendKeysAsync,
193
+ sendKeys,
194
+ capturePane,
195
+ capturePaneAsync,
196
+ waitForClaudePrompt,
197
+ confirmDelivery,
198
+ getAgentSessions,
199
+ init_tmux
200
+ };
201
+ //# sourceMappingURL=chunk-W2OTF6OS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/tmux.ts"],"sourcesContent":["import { execSync, exec } from 'child_process';\nimport { promisify } from 'util';\nimport { writeFileSync, chmodSync, appendFileSync, mkdirSync, existsSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { PANOPTICON_HOME } from './paths.js';\n\n/**\n * Log file for tmux sendKeys operations\n * This helps debug mysterious messages appearing in agent prompts\n */\nconst SENDKEYS_LOG_FILE = join(PANOPTICON_HOME, 'logs', 'sendkeys.jsonl');\n\n/**\n * Ensure log directory exists\n */\nfunction ensureLogDir(): void {\n const logDir = join(PANOPTICON_HOME, 'logs');\n if (!existsSync(logDir)) {\n mkdirSync(logDir, { recursive: true });\n }\n}\n\n/**\n * Log a sendKeys operation for debugging\n */\nfunction logSendKeys(sessionName: string, keys: string, caller?: string): void {\n try {\n ensureLogDir();\n\n // Get call stack to identify caller if not provided\n const stack = new Error().stack || '';\n const stackLines = stack.split('\\n').slice(3, 6); // Skip Error, logSendKeys, sendKeys\n const callerInfo = caller || stackLines.map(l => l.trim()).join(' <- ');\n\n const entry = {\n timestamp: new Date().toISOString(),\n sessionName,\n keysLength: keys.length,\n keysPreview: keys.length > 200 ? keys.slice(0, 200) + '...' : keys,\n caller: callerInfo,\n pid: process.pid,\n };\n\n appendFileSync(SENDKEYS_LOG_FILE, JSON.stringify(entry) + '\\n', 'utf-8');\n } catch {\n // Silently fail - logging should never break functionality\n }\n}\n\nexport interface TmuxSession {\n name: string;\n created: Date;\n attached: boolean;\n windows: number;\n}\n\nexport function listSessions(): TmuxSession[] {\n try {\n const output = execSync('tmux list-sessions -F \"#{session_name}|#{session_created}|#{session_attached}|#{session_windows}\"', {\n encoding: 'utf8',\n });\n\n return output.trim().split('\\n').filter(Boolean).map(line => {\n const [name, created, attached, windows] = line.split('|');\n return {\n name,\n created: new Date(parseInt(created) * 1000),\n attached: attached === '1',\n windows: parseInt(windows),\n };\n });\n } catch {\n return []; // No sessions\n }\n}\n\nexport function sessionExists(name: string): boolean {\n try {\n execSync(`tmux has-session -t ${name} 2>/dev/null`);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function createSession(\n name: string,\n cwd: string,\n initialCommand?: string,\n options?: { env?: Record<string, string> }\n): void {\n const escapedCwd = cwd.replace(/\"/g, '\\\\\"');\n\n // Build environment variable flags for tmux\n let envFlags = '';\n if (options?.env) {\n for (const [key, value] of Object.entries(options.env)) {\n envFlags += ` -e ${key}=\"${value.replace(/\"/g, '\\\\\"')}\"`;\n }\n }\n\n // For complex commands (with special chars), start session first then send command\n if (initialCommand && (initialCommand.includes('`') || initialCommand.includes('\\n') || initialCommand.length > 500)) {\n // Create session without command\n execSync(`tmux new-session -d -s ${name} -c \"${escapedCwd}\"${envFlags}`);\n\n // Small delay to let session initialize\n execSync('sleep 0.5');\n\n // Send the command in chunks if needed (tmux has buffer limits)\n // First, write to a temp file and source it\n const tmpFile = `/tmp/pan-cmd-${name}.sh`;\n writeFileSync(tmpFile, initialCommand);\n chmodSync(tmpFile, '755');\n\n // Execute the script\n execSync(`tmux send-keys -t ${name} \"bash ${tmpFile}\"`);\n execSync(`tmux send-keys -t ${name} C-m`);\n } else if (initialCommand) {\n // Simple command - use inline\n const cmd = `tmux new-session -d -s ${name} -c \"${escapedCwd}\"${envFlags} \"${initialCommand.replace(/\"/g, '\\\\\"')}\"`;\n execSync(cmd);\n } else {\n execSync(`tmux new-session -d -s ${name} -c \"${escapedCwd}\"${envFlags}`);\n }\n}\n\nexport function killSession(name: string): void {\n execSync(`tmux kill-session -t ${name}`);\n}\n\nconst execAsync = promisify(exec);\n\n/**\n * Send keys to a tmux session (async, non-blocking).\n * Uses load-buffer + paste-buffer for reliable delivery, with a delay before Enter.\n * MUST be used from the dashboard server and any async context.\n */\nexport async function sendKeysAsync(sessionName: string, keys: string, caller?: string): Promise<void> {\n logSendKeys(sessionName, keys, caller);\n\n // Use a unique named buffer per call to prevent race conditions.\n // The default (unnamed) paste buffer is global — concurrent load-buffer\n // calls from different specialist wakes clobber each other.\n const bufferName = `pan-${process.pid}-${Date.now()}`;\n const tmpFile = `/tmp/pan-sendkeys-${bufferName}.txt`;\n try {\n writeFileSync(tmpFile, keys);\n await execAsync(`tmux load-buffer -b ${bufferName} ${tmpFile}`);\n await execAsync(`tmux paste-buffer -b ${bufferName} -t ${sessionName} -d`);\n await new Promise(r => setTimeout(r, 300));\n await execAsync(`tmux send-keys -t ${sessionName} C-m`);\n } finally {\n try { unlinkSync(tmpFile); } catch {}\n try { await execAsync(`tmux delete-buffer -b ${bufferName} 2>/dev/null`); } catch {}\n }\n}\n\n/**\n * Send keys to a tmux session (sync, blocks event loop).\n * Only use from CLI commands — NEVER from the dashboard server.\n */\nexport function sendKeys(sessionName: string, keys: string, caller?: string): void {\n logSendKeys(sessionName, keys, caller);\n\n const tmpFile = `/tmp/pan-sendkeys-${process.pid}-${Date.now()}.txt`;\n try {\n writeFileSync(tmpFile, keys);\n execSync(`tmux load-buffer ${tmpFile}`);\n execSync(`tmux paste-buffer -t ${sessionName}`);\n execSync(`sleep 0.3`);\n execSync(`tmux send-keys -t ${sessionName} C-m`);\n } finally {\n try { unlinkSync(tmpFile); } catch {}\n }\n}\n\nexport function capturePane(sessionName: string, lines: number = 50): string {\n try {\n return execSync(`tmux capture-pane -t ${sessionName} -p -S -${lines}`, {\n encoding: 'utf8',\n });\n } catch {\n return '';\n }\n}\n\n/**\n * Capture tmux pane output (async, non-blocking).\n * MUST be used from the dashboard server and any async context.\n */\nexport async function capturePaneAsync(sessionName: string, lines: number = 50): Promise<string> {\n try {\n const { stdout } = await execAsync(`tmux capture-pane -t ${sessionName} -p -S -${lines}`, {\n encoding: 'utf-8',\n });\n return stdout;\n } catch {\n return '';\n }\n}\n\n/**\n * Wait for Claude Code to reach its interactive prompt (❯) in a tmux session.\n * Polls tmux output until the prompt appears or timeout is reached.\n *\n * @param sessionName - tmux session name\n * @param timeoutMs - maximum time to wait (default: 15s for fresh start, use 5s for already-running)\n * @returns true if prompt detected, false if timed out\n */\nexport async function waitForClaudePrompt(sessionName: string, timeoutMs: number = 15000): Promise<boolean> {\n const start = Date.now();\n const POLL = 500;\n while (Date.now() - start < timeoutMs) {\n const output = await capturePaneAsync(sessionName, 10);\n // Claude Code shows ❯ when ready for user input.\n // Check that the LAST non-empty line contains ❯ (not a stale prompt from earlier output).\n const lines = output.split('\\n').filter(l => l.trim());\n const lastLine = lines[lines.length - 1] || '';\n if (lastLine.includes('❯')) return true;\n await new Promise(r => setTimeout(r, POLL));\n }\n return false;\n}\n\n/**\n * Verify that a message sent to Claude was actually received and processing started.\n * Compares tmux output before and after to detect new activity (tool calls, responses).\n *\n * @param sessionName - tmux session name\n * @param outputBefore - tmux output snapshot taken BEFORE sending the message\n * @param timeoutMs - maximum time to wait for activity (default: 10s)\n * @returns true if new activity detected, false if timed out\n */\nexport async function confirmDelivery(\n sessionName: string,\n outputBefore: string,\n timeoutMs: number = 10000,\n): Promise<boolean> {\n const start = Date.now();\n const POLL = 1000;\n const beforeLineCount = outputBefore.split('\\n').filter(l => l.trim()).length;\n\n while (Date.now() - start < timeoutMs) {\n await new Promise(r => setTimeout(r, POLL));\n const after = await capturePaneAsync(sessionName, 50);\n const afterLines = after.split('\\n').filter(l => l.trim());\n const afterLineCount = afterLines.length;\n\n // Claude is processing if: new output lines appeared (tool calls: ●, results: ⎿, etc.)\n if (afterLineCount > beforeLineCount + 1) return true;\n\n // Or if we can see activity markers in the new output\n const newOutput = afterLines.slice(beforeLineCount).join('\\n');\n if (\n newOutput.includes('●') || newOutput.includes('⎿') || newOutput.includes('Read') ||\n newOutput.includes('✻') || newOutput.includes('·') || newOutput.includes('✶') ||\n newOutput.includes('✽') || newOutput.includes('✢') || newOutput.includes('Generating') ||\n newOutput.includes('thinking') || newOutput.includes('thought for')\n ) return true;\n }\n return false;\n}\n\nexport function getAgentSessions(): TmuxSession[] {\n return listSessions().filter(s => s.name.startsWith('agent-'));\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,78 @@
1
+ import {
2
+ stepFailed,
3
+ stepOk,
4
+ stepSkipped
5
+ } from "./chunk-R4KPLLRB.js";
6
+ import {
7
+ init_esm_shims
8
+ } from "./chunk-ZHC57RCV.js";
9
+
10
+ // src/lib/lifecycle/clean-planning.ts
11
+ init_esm_shims();
12
+ import { exec } from "child_process";
13
+ import { promisify } from "util";
14
+ var execAsync = promisify(exec);
15
+ var EPHEMERAL_PLANNING_FILES = [
16
+ ".planning/STATE.md",
17
+ ".planning/PRD.md",
18
+ ".planning/PLANNING_PROMPT.md",
19
+ ".planning/PLANNING_PROMPT.md.archived",
20
+ ".planning/.planning-complete"
21
+ ];
22
+ async function cleanPlanningArtifacts(ctx) {
23
+ const step = "clean-planning";
24
+ const { issueId, projectPath } = ctx;
25
+ try {
26
+ let trackedFiles = [];
27
+ for (const file of EPHEMERAL_PLANNING_FILES) {
28
+ try {
29
+ const { stdout } = await execAsync(
30
+ `git ls-files -- ${file}`,
31
+ { cwd: projectPath, encoding: "utf-8" }
32
+ );
33
+ if (stdout.trim()) {
34
+ trackedFiles.push(file);
35
+ }
36
+ } catch {
37
+ }
38
+ }
39
+ try {
40
+ const { stdout } = await execAsync(
41
+ `git ls-files -- .planning/feedback/`,
42
+ { cwd: projectPath, encoding: "utf-8" }
43
+ );
44
+ if (stdout.trim()) {
45
+ trackedFiles.push(".planning/feedback/");
46
+ }
47
+ } catch {
48
+ }
49
+ if (trackedFiles.length === 0) {
50
+ return stepSkipped(step, ["No tracked ephemeral planning files found on main"]);
51
+ }
52
+ const fileArgs = trackedFiles.map((f) => `"${f}"`).join(" ");
53
+ await execAsync(
54
+ `git rm -rf --ignore-unmatch ${fileArgs}`,
55
+ { cwd: projectPath, encoding: "utf-8" }
56
+ );
57
+ try {
58
+ await execAsync("git diff --cached --quiet", { cwd: projectPath, encoding: "utf-8" });
59
+ return stepSkipped(step, ["No staged deletions after git rm (already clean)"]);
60
+ } catch {
61
+ await execAsync(
62
+ `git commit -m "chore: remove ephemeral planning state after ${issueId} merge"`,
63
+ { cwd: projectPath, encoding: "utf-8" }
64
+ );
65
+ }
66
+ return stepOk(step, [
67
+ `Removed ${trackedFiles.length} ephemeral planning file(s) from main`,
68
+ `Files: ${trackedFiles.join(", ")}`
69
+ ]);
70
+ } catch (err) {
71
+ return stepFailed(step, `Failed to clean planning artifacts: ${err.message}`);
72
+ }
73
+ }
74
+
75
+ export {
76
+ cleanPlanningArtifacts
77
+ };
78
+ //# sourceMappingURL=chunk-WEQW3EAT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/lifecycle/clean-planning.ts"],"sourcesContent":["/**\n * clean-planning — Remove ephemeral .planning/ artifacts from main after merge.\n *\n * After a feature branch merges to main, ephemeral planning files\n * (STATE.md, PRD.md, PLANNING_PROMPT.md, .planning-complete, feedback/)\n * land on main and pollute new workspaces that inherit them.\n *\n * This module removes those files from the git index and working tree\n * with a dedicated commit, so new workspaces start clean.\n *\n * Idempotent — if none of the target files are tracked, returns skipped.\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport type { LifecycleContext, StepResult } from './types.js';\nimport { stepOk, stepSkipped, stepFailed } from './types.js';\n\nconst execAsync = promisify(exec);\n\n/** Ephemeral planning files to remove from main after merge */\nconst EPHEMERAL_PLANNING_FILES = [\n '.planning/STATE.md',\n '.planning/PRD.md',\n '.planning/PLANNING_PROMPT.md',\n '.planning/PLANNING_PROMPT.md.archived',\n '.planning/.planning-complete',\n];\n\n/**\n * Remove ephemeral planning artifacts from main after a feature branch merge.\n *\n * Uses `git rm` to remove tracked files from both the index and working tree,\n * then commits the deletion. Untracked files are silently skipped.\n */\nexport async function cleanPlanningArtifacts(\n ctx: LifecycleContext,\n): Promise<StepResult> {\n const step = 'clean-planning';\n const { issueId, projectPath } = ctx;\n\n try {\n // Build the list of files git is currently tracking in .planning/\n // that match our ephemeral set. We include feedback/ glob separately.\n let trackedFiles: string[] = [];\n\n // Check individual ephemeral files\n for (const file of EPHEMERAL_PLANNING_FILES) {\n try {\n const { stdout } = await execAsync(\n `git ls-files -- ${file}`,\n { cwd: projectPath, encoding: 'utf-8' },\n );\n if (stdout.trim()) {\n trackedFiles.push(file);\n }\n } catch {\n // git ls-files failure is non-fatal\n }\n }\n\n // Check feedback/ directory\n try {\n const { stdout } = await execAsync(\n `git ls-files -- .planning/feedback/`,\n { cwd: projectPath, encoding: 'utf-8' },\n );\n if (stdout.trim()) {\n trackedFiles.push('.planning/feedback/');\n }\n } catch {\n // Non-fatal\n }\n\n if (trackedFiles.length === 0) {\n return stepSkipped(step, ['No tracked ephemeral planning files found on main']);\n }\n\n // Remove tracked files from index and working tree\n const fileArgs = trackedFiles.map(f => `\"${f}\"`).join(' ');\n await execAsync(\n `git rm -rf --ignore-unmatch ${fileArgs}`,\n { cwd: projectPath, encoding: 'utf-8' },\n );\n\n // Check if anything was actually staged for deletion\n try {\n await execAsync('git diff --cached --quiet', { cwd: projectPath, encoding: 'utf-8' });\n // Nothing staged — files may have already been removed\n return stepSkipped(step, ['No staged deletions after git rm (already clean)']);\n } catch {\n // There are staged changes — commit them\n await execAsync(\n `git commit -m \"chore: remove ephemeral planning state after ${issueId} merge\"`,\n { cwd: projectPath, encoding: 'utf-8' },\n );\n }\n\n return stepOk(step, [\n `Removed ${trackedFiles.length} ephemeral planning file(s) from main`,\n `Files: ${trackedFiles.join(', ')}`,\n ]);\n } catch (err) {\n return stepFailed(step, `Failed to clean planning artifacts: ${(err as Error).message}`);\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AAaA,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAI1B,IAAM,YAAY,UAAU,IAAI;AAGhC,IAAM,2BAA2B;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQA,eAAsB,uBACpB,KACqB;AACrB,QAAM,OAAO;AACb,QAAM,EAAE,SAAS,YAAY,IAAI;AAEjC,MAAI;AAGF,QAAI,eAAyB,CAAC;AAG9B,eAAW,QAAQ,0BAA0B;AAC3C,UAAI;AACF,cAAM,EAAE,OAAO,IAAI,MAAM;AAAA,UACvB,mBAAmB,IAAI;AAAA,UACvB,EAAE,KAAK,aAAa,UAAU,QAAQ;AAAA,QACxC;AACA,YAAI,OAAO,KAAK,GAAG;AACjB,uBAAa,KAAK,IAAI;AAAA,QACxB;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,MAAM;AAAA,QACvB;AAAA,QACA,EAAE,KAAK,aAAa,UAAU,QAAQ;AAAA,MACxC;AACA,UAAI,OAAO,KAAK,GAAG;AACjB,qBAAa,KAAK,qBAAqB;AAAA,MACzC;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,QAAI,aAAa,WAAW,GAAG;AAC7B,aAAO,YAAY,MAAM,CAAC,mDAAmD,CAAC;AAAA,IAChF;AAGA,UAAM,WAAW,aAAa,IAAI,OAAK,IAAI,CAAC,GAAG,EAAE,KAAK,GAAG;AACzD,UAAM;AAAA,MACJ,+BAA+B,QAAQ;AAAA,MACvC,EAAE,KAAK,aAAa,UAAU,QAAQ;AAAA,IACxC;AAGA,QAAI;AACF,YAAM,UAAU,6BAA6B,EAAE,KAAK,aAAa,UAAU,QAAQ,CAAC;AAEpF,aAAO,YAAY,MAAM,CAAC,kDAAkD,CAAC;AAAA,IAC/E,QAAQ;AAEN,YAAM;AAAA,QACJ,+DAA+D,OAAO;AAAA,QACtE,EAAE,KAAK,aAAa,UAAU,QAAQ;AAAA,MACxC;AAAA,IACF;AAEA,WAAO,OAAO,MAAM;AAAA,MAClB,WAAW,aAAa,MAAM;AAAA,MAC9B,UAAU,aAAa,KAAK,IAAI,CAAC;AAAA,IACnC,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,WAAO,WAAW,MAAM,uCAAwC,IAAc,OAAO,EAAE;AAAA,EACzF;AACF;","names":[]}