aicodeman 0.2.8

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 (246) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +403 -0
  3. package/dist/ai-checker-base.d.ts +175 -0
  4. package/dist/ai-checker-base.d.ts.map +1 -0
  5. package/dist/ai-checker-base.js +424 -0
  6. package/dist/ai-checker-base.js.map +1 -0
  7. package/dist/ai-idle-checker.d.ts +53 -0
  8. package/dist/ai-idle-checker.d.ts.map +1 -0
  9. package/dist/ai-idle-checker.js +141 -0
  10. package/dist/ai-idle-checker.js.map +1 -0
  11. package/dist/ai-plan-checker.d.ts +52 -0
  12. package/dist/ai-plan-checker.d.ts.map +1 -0
  13. package/dist/ai-plan-checker.js +103 -0
  14. package/dist/ai-plan-checker.js.map +1 -0
  15. package/dist/bash-tool-parser.d.ts +191 -0
  16. package/dist/bash-tool-parser.d.ts.map +1 -0
  17. package/dist/bash-tool-parser.js +598 -0
  18. package/dist/bash-tool-parser.js.map +1 -0
  19. package/dist/cli.d.ts +12 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +460 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/config/buffer-limits.d.ts +59 -0
  24. package/dist/config/buffer-limits.d.ts.map +1 -0
  25. package/dist/config/buffer-limits.js +74 -0
  26. package/dist/config/buffer-limits.js.map +1 -0
  27. package/dist/config/map-limits.d.ts +40 -0
  28. package/dist/config/map-limits.d.ts.map +1 -0
  29. package/dist/config/map-limits.js +52 -0
  30. package/dist/config/map-limits.js.map +1 -0
  31. package/dist/file-stream-manager.d.ts +148 -0
  32. package/dist/file-stream-manager.d.ts.map +1 -0
  33. package/dist/file-stream-manager.js +351 -0
  34. package/dist/file-stream-manager.js.map +1 -0
  35. package/dist/hooks-config.d.ts +31 -0
  36. package/dist/hooks-config.d.ts.map +1 -0
  37. package/dist/hooks-config.js +115 -0
  38. package/dist/hooks-config.js.map +1 -0
  39. package/dist/image-watcher.d.ts +86 -0
  40. package/dist/image-watcher.d.ts.map +1 -0
  41. package/dist/image-watcher.js +275 -0
  42. package/dist/image-watcher.js.map +1 -0
  43. package/dist/index.d.ts +11 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +54 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/mux-factory.d.ts +13 -0
  48. package/dist/mux-factory.d.ts.map +1 -0
  49. package/dist/mux-factory.js +19 -0
  50. package/dist/mux-factory.js.map +1 -0
  51. package/dist/mux-interface.d.ts +145 -0
  52. package/dist/mux-interface.d.ts.map +1 -0
  53. package/dist/mux-interface.js +9 -0
  54. package/dist/mux-interface.js.map +1 -0
  55. package/dist/plan-orchestrator.d.ts +123 -0
  56. package/dist/plan-orchestrator.d.ts.map +1 -0
  57. package/dist/plan-orchestrator.js +500 -0
  58. package/dist/plan-orchestrator.js.map +1 -0
  59. package/dist/prompts/index.d.ts +9 -0
  60. package/dist/prompts/index.d.ts.map +1 -0
  61. package/dist/prompts/index.js +9 -0
  62. package/dist/prompts/index.js.map +1 -0
  63. package/dist/prompts/planner.d.ts +14 -0
  64. package/dist/prompts/planner.d.ts.map +1 -0
  65. package/dist/prompts/planner.js +83 -0
  66. package/dist/prompts/planner.js.map +1 -0
  67. package/dist/prompts/research-agent.d.ts +10 -0
  68. package/dist/prompts/research-agent.d.ts.map +1 -0
  69. package/dist/prompts/research-agent.js +143 -0
  70. package/dist/prompts/research-agent.js.map +1 -0
  71. package/dist/push-store.d.ts +41 -0
  72. package/dist/push-store.d.ts.map +1 -0
  73. package/dist/push-store.js +168 -0
  74. package/dist/push-store.js.map +1 -0
  75. package/dist/ralph-config.d.ts +67 -0
  76. package/dist/ralph-config.d.ts.map +1 -0
  77. package/dist/ralph-config.js +134 -0
  78. package/dist/ralph-config.js.map +1 -0
  79. package/dist/ralph-loop.d.ts +124 -0
  80. package/dist/ralph-loop.d.ts.map +1 -0
  81. package/dist/ralph-loop.js +418 -0
  82. package/dist/ralph-loop.js.map +1 -0
  83. package/dist/ralph-tracker.d.ts +1081 -0
  84. package/dist/ralph-tracker.d.ts.map +1 -0
  85. package/dist/ralph-tracker.js +3343 -0
  86. package/dist/ralph-tracker.js.map +1 -0
  87. package/dist/respawn-controller.d.ts +1182 -0
  88. package/dist/respawn-controller.d.ts.map +1 -0
  89. package/dist/respawn-controller.js +2754 -0
  90. package/dist/respawn-controller.js.map +1 -0
  91. package/dist/run-summary.d.ts +123 -0
  92. package/dist/run-summary.d.ts.map +1 -0
  93. package/dist/run-summary.js +325 -0
  94. package/dist/run-summary.js.map +1 -0
  95. package/dist/session-lifecycle-log.d.ts +36 -0
  96. package/dist/session-lifecycle-log.d.ts.map +1 -0
  97. package/dist/session-lifecycle-log.js +101 -0
  98. package/dist/session-lifecycle-log.js.map +1 -0
  99. package/dist/session-manager.d.ts +97 -0
  100. package/dist/session-manager.d.ts.map +1 -0
  101. package/dist/session-manager.js +224 -0
  102. package/dist/session-manager.js.map +1 -0
  103. package/dist/session.d.ts +686 -0
  104. package/dist/session.d.ts.map +1 -0
  105. package/dist/session.js +2025 -0
  106. package/dist/session.js.map +1 -0
  107. package/dist/state-store.d.ts +189 -0
  108. package/dist/state-store.d.ts.map +1 -0
  109. package/dist/state-store.js +730 -0
  110. package/dist/state-store.js.map +1 -0
  111. package/dist/subagent-watcher.d.ts +345 -0
  112. package/dist/subagent-watcher.d.ts.map +1 -0
  113. package/dist/subagent-watcher.js +1469 -0
  114. package/dist/subagent-watcher.js.map +1 -0
  115. package/dist/task-queue.d.ts +108 -0
  116. package/dist/task-queue.d.ts.map +1 -0
  117. package/dist/task-queue.js +235 -0
  118. package/dist/task-queue.js.map +1 -0
  119. package/dist/task-tracker.d.ts +306 -0
  120. package/dist/task-tracker.d.ts.map +1 -0
  121. package/dist/task-tracker.js +488 -0
  122. package/dist/task-tracker.js.map +1 -0
  123. package/dist/task.d.ts +73 -0
  124. package/dist/task.d.ts.map +1 -0
  125. package/dist/task.js +177 -0
  126. package/dist/task.js.map +1 -0
  127. package/dist/team-watcher.d.ts +53 -0
  128. package/dist/team-watcher.d.ts.map +1 -0
  129. package/dist/team-watcher.js +313 -0
  130. package/dist/team-watcher.js.map +1 -0
  131. package/dist/templates/case-template.md +461 -0
  132. package/dist/templates/claude-md.d.ts +26 -0
  133. package/dist/templates/claude-md.d.ts.map +1 -0
  134. package/dist/templates/claude-md.js +74 -0
  135. package/dist/templates/claude-md.js.map +1 -0
  136. package/dist/tmux-manager.d.ts +181 -0
  137. package/dist/tmux-manager.d.ts.map +1 -0
  138. package/dist/tmux-manager.js +1405 -0
  139. package/dist/tmux-manager.js.map +1 -0
  140. package/dist/transcript-watcher.d.ts +110 -0
  141. package/dist/transcript-watcher.d.ts.map +1 -0
  142. package/dist/transcript-watcher.js +338 -0
  143. package/dist/transcript-watcher.js.map +1 -0
  144. package/dist/tunnel-manager.d.ts +54 -0
  145. package/dist/tunnel-manager.d.ts.map +1 -0
  146. package/dist/tunnel-manager.js +251 -0
  147. package/dist/tunnel-manager.js.map +1 -0
  148. package/dist/types.d.ts +1139 -0
  149. package/dist/types.d.ts.map +1 -0
  150. package/dist/types.js +215 -0
  151. package/dist/types.js.map +1 -0
  152. package/dist/utils/buffer-accumulator.d.ts +111 -0
  153. package/dist/utils/buffer-accumulator.d.ts.map +1 -0
  154. package/dist/utils/buffer-accumulator.js +172 -0
  155. package/dist/utils/buffer-accumulator.js.map +1 -0
  156. package/dist/utils/claude-cli-resolver.d.ts +26 -0
  157. package/dist/utils/claude-cli-resolver.d.ts.map +1 -0
  158. package/dist/utils/claude-cli-resolver.js +78 -0
  159. package/dist/utils/claude-cli-resolver.js.map +1 -0
  160. package/dist/utils/cleanup-manager.d.ts +165 -0
  161. package/dist/utils/cleanup-manager.d.ts.map +1 -0
  162. package/dist/utils/cleanup-manager.js +274 -0
  163. package/dist/utils/cleanup-manager.js.map +1 -0
  164. package/dist/utils/index.d.ts +19 -0
  165. package/dist/utils/index.d.ts.map +1 -0
  166. package/dist/utils/index.js +19 -0
  167. package/dist/utils/index.js.map +1 -0
  168. package/dist/utils/lru-map.d.ts +140 -0
  169. package/dist/utils/lru-map.d.ts.map +1 -0
  170. package/dist/utils/lru-map.js +234 -0
  171. package/dist/utils/lru-map.js.map +1 -0
  172. package/dist/utils/nice-wrapper.d.ts +13 -0
  173. package/dist/utils/nice-wrapper.d.ts.map +1 -0
  174. package/dist/utils/nice-wrapper.js +17 -0
  175. package/dist/utils/nice-wrapper.js.map +1 -0
  176. package/dist/utils/opencode-cli-resolver.d.ts +21 -0
  177. package/dist/utils/opencode-cli-resolver.d.ts.map +1 -0
  178. package/dist/utils/opencode-cli-resolver.js +67 -0
  179. package/dist/utils/opencode-cli-resolver.js.map +1 -0
  180. package/dist/utils/regex-patterns.d.ts +64 -0
  181. package/dist/utils/regex-patterns.d.ts.map +1 -0
  182. package/dist/utils/regex-patterns.js +74 -0
  183. package/dist/utils/regex-patterns.js.map +1 -0
  184. package/dist/utils/stale-expiration-map.d.ts +159 -0
  185. package/dist/utils/stale-expiration-map.d.ts.map +1 -0
  186. package/dist/utils/stale-expiration-map.js +277 -0
  187. package/dist/utils/stale-expiration-map.js.map +1 -0
  188. package/dist/utils/string-similarity.d.ts +108 -0
  189. package/dist/utils/string-similarity.d.ts.map +1 -0
  190. package/dist/utils/string-similarity.js +189 -0
  191. package/dist/utils/string-similarity.js.map +1 -0
  192. package/dist/utils/token-validation.d.ts +39 -0
  193. package/dist/utils/token-validation.d.ts.map +1 -0
  194. package/dist/utils/token-validation.js +59 -0
  195. package/dist/utils/token-validation.js.map +1 -0
  196. package/dist/utils/type-safety.d.ts +33 -0
  197. package/dist/utils/type-safety.d.ts.map +1 -0
  198. package/dist/utils/type-safety.js +35 -0
  199. package/dist/utils/type-safety.js.map +1 -0
  200. package/dist/web/public/app.js +491 -0
  201. package/dist/web/public/app.js.br +0 -0
  202. package/dist/web/public/app.js.gz +0 -0
  203. package/dist/web/public/index.html +1675 -0
  204. package/dist/web/public/index.html.br +0 -0
  205. package/dist/web/public/index.html.gz +0 -0
  206. package/dist/web/public/manifest.json +8 -0
  207. package/dist/web/public/mobile.css +1 -0
  208. package/dist/web/public/mobile.css.br +0 -0
  209. package/dist/web/public/mobile.css.gz +0 -0
  210. package/dist/web/public/ralph-wizard.js +1037 -0
  211. package/dist/web/public/ralph-wizard.js.br +0 -0
  212. package/dist/web/public/ralph-wizard.js.gz +0 -0
  213. package/dist/web/public/styles.css +1 -0
  214. package/dist/web/public/styles.css.br +0 -0
  215. package/dist/web/public/styles.css.gz +0 -0
  216. package/dist/web/public/sw.js +67 -0
  217. package/dist/web/public/sw.js.br +0 -0
  218. package/dist/web/public/sw.js.gz +0 -0
  219. package/dist/web/public/upload.html +155 -0
  220. package/dist/web/public/upload.html.br +0 -0
  221. package/dist/web/public/upload.html.gz +0 -0
  222. package/dist/web/public/vendor/xterm-addon-fit.min.js +1 -0
  223. package/dist/web/public/vendor/xterm-addon-fit.min.js.br +0 -0
  224. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  225. package/dist/web/public/vendor/xterm-addon-unicode11.min.js +1 -0
  226. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.br +0 -0
  227. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  228. package/dist/web/public/vendor/xterm-addon-webgl.min.js +2 -0
  229. package/dist/web/public/vendor/xterm-addon-webgl.min.js.br +0 -0
  230. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  231. package/dist/web/public/vendor/xterm.css +209 -0
  232. package/dist/web/public/vendor/xterm.css.br +0 -0
  233. package/dist/web/public/vendor/xterm.css.gz +0 -0
  234. package/dist/web/public/vendor/xterm.min.js +9 -0
  235. package/dist/web/public/vendor/xterm.min.js.br +0 -0
  236. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  237. package/dist/web/schemas.d.ts +479 -0
  238. package/dist/web/schemas.d.ts.map +1 -0
  239. package/dist/web/schemas.js +448 -0
  240. package/dist/web/schemas.js.map +1 -0
  241. package/dist/web/server.d.ts +207 -0
  242. package/dist/web/server.d.ts.map +1 -0
  243. package/dist/web/server.js +5784 -0
  244. package/dist/web/server.js.map +1 -0
  245. package/package.json +110 -0
  246. package/scripts/postinstall.js +390 -0
