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.
- package/LICENSE +21 -0
- package/README.md +403 -0
- package/dist/ai-checker-base.d.ts +175 -0
- package/dist/ai-checker-base.d.ts.map +1 -0
- package/dist/ai-checker-base.js +424 -0
- package/dist/ai-checker-base.js.map +1 -0
- package/dist/ai-idle-checker.d.ts +53 -0
- package/dist/ai-idle-checker.d.ts.map +1 -0
- package/dist/ai-idle-checker.js +141 -0
- package/dist/ai-idle-checker.js.map +1 -0
- package/dist/ai-plan-checker.d.ts +52 -0
- package/dist/ai-plan-checker.d.ts.map +1 -0
- package/dist/ai-plan-checker.js +103 -0
- package/dist/ai-plan-checker.js.map +1 -0
- package/dist/bash-tool-parser.d.ts +191 -0
- package/dist/bash-tool-parser.d.ts.map +1 -0
- package/dist/bash-tool-parser.js +598 -0
- package/dist/bash-tool-parser.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +460 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/buffer-limits.d.ts +59 -0
- package/dist/config/buffer-limits.d.ts.map +1 -0
- package/dist/config/buffer-limits.js +74 -0
- package/dist/config/buffer-limits.js.map +1 -0
- package/dist/config/map-limits.d.ts +40 -0
- package/dist/config/map-limits.d.ts.map +1 -0
- package/dist/config/map-limits.js +52 -0
- package/dist/config/map-limits.js.map +1 -0
- package/dist/file-stream-manager.d.ts +148 -0
- package/dist/file-stream-manager.d.ts.map +1 -0
- package/dist/file-stream-manager.js +351 -0
- package/dist/file-stream-manager.js.map +1 -0
- package/dist/hooks-config.d.ts +31 -0
- package/dist/hooks-config.d.ts.map +1 -0
- package/dist/hooks-config.js +115 -0
- package/dist/hooks-config.js.map +1 -0
- package/dist/image-watcher.d.ts +86 -0
- package/dist/image-watcher.d.ts.map +1 -0
- package/dist/image-watcher.js +275 -0
- package/dist/image-watcher.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/mux-factory.d.ts +13 -0
- package/dist/mux-factory.d.ts.map +1 -0
- package/dist/mux-factory.js +19 -0
- package/dist/mux-factory.js.map +1 -0
- package/dist/mux-interface.d.ts +145 -0
- package/dist/mux-interface.d.ts.map +1 -0
- package/dist/mux-interface.js +9 -0
- package/dist/mux-interface.js.map +1 -0
- package/dist/plan-orchestrator.d.ts +123 -0
- package/dist/plan-orchestrator.d.ts.map +1 -0
- package/dist/plan-orchestrator.js +500 -0
- package/dist/plan-orchestrator.js.map +1 -0
- package/dist/prompts/index.d.ts +9 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +9 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/planner.d.ts +14 -0
- package/dist/prompts/planner.d.ts.map +1 -0
- package/dist/prompts/planner.js +83 -0
- package/dist/prompts/planner.js.map +1 -0
- package/dist/prompts/research-agent.d.ts +10 -0
- package/dist/prompts/research-agent.d.ts.map +1 -0
- package/dist/prompts/research-agent.js +143 -0
- package/dist/prompts/research-agent.js.map +1 -0
- package/dist/push-store.d.ts +41 -0
- package/dist/push-store.d.ts.map +1 -0
- package/dist/push-store.js +168 -0
- package/dist/push-store.js.map +1 -0
- package/dist/ralph-config.d.ts +67 -0
- package/dist/ralph-config.d.ts.map +1 -0
- package/dist/ralph-config.js +134 -0
- package/dist/ralph-config.js.map +1 -0
- package/dist/ralph-loop.d.ts +124 -0
- package/dist/ralph-loop.d.ts.map +1 -0
- package/dist/ralph-loop.js +418 -0
- package/dist/ralph-loop.js.map +1 -0
- package/dist/ralph-tracker.d.ts +1081 -0
- package/dist/ralph-tracker.d.ts.map +1 -0
- package/dist/ralph-tracker.js +3343 -0
- package/dist/ralph-tracker.js.map +1 -0
- package/dist/respawn-controller.d.ts +1182 -0
- package/dist/respawn-controller.d.ts.map +1 -0
- package/dist/respawn-controller.js +2754 -0
- package/dist/respawn-controller.js.map +1 -0
- package/dist/run-summary.d.ts +123 -0
- package/dist/run-summary.d.ts.map +1 -0
- package/dist/run-summary.js +325 -0
- package/dist/run-summary.js.map +1 -0
- package/dist/session-lifecycle-log.d.ts +36 -0
- package/dist/session-lifecycle-log.d.ts.map +1 -0
- package/dist/session-lifecycle-log.js +101 -0
- package/dist/session-lifecycle-log.js.map +1 -0
- package/dist/session-manager.d.ts +97 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +224 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/session.d.ts +686 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +2025 -0
- package/dist/session.js.map +1 -0
- package/dist/state-store.d.ts +189 -0
- package/dist/state-store.d.ts.map +1 -0
- package/dist/state-store.js +730 -0
- package/dist/state-store.js.map +1 -0
- package/dist/subagent-watcher.d.ts +345 -0
- package/dist/subagent-watcher.d.ts.map +1 -0
- package/dist/subagent-watcher.js +1469 -0
- package/dist/subagent-watcher.js.map +1 -0
- package/dist/task-queue.d.ts +108 -0
- package/dist/task-queue.d.ts.map +1 -0
- package/dist/task-queue.js +235 -0
- package/dist/task-queue.js.map +1 -0
- package/dist/task-tracker.d.ts +306 -0
- package/dist/task-tracker.d.ts.map +1 -0
- package/dist/task-tracker.js +488 -0
- package/dist/task-tracker.js.map +1 -0
- package/dist/task.d.ts +73 -0
- package/dist/task.d.ts.map +1 -0
- package/dist/task.js +177 -0
- package/dist/task.js.map +1 -0
- package/dist/team-watcher.d.ts +53 -0
- package/dist/team-watcher.d.ts.map +1 -0
- package/dist/team-watcher.js +313 -0
- package/dist/team-watcher.js.map +1 -0
- package/dist/templates/case-template.md +461 -0
- package/dist/templates/claude-md.d.ts +26 -0
- package/dist/templates/claude-md.d.ts.map +1 -0
- package/dist/templates/claude-md.js +74 -0
- package/dist/templates/claude-md.js.map +1 -0
- package/dist/tmux-manager.d.ts +181 -0
- package/dist/tmux-manager.d.ts.map +1 -0
- package/dist/tmux-manager.js +1405 -0
- package/dist/tmux-manager.js.map +1 -0
- package/dist/transcript-watcher.d.ts +110 -0
- package/dist/transcript-watcher.d.ts.map +1 -0
- package/dist/transcript-watcher.js +338 -0
- package/dist/transcript-watcher.js.map +1 -0
- package/dist/tunnel-manager.d.ts +54 -0
- package/dist/tunnel-manager.d.ts.map +1 -0
- package/dist/tunnel-manager.js +251 -0
- package/dist/tunnel-manager.js.map +1 -0
- package/dist/types.d.ts +1139 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +215 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/buffer-accumulator.d.ts +111 -0
- package/dist/utils/buffer-accumulator.d.ts.map +1 -0
- package/dist/utils/buffer-accumulator.js +172 -0
- package/dist/utils/buffer-accumulator.js.map +1 -0
- package/dist/utils/claude-cli-resolver.d.ts +26 -0
- package/dist/utils/claude-cli-resolver.d.ts.map +1 -0
- package/dist/utils/claude-cli-resolver.js +78 -0
- package/dist/utils/claude-cli-resolver.js.map +1 -0
- package/dist/utils/cleanup-manager.d.ts +165 -0
- package/dist/utils/cleanup-manager.d.ts.map +1 -0
- package/dist/utils/cleanup-manager.js +274 -0
- package/dist/utils/cleanup-manager.js.map +1 -0
- package/dist/utils/index.d.ts +19 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +19 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/lru-map.d.ts +140 -0
- package/dist/utils/lru-map.d.ts.map +1 -0
- package/dist/utils/lru-map.js +234 -0
- package/dist/utils/lru-map.js.map +1 -0
- package/dist/utils/nice-wrapper.d.ts +13 -0
- package/dist/utils/nice-wrapper.d.ts.map +1 -0
- package/dist/utils/nice-wrapper.js +17 -0
- package/dist/utils/nice-wrapper.js.map +1 -0
- package/dist/utils/opencode-cli-resolver.d.ts +21 -0
- package/dist/utils/opencode-cli-resolver.d.ts.map +1 -0
- package/dist/utils/opencode-cli-resolver.js +67 -0
- package/dist/utils/opencode-cli-resolver.js.map +1 -0
- package/dist/utils/regex-patterns.d.ts +64 -0
- package/dist/utils/regex-patterns.d.ts.map +1 -0
- package/dist/utils/regex-patterns.js +74 -0
- package/dist/utils/regex-patterns.js.map +1 -0
- package/dist/utils/stale-expiration-map.d.ts +159 -0
- package/dist/utils/stale-expiration-map.d.ts.map +1 -0
- package/dist/utils/stale-expiration-map.js +277 -0
- package/dist/utils/stale-expiration-map.js.map +1 -0
- package/dist/utils/string-similarity.d.ts +108 -0
- package/dist/utils/string-similarity.d.ts.map +1 -0
- package/dist/utils/string-similarity.js +189 -0
- package/dist/utils/string-similarity.js.map +1 -0
- package/dist/utils/token-validation.d.ts +39 -0
- package/dist/utils/token-validation.d.ts.map +1 -0
- package/dist/utils/token-validation.js +59 -0
- package/dist/utils/token-validation.js.map +1 -0
- package/dist/utils/type-safety.d.ts +33 -0
- package/dist/utils/type-safety.d.ts.map +1 -0
- package/dist/utils/type-safety.js +35 -0
- package/dist/utils/type-safety.js.map +1 -0
- package/dist/web/public/app.js +491 -0
- package/dist/web/public/app.js.br +0 -0
- package/dist/web/public/app.js.gz +0 -0
- package/dist/web/public/index.html +1675 -0
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/manifest.json +8 -0
- package/dist/web/public/mobile.css +1 -0
- package/dist/web/public/mobile.css.br +0 -0
- package/dist/web/public/mobile.css.gz +0 -0
- package/dist/web/public/ralph-wizard.js +1037 -0
- package/dist/web/public/ralph-wizard.js.br +0 -0
- package/dist/web/public/ralph-wizard.js.gz +0 -0
- package/dist/web/public/styles.css +1 -0
- package/dist/web/public/styles.css.br +0 -0
- package/dist/web/public/styles.css.gz +0 -0
- package/dist/web/public/sw.js +67 -0
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/upload.html +155 -0
- package/dist/web/public/upload.html.br +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js +1 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.br +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js +1 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.br +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js +2 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.br +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css +209 -0
- package/dist/web/public/vendor/xterm.css.br +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js +9 -0
- package/dist/web/public/vendor/xterm.min.js.br +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/schemas.d.ts +479 -0
- package/dist/web/schemas.d.ts.map +1 -0
- package/dist/web/schemas.js +448 -0
- package/dist/web/schemas.js.map +1 -0
- package/dist/web/server.d.ts +207 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +5784 -0
- package/dist/web/server.js.map +1 -0
- package/package.json +110 -0
- 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(/<promise>/g, '<span class="preview-highlight"><promise>')
|
|
367
|
+
.replace(/<\/promise>/g, '</promise></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
|
+
});
|