@@ -0,0 +1,1037 @@
1
+ /**
2
+ * Ralph Loop Wizard — extracted from app.js for maintainability.
3
+ * Extends CodemanApp.prototype with wizard methods.
4
+ * Loaded after app.js in index.html.
5
+ */
6
+
7
+ // ========== Ralph Loop Wizard ==========
8
+
9
+ Object.assign(CodemanApp.prototype, {
10
+
11
+ showRalphWizard() {
12
+ // Reset wizard state
13
+ this.ralphWizardStep = 1;
14
+ this.ralphWizardConfig = {
15
+ taskDescription: '',
16
+ completionPhrase: 'COMPLETE',
17
+ maxIterations: 10,
18
+ caseName: document.getElementById('quickStartCase')?.value || 'testcase',
19
+ enableRespawn: false,
20
+ generatedPlan: null,
21
+ planGenerated: false,
22
+ skipPlanGeneration: false,
23
+ planDetailLevel: 'detailed',
24
+ existingPlan: null,
25
+ useExistingPlan: false,
26
+ };
27
+
28
+ // Reset UI
29
+ document.getElementById('ralphTaskDescription').value = '';
30
+ document.getElementById('ralphCompletionPhrase').value = 'COMPLETE';
31
+ this.selectIterationPreset(10);
32
+ const autoStartStep2El = document.getElementById('ralphAutoStartStep2');
33
+ if (autoStartStep2El) autoStartStep2El.checked = false;
34
+
35
+ // Populate case selector
36
+ this.populateRalphCaseSelector();
37
+
38
+ // Reset plan generation UI
39
+ this.resetPlanGenerationUI();
40
+
41
+ // Check for existing @fix_plan.md in selected case
42
+ this.checkExistingFixPlan();
43
+
44
+ // Show wizard modal
45
+ this.updateRalphWizardUI();
46
+ const modal = document.getElementById('ralphWizardModal');
47
+ modal.classList.add('active');
48
+
49
+ // Activate focus trap
50
+ this.activeFocusTrap = new FocusTrap(modal);
51
+ this.activeFocusTrap.previouslyFocused = document.activeElement;
52
+ modal.addEventListener('keydown', this.activeFocusTrap.boundHandleKeydown);
53
+
54
+ document.getElementById('ralphTaskDescription').focus();
55
+ },
56
+
57
+ closeRalphWizard() {
58
+ const modal = document.getElementById('ralphWizardModal');
59
+ modal?.classList.remove('active');
60
+
61
+ // Deactivate focus trap and restore focus
62
+ if (this.activeFocusTrap) {
63
+ this.activeFocusTrap.deactivate();
64
+ this.activeFocusTrap = null;
65
+ }
66
+
67
+ // Cancel any in-flight plan generation
68
+ if (this.activePlanOrchestratorId) {
69
+ fetch('/api/cancel-plan-generation', {
70
+ method: 'POST',
71
+ headers: { 'Content-Type': 'application/json' },
72
+ body: JSON.stringify({ orchestratorId: this.activePlanOrchestratorId }),
73
+ }).catch(err => console.error('[Wizard Close] Failed to cancel plan:', err));
74
+ this.activePlanOrchestratorId = null;
75
+ }
76
+
77
+ // Abort any in-flight fetch request
78
+ if (this.planGenerationAbortController) {
79
+ this.planGenerationAbortController.abort();
80
+ this.planGenerationAbortController = null;
81
+ }
82
+
83
+ this._planProgressHandler = null;
84
+
85
+ // Clear plan loading timers
86
+ if (this.planLoadingTimer) {
87
+ clearInterval(this.planLoadingTimer);
88
+ this.planLoadingTimer = null;
89
+ }
90
+ if (this.planPhaseTimer) {
91
+ clearTimeout(this.planPhaseTimer);
92
+ this.planPhaseTimer = null;
93
+ }
94
+
95
+ this.planGenerationStopped = true;
96
+
97
+ // Close all plan subagent windows
98
+ this.closePlanSubagentWindows();
99
+
100
+ // Update monitor panel
101
+ this.renderMonitorPlanAgents();
102
+ this.updateConnectionLines();
103
+ },
104
+
105
+ populateRalphCaseSelector() {
106
+ const select = document.getElementById('ralphCaseSelect');
107
+ const quickStartSelect = document.getElementById('quickStartCase');
108
+
109
+ if (quickStartSelect && select) {
110
+ select.innerHTML = quickStartSelect.innerHTML;
111
+ select.value = this.ralphWizardConfig.caseName;
112
+ }
113
+ },
114
+
115
+ selectIterationPreset(iterations) {
116
+ this.ralphWizardConfig.maxIterations = iterations;
117
+
118
+ // Update button states
119
+ document.querySelectorAll('.iteration-preset-btn').forEach(btn => {
120
+ const btnIterations = parseInt(btn.dataset.iterations);
121
+ btn.classList.toggle('active', btnIterations === iterations);
122
+ });
123
+ },
124
+
125
+ // Check for existing @fix_plan.md in the selected case
126
+ async checkExistingFixPlan() {
127
+ const caseName = this.ralphWizardConfig.caseName;
128
+ if (!caseName) return;
129
+
130
+ try {
131
+ const res = await fetch(`/api/cases/${encodeURIComponent(caseName)}/fix-plan`);
132
+ const data = await res.json();
133
+
134
+ if (data.success && data.exists && data.todos?.length > 0) {
135
+ this.ralphWizardConfig.existingPlan = {
136
+ todos: data.todos,
137
+ stats: data.stats,
138
+ content: data.content,
139
+ };
140
+ this.updateExistingPlanUI();
141
+ } else {
142
+ this.ralphWizardConfig.existingPlan = null;
143
+ this.updateExistingPlanUI();
144
+ }
145
+ } catch (err) {
146
+ console.error('Failed to check for existing plan:', err);
147
+ this.ralphWizardConfig.existingPlan = null;
148
+ }
149
+ },
150
+
151
+ // Called when case selector changes
152
+ onRalphCaseChange() {
153
+ const caseName = document.getElementById('ralphCaseSelect')?.value;
154
+ if (caseName) {
155
+ this.ralphWizardConfig.caseName = caseName;
156
+ this.ralphWizardConfig.existingPlan = null;
157
+ this.ralphWizardConfig.useExistingPlan = false;
158
+ this.checkExistingFixPlan();
159
+ }
160
+ },
161
+
162
+ // Update UI to show existing plan indicator
163
+ updateExistingPlanUI() {
164
+ const existingPlanBadge = document.getElementById('existingPlanBadge');
165
+ const existingPlanSection = document.getElementById('existingPlanSection');
166
+ const plan = this.ralphWizardConfig.existingPlan;
167
+
168
+ if (existingPlanBadge) {
169
+ if (plan) {
170
+ const pending = plan.stats?.pending || 0;
171
+ const total = plan.stats?.total || 0;
172
+ existingPlanBadge.textContent = `${pending}/${total} tasks remaining`;
173
+ existingPlanBadge.style.display = '';
174
+ } else {
175
+ existingPlanBadge.style.display = 'none';
176
+ }
177
+ }
178
+
179
+ if (existingPlanSection) {
180
+ if (plan) {
181
+ const pending = plan.stats?.pending || 0;
182
+ const completed = plan.stats?.completed || 0;
183
+ const total = plan.stats?.total || 0;
184
+ existingPlanSection.innerHTML = `
185
+ <div class="existing-plan-card">
186
+ <div class="existing-plan-header">
187
+ <span class="existing-plan-icon">📋</span>
188
+ <span>Existing @fix_plan.md found</span>
189
+ </div>
190
+ <div class="existing-plan-stats">
191
+ <span class="stat pending">${pending} pending</span>
192
+ <span class="stat completed">${completed} completed</span>
193
+ <span class="stat total">${total} total</span>
194
+ </div>
195
+ <div class="existing-plan-actions">
196
+ <button class="btn-toolbar btn-primary btn-sm" onclick="app.useExistingPlan()">
197
+ Use Existing Plan
198
+ </button>
199
+ <button class="btn-toolbar btn-sm" onclick="app.generateNewPlan()">
200
+ Generate New
201
+ </button>
202
+ </div>
203
+ </div>
204
+ `;
205
+ existingPlanSection.classList.remove('hidden');
206
+ } else {
207
+ existingPlanSection.classList.add('hidden');
208
+ }
209
+ }
210
+ },
211
+
212
+ // Use the existing @fix_plan.md
213
+ useExistingPlan() {
214
+ const plan = this.ralphWizardConfig.existingPlan;
215
+ if (!plan) return;
216
+
217
+ // Stop any ongoing plan generation
218
+ this.stopPlanGeneration();
219
+
220
+ // Convert existing todos to generatedPlan format (only pending items)
221
+ const pendingTodos = plan.todos.filter(t => t.status === 'pending' || t.status === 'in_progress');
222
+ this.ralphWizardConfig.generatedPlan = pendingTodos.map((todo, idx) => ({
223
+ content: todo.content,
224
+ priority: todo.priority,
225
+ enabled: true,
226
+ id: `existing-${Date.now()}-${idx}`,
227
+ }));
228
+ this.ralphWizardConfig.planGenerated = true;
229
+ this.ralphWizardConfig.useExistingPlan = true;
230
+ this.ralphWizardConfig.planCost = 0; // No cost for existing plan
231
+
232
+ this.renderPlanChecklist();
233
+ this.updateDetailLevelButtons();
234
+ },
235
+
236
+ // Stop any ongoing plan generation (abort fetch, clear timers, hide spinner)
237
+ stopPlanGeneration() {
238
+ // Abort ongoing fetch
239
+ if (this.planGenerationAbortController) {
240
+ this.planGenerationAbortController.abort();
241
+ this.planGenerationAbortController = null;
242
+ }
243
+
244
+ // Clear timers
245
+ if (this.planLoadingTimer) {
246
+ clearInterval(this.planLoadingTimer);
247
+ this.planLoadingTimer = null;
248
+ }
249
+ if (this.planPhaseTimer) {
250
+ clearInterval(this.planPhaseTimer);
251
+ this.planPhaseTimer = null;
252
+ }
253
+
254
+ // Hide loading spinner
255
+ document.getElementById('planGenerationLoading')?.classList.add('hidden');
256
+ },
257
+
258
+ // Generate a new plan (ignore existing)
259
+ generateNewPlan() {
260
+ this.ralphWizardConfig.useExistingPlan = false;
261
+ document.getElementById('existingPlanSection')?.classList.add('hidden');
262
+ this.generatePlan();
263
+ },
264
+
265
+ ralphWizardNext() {
266
+ if (this.ralphWizardStep === 1) {
267
+ // Validate step 1
268
+ const taskDescription = document.getElementById('ralphTaskDescription').value.trim();
269
+ const completionPhrase = document.getElementById('ralphCompletionPhrase').value.trim() || 'COMPLETE';
270
+ const caseName = document.getElementById('ralphCaseSelect').value;
271
+
272
+ if (!taskDescription) {
273
+ this.showToast('Please enter a task description', 'error');
274
+ document.getElementById('ralphTaskDescription').focus();
275
+ return;
276
+ }
277
+
278
+ // Save config
279
+ this.ralphWizardConfig.taskDescription = taskDescription;
280
+ this.ralphWizardConfig.completionPhrase = completionPhrase.toUpperCase();
281
+ this.ralphWizardConfig.caseName = caseName;
282
+
283
+ // Move to step 2 (plan generation)
284
+ this.ralphWizardStep = 2;
285
+ this.updateRalphWizardUI();
286
+
287
+ // If there's an existing plan, show option to use it; otherwise auto-start generation
288
+ if (this.ralphWizardConfig.existingPlan) {
289
+ this.updateExistingPlanUI();
290
+ } else {
291
+ this.generatePlan();
292
+ }
293
+ } else if (this.ralphWizardStep === 2) {
294
+ // Must have generated or skipped plan
295
+ if (!this.ralphWizardConfig.planGenerated && !this.ralphWizardConfig.skipPlanGeneration) {
296
+ this.showToast('Wait for plan generation or skip', 'warning');
297
+ return;
298
+ }
299
+
300
+ // Generate preview
301
+ this.updateRalphPromptPreview();
302
+
303
+ // Move to step 3 (launch)
304
+ this.ralphWizardStep = 3;
305
+ this.updateRalphWizardUI();
306
+ }
307
+ },
308
+
309
+ ralphWizardBack() {
310
+ if (this.ralphWizardStep === 3) {
311
+ this.ralphWizardStep = 2;
312
+ this.updateRalphWizardUI();
313
+ } else if (this.ralphWizardStep === 2) {
314
+ this.ralphWizardStep = 1;
315
+ this.updateRalphWizardUI();
316
+ }
317
+ },
318
+
319
+ updateRalphWizardUI() {
320
+ const step = this.ralphWizardStep;
321
+
322
+ // Update progress indicators
323
+ document.querySelectorAll('.wizard-step').forEach(el => {
324
+ const stepNum = parseInt(el.dataset.step);
325
+ el.classList.toggle('active', stepNum === step);
326
+ el.classList.toggle('completed', stepNum < step);
327
+ });
328
+
329
+ // Show/hide pages (now 3 pages)
330
+ document.getElementById('ralphWizardStep1').classList.toggle('hidden', step !== 1);
331
+ document.getElementById('ralphWizardStep2').classList.toggle('hidden', step !== 2);
332
+ document.getElementById('ralphWizardStep3').classList.toggle('hidden', step !== 3);
333
+
334
+ // Show/hide buttons
335
+ document.getElementById('ralphBackBtn').style.display = step === 1 ? 'none' : 'block';
336
+ document.getElementById('ralphNextBtn').style.display = step === 3 ? 'none' : 'block';
337
+ document.getElementById('ralphStartBtn').style.display = step === 3 ? 'block' : 'none';
338
+ },
339
+
340
+ updateRalphPromptPreview() {
341
+ const config = this.ralphWizardConfig;
342
+ const preview = document.getElementById('ralphPromptPreview');
343
+ const hasPlan = config.generatedPlan && config.generatedPlan.filter(i => i.enabled).length > 0;
344
+
345
+ // Build the formatted prompt (abbreviated for preview)
346
+ let prompt = config.taskDescription;
347
+ prompt += '\n\n---\n\n';
348
+
349
+ if (hasPlan) {
350
+ prompt += '## Task Plan\n';
351
+ prompt += '📋 @fix_plan.md will be created with your task items\n\n';
352
+ }
353
+
354
+ prompt += '## Iteration Protocol\n';
355
+ prompt += '• Check previous work • Make progress • Commit changes\n\n';
356
+
357
+ prompt += '## Completion Criteria\n';
358
+ prompt += `Output \`<promise>${config.completionPhrase}</promise>\` when done\n\n`;
359
+
360
+ prompt += '## If Stuck\n';
361
+ prompt += 'Output `<promise>BLOCKED</promise>` with explanation';
362
+
363
+ // Show preview with highlighting (escape first, then apply formatting)
364
+ const escapedPrompt = this.escapeHtml(prompt);
365
+ const highlightedPrompt = escapedPrompt
366
+ .replace(/&lt;promise&gt;/g, '<span class="preview-highlight">&lt;promise&gt;')
367
+ .replace(/&lt;\/promise&gt;/g, '&lt;/promise&gt;</span>')
368
+ .replace(/`([^`]+)`/g, '<code>$1</code>');
369
+
370
+ preview.innerHTML = highlightedPrompt;
371
+
372
+ // Update summary
373
+ document.getElementById('summaryPhrase').textContent = config.completionPhrase;
374
+ document.getElementById('summaryIterations').textContent =
375
+ config.maxIterations === 0 ? 'Unlimited' : config.maxIterations;
376
+ document.getElementById('summaryCase').textContent = config.caseName;
377
+
378
+ // Show plan status in summary if plan was generated
379
+ const planSummary = document.getElementById('summaryPlan');
380
+ if (planSummary) {
381
+ if (config.generatedPlan && config.generatedPlan.length > 0) {
382
+ const enabledCount = config.generatedPlan.filter(i => i.enabled).length;
383
+ planSummary.textContent = `${enabledCount} item${enabledCount !== 1 ? 's' : ''}`;
384
+ planSummary.parentElement.style.display = '';
385
+ } else {
386
+ planSummary.parentElement.style.display = 'none';
387
+ }
388
+ }
389
+ },
390
+
391
+ // ========== Plan Generation ==========
392
+
393
+ resetPlanGenerationUI() {
394
+ // Hide all plan generation states
395
+ document.getElementById('existingPlanSection')?.classList.add('hidden');
396
+ document.getElementById('planGenerationLoading')?.classList.add('hidden');
397
+ document.getElementById('planGenerationError')?.classList.add('hidden');
398
+ document.getElementById('planEditor')?.classList.add('hidden');
399
+
400
+ // Reset spinner visibility (in case it was hidden after "Done!")
401
+ const spinnerEl = document.querySelector('.plan-spinner');
402
+ if (spinnerEl) spinnerEl.style.display = '';
403
+
404
+ // Reset stopped indicator
405
+ const stoppedIndicator = document.getElementById('planStoppedIndicator');
406
+ if (stoppedIndicator) stoppedIndicator.style.display = 'none';
407
+
408
+ // Reset existing plan badge
409
+ const badge = document.getElementById('existingPlanBadge');
410
+ if (badge) badge.style.display = 'none';
411
+ },
412
+
413
+ async generatePlan() {
414
+ const config = this.ralphWizardConfig;
415
+ const isDetailed = config.planDetailLevel === 'detailed';
416
+
417
+ // Stop any existing generation first
418
+ this.stopPlanGeneration();
419
+
420
+ // Close old plan subagent windows and clear their state
421
+ this.closePlanSubagentWindows();
422
+
423
+ // Reset stopped flag to allow new SSE events
424
+ this.planGenerationStopped = false;
425
+
426
+ // Create abort controller for this generation
427
+ this.planGenerationAbortController = new AbortController();
428
+
429
+ // Show loading state, hide other sections
430
+ document.getElementById('existingPlanSection')?.classList.add('hidden');
431
+ document.getElementById('planGenerationError')?.classList.add('hidden');
432
+ document.getElementById('planEditor')?.classList.add('hidden');
433
+ document.getElementById('planGenerationLoading')?.classList.remove('hidden');
434
+
435
+ // Different phases for detailed vs standard generation
436
+ const standardPhases = [
437
+ { time: 0, title: 'Starting Opus 4.5...', hint: 'Initializing deep reasoning model' },
438
+ { time: 3, title: 'Analyzing task requirements...', hint: 'Understanding the scope and complexity' },
439
+ { time: 8, title: 'Identifying components...', hint: 'Breaking down into modules and features' },
440
+ { time: 15, title: 'Planning TDD approach...', hint: 'Designing test-first implementation strategy' },
441
+ { time: 25, title: 'Generating implementation steps...', hint: 'Creating detailed action items with tests' },
442
+ { time: 40, title: 'Adding verification checkpoints...', hint: 'Ensuring each phase has validation' },
443
+ { time: 55, title: 'Reviewing for completeness...', hint: 'Checking all requirements are covered' },
444
+ { time: 70, title: 'Finalizing plan...', hint: 'Organizing and prioritizing steps' },
445
+ { time: 90, title: 'Still working...', hint: 'Complex tasks take longer - hang tight!' },
446
+ ];
447
+
448
+ const detailedPhases = [
449
+ { time: 0, title: 'Starting research agent...', hint: 'Gathering external resources and codebase context' },
450
+ { time: 30, title: 'Research agent working...', hint: 'Searching docs, GitHub repos, and analyzing codebase' },
451
+ { time: 60, title: 'Research continuing...', hint: 'Web search and codebase exploration in progress' },
452
+ { time: 120, title: 'Research agent deep diving...', hint: 'Complex tasks require thorough research' },
453
+ { time: 180, title: 'Research almost complete...', hint: 'Compiling findings and recommendations' },
454
+ { time: 300, title: 'Spawning analysis subagents...', hint: 'Starting 4 specialist agents in parallel' },
455
+ { time: 330, title: 'Subagents analyzing...', hint: 'Requirements, Architecture, Testing, Risk analysts working' },
456
+ { time: 400, title: 'Subagents completing...', hint: 'Collecting analysis results' },
457
+ { time: 450, title: 'Synthesizing results...', hint: 'Merging and deduplicating items' },
458
+ { time: 500, title: 'Running verification...', hint: 'Quality assurance and priority assignment' },
459
+ { time: 550, title: 'Optimizing execution...', hint: 'Planning parallelization for Claude Code' },
460
+ { time: 600, title: 'Final review...', hint: 'Holistic validation and gap detection' },
461
+ { time: 660, title: 'Still working...', hint: 'Complex tasks take longer - hang tight!' },
462
+ ];
463
+
464
+ const phases = isDetailed ? detailedPhases : standardPhases;
465
+
466
+ // Start elapsed time and phase display
467
+ this.planLoadingStartTime = Date.now();
468
+ const timeEl = document.getElementById('planLoadingTime');
469
+ const titleEl = document.getElementById('planLoadingTitle');
470
+ const hintEl = document.getElementById('planLoadingHint');
471
+
472
+ if (timeEl) timeEl.textContent = '0s';
473
+ if (titleEl) titleEl.textContent = phases[0].title;
474
+ if (hintEl) hintEl.textContent = phases[0].hint;
475
+
476
+ let currentPhaseIndex = 0;
477
+ this.planLoadingTimer = setInterval(() => {
478
+ const elapsed = Math.floor((Date.now() - this.planLoadingStartTime) / 1000);
479
+ if (timeEl) timeEl.textContent = `${elapsed}s`;
480
+
481
+ // Update phase based on elapsed time
482
+ for (let i = phases.length - 1; i >= 0; i--) {
483
+ if (elapsed >= phases[i].time && i > currentPhaseIndex) {
484
+ currentPhaseIndex = i;
485
+ if (titleEl) titleEl.textContent = phases[i].title;
486
+ if (hintEl) hintEl.textContent = phases[i].hint;
487
+ break;
488
+ }
489
+ }
490
+ }, 1000);
491
+
492
+ // Listen for real-time progress updates from detailed generation
493
+ const handlePlanProgress = (event) => {
494
+ if (event.type === 'plan:progress' && event.data) {
495
+ const titleEl = document.getElementById('planLoadingTitle');
496
+ const hintEl = document.getElementById('planLoadingHint');
497
+ if (titleEl && event.data.phase) {
498
+ const phaseLabels = {
499
+ 'research': 'Research agent working...',
500
+ 'parallel-analysis': 'Spawning analysis subagents...',
501
+ 'subagent': event.data.detail || 'Subagent working...',
502
+ 'synthesis': 'Synthesizing results...',
503
+ 'verification': 'Running verification...',
504
+ 'review-injection': 'Adding review tasks...',
505
+ 'execution-optimization': 'Optimizing for Claude Code...',
506
+ 'final-review': 'Running final review...',
507
+ };
508
+ titleEl.textContent = phaseLabels[event.data.phase] || event.data.phase;
509
+ }
510
+ if (hintEl && event.data.detail) {
511
+ hintEl.textContent = event.data.detail;
512
+ }
513
+ }
514
+ };
515
+
516
+ // Add SSE listener for detailed mode progress
517
+ if (isDetailed) {
518
+ this._planProgressHandler = handlePlanProgress;
519
+ }
520
+
521
+ try {
522
+ // Use different endpoint for detailed mode
523
+ const endpoint = isDetailed ? '/api/generate-plan-detailed' : '/api/generate-plan';
524
+ const body = isDetailed
525
+ ? { taskDescription: config.taskDescription, caseName: config.caseName }
526
+ : { taskDescription: config.taskDescription, detailLevel: config.planDetailLevel };
527
+
528
+ // Retry logic for network errors
529
+ const maxRetries = 3;
530
+ let lastError = null;
531
+ let data = null;
532
+
533
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
534
+ try {
535
+ const res = await fetch(endpoint, {
536
+ method: 'POST',
537
+ headers: { 'Content-Type': 'application/json' },
538
+ body: JSON.stringify(body),
539
+ signal: this.planGenerationAbortController?.signal,
540
+ });
541
+ data = await res.json();
542
+ break; // Success, exit retry loop
543
+ } catch (fetchErr) {
544
+ lastError = fetchErr;
545
+ // Don't retry if aborted
546
+ if (fetchErr.name === 'AbortError') throw fetchErr;
547
+ // Network error - retry with exponential backoff
548
+ console.warn(`[Plan] Fetch attempt ${attempt + 1} failed:`, fetchErr.message);
549
+ if (attempt < maxRetries - 1) {
550
+ const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
551
+ const titleEl = document.getElementById('planLoadingTitle');
552
+ const hintEl = document.getElementById('planLoadingHint');
553
+ if (titleEl) titleEl.textContent = 'Connection lost, retrying...';
554
+ if (hintEl) hintEl.textContent = `Attempt ${attempt + 2} of ${maxRetries} in ${delay / 1000}s`;
555
+ await new Promise(r => setTimeout(r, delay));
556
+ }
557
+ }
558
+ }
559
+
560
+ if (!data) {
561
+ throw lastError || new Error('Failed to fetch after retries');
562
+ }
563
+
564
+ // Stop timer
565
+ if (this.planLoadingTimer) {
566
+ clearInterval(this.planLoadingTimer);
567
+ this.planLoadingTimer = null;
568
+ }
569
+
570
+ // Remove progress handler
571
+ this._planProgressHandler = null;
572
+
573
+ if (!data.success) {
574
+ this.showPlanError(data.error || 'Failed to generate plan');
575
+ return;
576
+ }
577
+
578
+ if (!data.data?.items || data.data.items.length === 0) {
579
+ this.showPlanError('No plan items generated. Try adding more detail to your task.');
580
+ return;
581
+ }
582
+
583
+ // Show "Done!" with quality info for detailed mode
584
+ const doneTitle = document.getElementById('planLoadingTitle');
585
+ const doneHint = document.getElementById('planLoadingHint');
586
+ const spinnerEl = document.querySelector('.plan-spinner');
587
+
588
+ if (doneTitle) doneTitle.textContent = 'Done!';
589
+ if (doneHint) {
590
+ if (isDetailed && data.data.metadata?.qualityScore) {
591
+ const quality = Math.round(data.data.metadata.qualityScore * 100);
592
+ doneHint.textContent = `Generated ${data.data.items.length} steps (Quality: ${quality}%)`;
593
+ } else {
594
+ doneHint.textContent = `Generated ${data.data.items.length} steps`;
595
+ }
596
+ }
597
+ if (spinnerEl) spinnerEl.style.display = 'none';
598
+
599
+ // Brief pause to show "Done!" before showing editor
600
+ await new Promise(r => setTimeout(r, 500));
601
+
602
+ // Store plan with enabled state and IDs
603
+ config.generatedPlan = data.data.items.map((item, idx) => ({
604
+ ...item,
605
+ enabled: true,
606
+ id: `plan-${Date.now()}-${idx}`,
607
+ }));
608
+ config.planGenerated = true;
609
+ config.skipPlanGeneration = false;
610
+ config.planCost = data.data.costUsd || 0;
611
+
612
+ // Store metadata for detailed mode
613
+ if (isDetailed && data.data.metadata) {
614
+ config.planMetadata = data.data.metadata;
615
+ }
616
+
617
+ // Show editor and update detail buttons
618
+ this.renderPlanChecklist();
619
+ this.updateDetailLevelButtons();
620
+
621
+ // Check for auto-start
622
+ if (this.ralphWizardConfig.autoStart) {
623
+ console.log('[RalphWizard] Auto-start enabled, starting loop automatically...');
624
+ this.showToast('Plan complete! Auto-starting Ralph Loop...', 'success');
625
+ // Small delay to let user see the plan briefly
626
+ await new Promise(r => setTimeout(r, 1500));
627
+ this.startRalphLoop();
628
+ }
629
+
630
+ } catch (err) {
631
+ // Stop timer
632
+ if (this.planLoadingTimer) {
633
+ clearInterval(this.planLoadingTimer);
634
+ this.planLoadingTimer = null;
635
+ }
636
+
637
+ // Remove progress handler
638
+ this._planProgressHandler = null;
639
+
640
+ // Ignore abort errors (user cancelled, e.g., clicked "Use Existing Plan")
641
+ if (err.name === 'AbortError') {
642
+ console.log('Plan generation aborted by user');
643
+ return;
644
+ }
645
+
646
+ console.error('Plan generation failed:', err);
647
+ this.showPlanError('Network error: ' + err.message);
648
+ }
649
+ },
650
+
651
+ setPlanDetail(level) {
652
+ const previousLevel = this.ralphWizardConfig.planDetailLevel;
653
+ this.ralphWizardConfig.planDetailLevel = level;
654
+ this.updateDetailLevelButtons();
655
+
656
+ // If plan was already generated and level changed, automatically regenerate
657
+ if (this.ralphWizardConfig.planGenerated && previousLevel !== level) {
658
+ const modeLabel = level === 'detailed' ? 'Enhanced (Multi-Agent)' : 'Standard';
659
+ console.log(`[Ralph Wizard] Plan mode changed to ${modeLabel}, regenerating...`);
660
+
661
+ // Clear current plan and regenerate
662
+ this.ralphWizardConfig.generatedPlan = null;
663
+ this.ralphWizardConfig.planGenerated = false;
664
+ this.ralphWizardConfig.planMetadata = null;
665
+
666
+ // Trigger regeneration with visual feedback
667
+ this.generatePlan();
668
+ }
669
+ },
670
+
671
+ updateDetailLevelButtons() {
672
+ const level = this.ralphWizardConfig.planDetailLevel;
673
+ document.querySelectorAll('.plan-detail-btn').forEach(btn => {
674
+ btn.classList.toggle('active', btn.dataset.detail === level);
675
+ });
676
+ },
677
+
678
+ showPlanError(message) {
679
+ document.getElementById('planGenerationLoading')?.classList.add('hidden');
680
+ document.getElementById('planEditor')?.classList.add('hidden');
681
+
682
+ const errorEl = document.getElementById('planGenerationError');
683
+ const msgEl = document.getElementById('planErrorMsg');
684
+ const stoppedIndicator = document.getElementById('planStoppedIndicator');
685
+
686
+ // Hide the stopped indicator for real errors, show error message
687
+ if (stoppedIndicator) stoppedIndicator.style.display = 'none';
688
+ if (msgEl) msgEl.textContent = message;
689
+ errorEl?.classList.remove('hidden');
690
+ },
691
+
692
+ renderPlanChecklist() {
693
+ document.getElementById('planGenerationLoading')?.classList.add('hidden');
694
+ document.getElementById('planGenerationError')?.classList.add('hidden');
695
+ document.getElementById('planEditor')?.classList.remove('hidden');
696
+
697
+ // Close plan subagent windows since generation is complete
698
+ this.closePlanSubagentWindows();
699
+
700
+ const list = document.getElementById('planItemsList');
701
+ if (!list) return;
702
+
703
+ list.innerHTML = '';
704
+ const items = this.ralphWizardConfig.generatedPlan || [];
705
+ const cost = this.ralphWizardConfig.planCost || 0;
706
+
707
+ // Update stats
708
+ const statsEl = document.getElementById('planStats');
709
+ if (statsEl) {
710
+ const enabledCount = items.filter(i => i.enabled).length;
711
+ statsEl.textContent = `${enabledCount}/${items.length} steps · $${cost.toFixed(3)}`;
712
+ }
713
+
714
+ // Render read-only checklist with DocumentFragment
715
+ const fragment = document.createDocumentFragment();
716
+ items.forEach((item, index) => {
717
+ const row = document.createElement('div');
718
+ let className = 'plan-item';
719
+ if (item.priority) className += ` priority-${item.priority.toLowerCase()}`;
720
+ if (!item.enabled) className += ' disabled';
721
+ row.className = className;
722
+
723
+ row.innerHTML = `
724
+ <input type="checkbox" class="plan-item-checkbox" ${item.enabled ? 'checked' : ''}
725
+ onchange="app.togglePlanItem(${index})">
726
+ ${item.priority ? `<span class="plan-item-priority-badge">${item.priority}</span>` : ''}
727
+ <span class="plan-item-text">${this.escapeHtml(item.content)}</span>
728
+ `;
729
+ fragment.appendChild(row);
730
+ });
731
+ list.appendChild(fragment);
732
+ },
733
+
734
+ togglePlanItem(index) {
735
+ const plan = this.ralphWizardConfig.generatedPlan;
736
+ if (plan && plan[index]) {
737
+ plan[index].enabled = !plan[index].enabled;
738
+ this.renderPlanChecklist();
739
+ }
740
+ },
741
+
742
+ async cancelPlanGeneration() {
743
+ this.stopPlanGeneration();
744
+ this.planGenerationStopped = true; // Ignore future SSE events
745
+
746
+ // Call the cancel API to stop server-side processing
747
+ if (this.activePlanOrchestratorId) {
748
+ try {
749
+ console.log('[Cancel] Sending cancel request for', this.activePlanOrchestratorId);
750
+ await fetch('/api/cancel-plan-generation', {
751
+ method: 'POST',
752
+ headers: { 'Content-Type': 'application/json' },
753
+ body: JSON.stringify({ orchestratorId: this.activePlanOrchestratorId }),
754
+ });
755
+ this.activePlanOrchestratorId = null;
756
+ } catch (err) {
757
+ console.error('[Cancel] Failed to cancel plan generation:', err);
758
+ }
759
+ }
760
+
761
+ this.showToast('Plan generation stopped', 'info');
762
+
763
+ // Allow user to proceed without a plan by clicking Next
764
+ this.ralphWizardConfig.skipPlanGeneration = true;
765
+
766
+ // Show stopped state with clear visual indicator
767
+ const errorEl = document.getElementById('planGenerationError');
768
+ const msgEl = document.getElementById('planErrorMsg');
769
+ const stoppedIndicator = document.getElementById('planStoppedIndicator');
770
+
771
+ // Show the stopped indicator, hide error message since this was intentional
772
+ if (stoppedIndicator) stoppedIndicator.style.display = 'flex';
773
+ if (msgEl) msgEl.textContent = '';
774
+
775
+ // Hide spinner immediately and show stopped state
776
+ document.getElementById('planGenerationLoading')?.classList.add('hidden');
777
+ errorEl?.classList.remove('hidden');
778
+
779
+ // Close all plan subagent windows
780
+ this.closePlanSubagentWindows();
781
+ },
782
+
783
+ // ========== Plan Subagent Windows ==========
784
+
785
+ handlePlanSubagentEvent(event) {
786
+ if (this.planGenerationStopped) return;
787
+
788
+ const { type, agentId, agentType, model, status, detail, itemCount, durationMs, error } = event;
789
+
790
+ if (type === 'started') {
791
+ this.createPlanSubagentWindow(agentId, agentType, model, detail);
792
+ } else if (type === 'completed' || type === 'failed') {
793
+ this.updatePlanSubagentWindow(agentId, status, itemCount, durationMs, error);
794
+ } else if (type === 'progress') {
795
+ const windowData = this.planSubagents.get(agentId);
796
+ if (windowData?.element) {
797
+ const detailEl = windowData.element.querySelector('.plan-subagent-detail');
798
+ if (detailEl) detailEl.textContent = detail || '';
799
+ }
800
+ }
801
+
802
+ this.renderMonitorPlanAgents();
803
+ },
804
+
805
+ /**
806
+ * Position a plan subagent window to the left or right of the wizard.
807
+ * Research goes left, planner goes right.
808
+ */
809
+ _positionPlanSubagentWindow(agentType) {
810
+ const wizardModal = document.getElementById('ralphWizardModal');
811
+ const wizardContent = wizardModal?.querySelector('.modal-content');
812
+ const wizardRect = wizardContent?.getBoundingClientRect();
813
+ const gap = 20;
814
+ const windowWidth = 280;
815
+
816
+ if (!wizardRect) {
817
+ const offset = this.planSubagents.size * 30;
818
+ return { x: window.innerWidth - windowWidth - 50 + offset, y: 120 + offset };
819
+ }
820
+
821
+ const side = agentType === 'research' ? 'left' : 'right';
822
+ let x = side === 'left'
823
+ ? wizardRect.left - windowWidth - gap
824
+ : wizardRect.right + gap;
825
+ x = Math.max(10, Math.min(x, window.innerWidth - windowWidth - 10));
826
+ const y = Math.max(60, Math.min(wizardRect.top, window.innerHeight - 120));
827
+
828
+ return { x, y };
829
+ },
830
+
831
+ createPlanSubagentWindow(agentId, agentType, model, detail) {
832
+ if (this.planSubagents.has(agentId)) return;
833
+
834
+ const { x, y } = this._positionPlanSubagentWindow(agentType);
835
+
836
+ const win = document.createElement('div');
837
+ win.className = 'plan-subagent-window';
838
+ win.id = `plan-subagent-${agentId}`;
839
+ win.style.left = `${x}px`;
840
+ win.style.top = `${y}px`;
841
+ win.style.zIndex = ++this.planSubagentWindowZIndex;
842
+
843
+ const typeLabels = { research: 'Research Agent', planner: 'Planner' };
844
+ const typeIcons = { research: '🔬', planner: '📋' };
845
+
846
+ win.innerHTML = `
847
+ <div class="plan-subagent-header">
848
+ <span>
849
+ <span class="plan-subagent-icon">${typeIcons[agentType] || '🤖'}</span>
850
+ <span class="plan-subagent-title">${typeLabels[agentType] || this.escapeHtml(agentType)}</span>
851
+ </span>
852
+ <span class="plan-subagent-model">${model}</span>
853
+ </div>
854
+ <div class="plan-subagent-body">
855
+ <div class="plan-subagent-status running">
856
+ <span class="plan-subagent-spinner"></span>
857
+ <span class="plan-subagent-status-text">Running...</span>
858
+ </div>
859
+ <div class="plan-subagent-detail">${detail || ''}</div>
860
+ </div>
861
+ `;
862
+
863
+ document.body.appendChild(win);
864
+
865
+ const header = win.querySelector('.plan-subagent-header');
866
+ const dragListeners = this.makePlanSubagentDraggable(win, header);
867
+
868
+ this.planSubagents.set(agentId, {
869
+ agentId,
870
+ type: agentType,
871
+ model,
872
+ status: 'running',
873
+ startTime: Date.now(),
874
+ element: win,
875
+ dragListeners,
876
+ });
877
+ },
878
+
879
+ makePlanSubagentDraggable(win, handle) {
880
+ let isDragging = false;
881
+ let startX, startY, startLeft, startTop;
882
+
883
+ const mousedownHandler = (e) => {
884
+ isDragging = true;
885
+ startX = e.clientX;
886
+ startY = e.clientY;
887
+ startLeft = parseInt(win.style.left) || 0;
888
+ startTop = parseInt(win.style.top) || 0;
889
+ win.style.zIndex = ++this.planSubagentWindowZIndex;
890
+ e.preventDefault();
891
+ };
892
+
893
+ const moveHandler = (e) => {
894
+ if (!isDragging) return;
895
+ const newLeft = Math.max(10, Math.min(startLeft + (e.clientX - startX), window.innerWidth - 290));
896
+ const newTop = Math.max(10, Math.min(startTop + (e.clientY - startY), window.innerHeight - 110));
897
+ win.style.left = `${newLeft}px`;
898
+ win.style.top = `${newTop}px`;
899
+ };
900
+
901
+ const upHandler = () => { isDragging = false; };
902
+
903
+ handle.addEventListener('mousedown', mousedownHandler);
904
+ document.addEventListener('mousemove', moveHandler);
905
+ document.addEventListener('mouseup', upHandler);
906
+
907
+ return { mousedown: mousedownHandler, move: moveHandler, up: upHandler };
908
+ },
909
+
910
+ updatePlanSubagentWindow(agentId, status, itemCount, durationMs, error) {
911
+ const windowData = this.planSubagents.get(agentId);
912
+ if (!windowData?.element) return;
913
+
914
+ const win = windowData.element;
915
+ const statusEl = win.querySelector('.plan-subagent-status');
916
+ const statusTextEl = win.querySelector('.plan-subagent-status-text');
917
+ const spinnerEl = win.querySelector('.plan-subagent-spinner');
918
+ const detailEl = win.querySelector('.plan-subagent-detail');
919
+
920
+ windowData.status = status;
921
+ windowData.itemCount = itemCount;
922
+
923
+ if (status === 'completed') {
924
+ statusEl?.classList.remove('running');
925
+ statusEl?.classList.add('completed');
926
+ if (spinnerEl) spinnerEl.style.display = 'none';
927
+ if (statusTextEl) statusTextEl.textContent = `Done (${itemCount || 0} items)`;
928
+ if (detailEl && durationMs) detailEl.textContent = `${(durationMs / 1000).toFixed(1)}s`;
929
+ } else if (status === 'failed' || status === 'cancelled') {
930
+ statusEl?.classList.remove('running');
931
+ statusEl?.classList.add('failed');
932
+ if (spinnerEl) spinnerEl.style.display = 'none';
933
+ if (statusTextEl) statusTextEl.textContent = status === 'cancelled' ? 'Cancelled' : 'Failed';
934
+ if (detailEl) detailEl.textContent = error || '';
935
+ }
936
+ },
937
+
938
+ closePlanSubagentWindows() {
939
+ for (const [, windowData] of this.planSubagents) {
940
+ if (windowData.dragListeners) {
941
+ document.removeEventListener('mousemove', windowData.dragListeners.move);
942
+ document.removeEventListener('mouseup', windowData.dragListeners.up);
943
+ }
944
+ if (windowData.element) {
945
+ windowData.element.remove();
946
+ }
947
+ }
948
+ this.planSubagents.clear();
949
+ this.renderMonitorPlanAgents();
950
+ },
951
+
952
+ regeneratePlan() {
953
+ this.ralphWizardConfig.generatedPlan = null;
954
+ this.ralphWizardConfig.planGenerated = false;
955
+ this.generatePlan();
956
+ },
957
+
958
+ generateFixPlanContent(items) {
959
+ // Group items by priority
960
+ const p0Items = items.filter(i => i.priority === 'P0');
961
+ const p1Items = items.filter(i => i.priority === 'P1');
962
+ const p2Items = items.filter(i => i.priority === 'P2');
963
+ const noPriorityItems = items.filter(i => !i.priority);
964
+
965
+ let content = '# Implementation Plan\n\n';
966
+ content += `Generated: ${new Date().toISOString().slice(0, 10)}\n\n`;
967
+
968
+ if (p0Items.length > 0) {
969
+ content += '## Critical Path (P0)\n\n';
970
+ p0Items.forEach(item => {
971
+ content += `- [ ] ${item.content}\n`;
972
+ });
973
+ content += '\n';
974
+ }
975
+
976
+ if (p1Items.length > 0) {
977
+ content += '## Standard (P1)\n\n';
978
+ p1Items.forEach(item => {
979
+ content += `- [ ] ${item.content}\n`;
980
+ });
981
+ content += '\n';
982
+ }
983
+
984
+ if (p2Items.length > 0) {
985
+ content += '## Nice-to-Have (P2)\n\n';
986
+ p2Items.forEach(item => {
987
+ content += `- [ ] ${item.content}\n`;
988
+ });
989
+ content += '\n';
990
+ }
991
+
992
+ if (noPriorityItems.length > 0) {
993
+ content += '## Tasks\n\n';
994
+ noPriorityItems.forEach(item => {
995
+ content += `- [ ] ${item.content}\n`;
996
+ });
997
+ content += '\n';
998
+ }
999
+
1000
+ return content;
1001
+ },
1002
+
1003
+ async startRalphLoop() {
1004
+ const config = this.ralphWizardConfig;
1005
+ config.enableRespawn = document.getElementById('ralphEnableRespawn')?.checked ?? false;
1006
+ this.closeRalphWizard();
1007
+
1008
+ const enabledItems = config.generatedPlan?.filter(i => i.enabled);
1009
+
1010
+ try {
1011
+ const res = await fetch('/api/ralph-loop/start', {
1012
+ method: 'POST',
1013
+ headers: { 'Content-Type': 'application/json' },
1014
+ body: JSON.stringify({
1015
+ caseName: config.caseName,
1016
+ taskDescription: config.taskDescription,
1017
+ completionPhrase: config.completionPhrase,
1018
+ maxIterations: config.maxIterations || null,
1019
+ enableRespawn: config.enableRespawn,
1020
+ planItems: enabledItems?.length ? enabledItems : undefined,
1021
+ }),
1022
+ });
1023
+ const data = await res.json();
1024
+ if (!data.success) {
1025
+ this.showToast(data.error || 'Failed to start', 'error');
1026
+ return;
1027
+ }
1028
+ this.ralphClosedSessions.delete(data.sessionId);
1029
+ await this.selectSession(data.sessionId);
1030
+ this.showToast(`Ralph Loop started in ${config.caseName}`, 'success');
1031
+ } catch (err) {
1032
+ console.error('Failed to start Ralph loop:', err);
1033
+ this.showToast('Failed to start Ralph loop: ' + err.message, 'error');
1034
+ }
1035
+ },
1036
+
1037
+ });