clay-server 2.27.0-beta.8 → 2.27.0

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 (72) hide show
  1. package/README.md +10 -0
  2. package/lib/daemon-projects.js +164 -0
  3. package/lib/daemon.js +13 -126
  4. package/lib/mates-identity.js +132 -0
  5. package/lib/mates-knowledge.js +113 -0
  6. package/lib/mates-prompts.js +398 -0
  7. package/lib/mates.js +40 -599
  8. package/lib/project-connection.js +2 -0
  9. package/lib/project-debate.js +19 -12
  10. package/lib/project-http.js +4 -2
  11. package/lib/project-loop.js +110 -48
  12. package/lib/project-mate-interaction.js +4 -0
  13. package/lib/project-notifications.js +210 -0
  14. package/lib/project-sessions.js +5 -2
  15. package/lib/project-user-message.js +2 -1
  16. package/lib/project.js +26 -2
  17. package/lib/public/app.js +1193 -8521
  18. package/lib/public/css/command-palette.css +14 -0
  19. package/lib/public/css/loop.css +301 -0
  20. package/lib/public/css/notifications-center.css +190 -0
  21. package/lib/public/css/rewind.css +6 -0
  22. package/lib/public/index.html +89 -35
  23. package/lib/public/modules/app-connection.js +160 -0
  24. package/lib/public/modules/app-cursors.js +473 -0
  25. package/lib/public/modules/app-debate-ui.js +389 -0
  26. package/lib/public/modules/app-dm.js +627 -0
  27. package/lib/public/modules/app-favicon.js +212 -0
  28. package/lib/public/modules/app-header.js +229 -0
  29. package/lib/public/modules/app-home-hub.js +600 -0
  30. package/lib/public/modules/app-loop-ui.js +589 -0
  31. package/lib/public/modules/app-loop-wizard.js +439 -0
  32. package/lib/public/modules/app-messages.js +1560 -0
  33. package/lib/public/modules/app-misc.js +299 -0
  34. package/lib/public/modules/app-notifications.js +372 -0
  35. package/lib/public/modules/app-panels.js +888 -0
  36. package/lib/public/modules/app-projects.js +798 -0
  37. package/lib/public/modules/app-rate-limit.js +451 -0
  38. package/lib/public/modules/app-rendering.js +597 -0
  39. package/lib/public/modules/app-skills-install.js +234 -0
  40. package/lib/public/modules/command-palette.js +27 -4
  41. package/lib/public/modules/input.js +31 -20
  42. package/lib/public/modules/scheduler-config.js +1532 -0
  43. package/lib/public/modules/scheduler-history.js +79 -0
  44. package/lib/public/modules/scheduler.js +33 -1554
  45. package/lib/public/modules/session-search.js +13 -1
  46. package/lib/public/modules/sidebar-mates.js +812 -0
  47. package/lib/public/modules/sidebar-mobile.js +1269 -0
  48. package/lib/public/modules/sidebar-projects.js +1449 -0
  49. package/lib/public/modules/sidebar-sessions.js +986 -0
  50. package/lib/public/modules/sidebar.js +232 -4591
  51. package/lib/public/modules/store.js +27 -0
  52. package/lib/public/modules/ws-ref.js +7 -0
  53. package/lib/public/style.css +1 -0
  54. package/lib/sdk-bridge.js +96 -717
  55. package/lib/sdk-message-processor.js +587 -0
  56. package/lib/sdk-message-queue.js +42 -0
  57. package/lib/sdk-skill-discovery.js +131 -0
  58. package/lib/server-admin.js +712 -0
  59. package/lib/server-auth.js +737 -0
  60. package/lib/server-dm.js +221 -0
  61. package/lib/server-mates.js +281 -0
  62. package/lib/server-palette.js +110 -0
  63. package/lib/server-settings.js +479 -0
  64. package/lib/server-skills.js +280 -0
  65. package/lib/server.js +246 -2755
  66. package/lib/sessions.js +11 -4
  67. package/lib/users-auth.js +146 -0
  68. package/lib/users-permissions.js +118 -0
  69. package/lib/users-preferences.js +210 -0
  70. package/lib/users.js +48 -398
  71. package/lib/ws-schema.js +498 -0
  72. package/package.json +1 -1
@@ -0,0 +1,589 @@
1
+ // app-loop-ui.js - Ralph Loop UI: bars, banners, preview modal, execution modal
2
+ // Wizard logic extracted to app-loop-wizard.js
3
+
4
+ import { refreshIcons, iconHtml } from './icons.js';
5
+ import { escapeHtml } from './utils.js';
6
+ import { store } from './store.js';
7
+ import { getWs } from './ws-ref.js';
8
+ import { showConfirm } from './app-misc.js';
9
+ import { openRalphWizard } from './app-loop-wizard.js';
10
+ import { openSchedulerToTab } from './scheduler.js';
11
+ import { showDebateSticky } from './app-debate-ui.js';
12
+
13
+ // Execution modal: module-internal UI var (not in store)
14
+ var pendingIterations = 20;
15
+
16
+ // ========================================================
17
+ // Init
18
+ // ========================================================
19
+ export function initLoopUi() {
20
+
21
+ // --- Preview modal listeners ---
22
+ // Backdrop click intentionally does NOT close the modal (no way to reopen it)
23
+
24
+ // Run button in preview modal footer
25
+ var previewRunBtn = document.getElementById("ralph-preview-run");
26
+ if (previewRunBtn) {
27
+ previewRunBtn.addEventListener("click", function (e) {
28
+ e.stopPropagation();
29
+ closeRalphPreviewModal();
30
+ startLoopFromUi();
31
+ });
32
+ }
33
+
34
+ // Delete/cancel button in preview modal header
35
+ var previewDeleteBtn = document.getElementById("ralph-preview-delete");
36
+ if (previewDeleteBtn) {
37
+ previewDeleteBtn.addEventListener("click", function (e) {
38
+ e.stopPropagation();
39
+ closeRalphPreviewModal();
40
+ showConfirm("Discard this loop setup?", function() {
41
+ var ws = getWs();
42
+ if (ws && ws.readyState === 1) {
43
+ ws.send(JSON.stringify({ type: "ralph_wizard_cancel" }));
44
+ }
45
+ var stickyEl = document.getElementById("ralph-sticky");
46
+ if (stickyEl) {
47
+ stickyEl.classList.add("hidden");
48
+ stickyEl.classList.remove("ralph-ready");
49
+ stickyEl.innerHTML = "";
50
+ }
51
+ });
52
+ });
53
+ }
54
+
55
+ var previewTabs = document.querySelectorAll(".ralph-tab");
56
+ for (var ti = 0; ti < previewTabs.length; ti++) {
57
+ previewTabs[ti].addEventListener("click", function() {
58
+ showRalphPreviewTab(this.getAttribute("data-tab"));
59
+ });
60
+ }
61
+
62
+ // Iterations input in preview modal footer
63
+ var previewIterInput = document.getElementById("ralph-preview-iterations");
64
+ if (previewIterInput) {
65
+ previewIterInput.addEventListener("input", function () {
66
+ pendingIterations = parseInt(this.value, 10) || pendingIterations;
67
+ syncIterationsUi();
68
+ });
69
+ }
70
+ }
71
+
72
+ // ========================================================
73
+ // Iteration sync helper
74
+ // ========================================================
75
+
76
+ function syncIterationsUi() {
77
+ var wizData = store.getState().wizardData || {};
78
+ var isSimple = wizData.loopMode === "simple";
79
+ var label = isSimple ? ("Run x" + pendingIterations) : ("Start (max " + pendingIterations + ")");
80
+
81
+ // Sync sticky bar input
82
+ var stickyIter = document.getElementById("ralph-sticky-iterations");
83
+ if (stickyIter && parseInt(stickyIter.value, 10) !== pendingIterations) {
84
+ stickyIter.value = pendingIterations;
85
+ }
86
+ var stickyStartBtn = document.querySelector(".ralph-sticky-start");
87
+ if (stickyStartBtn) stickyStartBtn.title = label;
88
+
89
+ // Sync preview modal input
90
+ var previewIter = document.getElementById("ralph-preview-iterations");
91
+ if (previewIter && parseInt(previewIter.value, 10) !== pendingIterations) {
92
+ previewIter.value = pendingIterations;
93
+ }
94
+ var comboLabel = document.getElementById("ralph-run-combo-label");
95
+ if (comboLabel) comboLabel.textContent = isSimple ? "Run x" : "Start max";
96
+ }
97
+
98
+ // ========================================================
99
+ // Start loop from UI (shared by sticky bar + exec modal)
100
+ // ========================================================
101
+
102
+ function startLoopFromUi() {
103
+ var basePath = store.getState().basePath;
104
+ var stickyEl = document.getElementById("ralph-sticky");
105
+
106
+ fetch(basePath + "api/git-dirty")
107
+ .then(function (res) { return res.json(); })
108
+ .then(function (data) {
109
+ if (data.dirty) {
110
+ var fileList = (data.files || []).slice(0, 15).join("\n");
111
+ if (data.files && data.files.length > 15) fileList += "\n... and " + (data.files.length - 15) + " more";
112
+ var msg = "You have uncommitted changes. The loop uses git diff to track progress, so uncommitted files may cause unexpected results.\n\n" + fileList + "\n\nStart anyway?";
113
+ showConfirm(msg, function () {
114
+ sendLoopStart();
115
+ if (stickyEl) { stickyEl.classList.add("hidden"); stickyEl.innerHTML = ""; }
116
+ }, "Start anyway", false);
117
+ } else {
118
+ sendLoopStart();
119
+ if (stickyEl) { stickyEl.classList.add("hidden"); stickyEl.innerHTML = ""; }
120
+ }
121
+ })
122
+ .catch(function () {
123
+ sendLoopStart();
124
+ if (stickyEl) { stickyEl.classList.add("hidden"); stickyEl.innerHTML = ""; }
125
+ });
126
+ }
127
+
128
+ function sendLoopStart() {
129
+ var ws = getWs();
130
+ if (ws && ws.readyState === 1) {
131
+ ws.send(JSON.stringify({ type: "loop_start", maxIterations: pendingIterations }));
132
+ }
133
+ }
134
+
135
+ // ========================================================
136
+ // Loop UI (exported)
137
+ // ========================================================
138
+
139
+ export function updateLoopInputVisibility(loop) {
140
+ var inputArea = document.getElementById("input-area");
141
+ if (!inputArea) return;
142
+ if (loop && loop.active && loop.role !== "crafting") {
143
+ inputArea.style.display = "none";
144
+ } else {
145
+ inputArea.style.display = "";
146
+ }
147
+ }
148
+
149
+ export function updateLoopButton() {
150
+ var section = document.getElementById("ralph-loop-section");
151
+ if (!section) return;
152
+
153
+ var s = store.getState();
154
+ var busy = s.loopActive || s.ralphPhase === "executing";
155
+ var phase = busy ? "executing" : s.ralphPhase;
156
+
157
+ var statusHtml = "";
158
+ var statusClass = "";
159
+ var clickAction = "wizard"; // default
160
+
161
+ if (phase === "crafting") {
162
+ statusHtml = '<span class="ralph-section-status crafting">' + iconHtml("loader", "icon-spin") + ' Crafting\u2026</span>';
163
+ clickAction = "none";
164
+ } else if (phase === "approval") {
165
+ statusHtml = '<span class="ralph-section-status ready">Ready</span>';
166
+ statusClass = "ralph-section-ready";
167
+ clickAction = "none";
168
+ } else if (phase === "executing") {
169
+ var iterText = s.loopIteration > 0 ? "Running \u00b7 iteration " + s.loopIteration + "/" + s.loopMaxIterations : "Starting\u2026";
170
+ statusHtml = '<span class="ralph-section-status running">' + iconHtml("loader", "icon-spin") + ' ' + iterText + '</span>';
171
+ statusClass = "ralph-section-running";
172
+ clickAction = "popover";
173
+ } else if (phase === "done") {
174
+ statusHtml = '<span class="ralph-section-status done">\u2713 Done</span>';
175
+ statusHtml += '<a href="#" class="ralph-section-tasks-link">View in Scheduled Tasks</a>';
176
+ statusClass = "ralph-section-done";
177
+ clickAction = "wizard";
178
+ } else {
179
+ // idle
180
+ statusHtml = '<span class="ralph-section-hint">Start a new loop</span>';
181
+ }
182
+
183
+ section.className = "ralph-loop-section" + (statusClass ? " " + statusClass : "");
184
+ section.innerHTML =
185
+ '<div class="ralph-section-inner">' +
186
+ '<div class="ralph-section-header">' +
187
+ '<span class="ralph-section-icon">' + iconHtml("repeat") + '</span>' +
188
+ '<span class="ralph-section-label">Loop</span>' +
189
+ '<span class="loop-experimental"><i data-lucide="flask-conical"></i> experimental</span>' +
190
+ '</div>' +
191
+ '<div class="ralph-section-body">' + statusHtml + '</div>' +
192
+ '</div>';
193
+
194
+ refreshIcons();
195
+
196
+ // Click handler on header
197
+ var header = section.querySelector(".ralph-section-header");
198
+ if (header) {
199
+ header.style.cursor = clickAction === "none" ? "default" : "pointer";
200
+ header.addEventListener("click", function() {
201
+ if (clickAction === "popover") {
202
+ toggleLoopPopover();
203
+ } else if (clickAction === "wizard") {
204
+ openRalphWizard();
205
+ }
206
+ });
207
+ }
208
+
209
+ // "View in Scheduled Tasks" link
210
+ var tasksLink = section.querySelector(".ralph-section-tasks-link");
211
+ if (tasksLink) {
212
+ tasksLink.addEventListener("click", function(e) {
213
+ e.preventDefault();
214
+ e.stopPropagation();
215
+ openSchedulerToTab("library");
216
+ });
217
+ }
218
+ }
219
+
220
+ export function showLoopBanner(show) {
221
+ var stickyEl = document.getElementById("ralph-sticky");
222
+ if (!stickyEl) { updateLoopButton(); return; }
223
+ if (!show) {
224
+ stickyEl.classList.add("hidden");
225
+ stickyEl.classList.remove("ralph-running");
226
+ stickyEl.innerHTML = "";
227
+ updateLoopButton();
228
+ return;
229
+ }
230
+
231
+ var bannerLabel = store.getState().loopBannerName || "Loop";
232
+ stickyEl.innerHTML =
233
+ '<div class="ralph-sticky-inner">' +
234
+ '<div class="ralph-sticky-header">' +
235
+ '<span class="ralph-sticky-icon">' + iconHtml("repeat") + '</span>' +
236
+ '<span class="ralph-sticky-label">' + escapeHtml(bannerLabel) + '</span>' +
237
+ '<span class="ralph-sticky-status" id="loop-status">Starting\u2026</span>' +
238
+ '<button class="ralph-sticky-action ralph-sticky-stop" title="Stop loop">' + iconHtml("square") + '</button>' +
239
+ '</div>' +
240
+ '</div>';
241
+ stickyEl.classList.remove("hidden", "ralph-ready");
242
+ stickyEl.classList.add("ralph-running");
243
+ refreshIcons();
244
+
245
+ stickyEl.querySelector(".ralph-sticky-stop").addEventListener("click", function(e) {
246
+ e.stopPropagation();
247
+ var w = getWs();
248
+ if (w && w.readyState === 1) {
249
+ w.send(JSON.stringify({ type: "loop_stop" }));
250
+ }
251
+ });
252
+ updateLoopButton();
253
+ }
254
+
255
+ export function updateLoopBanner(iteration, maxIterations, phase) {
256
+ var statusEl = document.getElementById("loop-status");
257
+ if (!statusEl) return;
258
+ var text;
259
+ if (phase === "stopping") {
260
+ text = "Stopping\u2026";
261
+ } else if (maxIterations <= 1) {
262
+ text = phase === "judging" ? "judging\u2026" : "running";
263
+ } else {
264
+ text = "#" + iteration + "/" + maxIterations;
265
+ if (phase === "judging") text += " judging\u2026";
266
+ else text += " running";
267
+ }
268
+ statusEl.textContent = text;
269
+ }
270
+
271
+ export function updateRalphBars() {
272
+ // Task source uses the scheduler panel, not the sticky bar
273
+ var s = store.getState();
274
+ var isTaskSource = s.ralphCraftingSource !== "ralph";
275
+ var onCraftingSession = s.ralphCraftingSessionId && s.activeSessionId === s.ralphCraftingSessionId;
276
+ // If approval phase but no craftingSessionId (recovered after server restart), show bar anyway
277
+ var recoveredApproval = s.ralphPhase === "approval" && !s.ralphCraftingSessionId;
278
+ if (!isTaskSource && s.ralphPhase === "crafting" && onCraftingSession) {
279
+ showRalphCraftingBar(true);
280
+ } else {
281
+ showRalphCraftingBar(false);
282
+ }
283
+ if (!isTaskSource && s.ralphPhase === "approval") {
284
+ showRalphApprovalBar(true);
285
+ } else {
286
+ showRalphApprovalBar(false);
287
+ }
288
+ // Restore running loop banner on session switch
289
+ if (s.loopActive && s.ralphPhase === "executing") {
290
+ showLoopBanner(true);
291
+ if (s.loopIteration > 0) {
292
+ updateLoopBanner(s.loopIteration, s.loopMaxIterations, "running");
293
+ }
294
+ }
295
+
296
+ // Restore debate sticky on session switch
297
+ var debateStickyState = store.getState().debateStickyState;
298
+ if (debateStickyState && debateStickyState.phase) {
299
+ showDebateSticky(debateStickyState.phase, debateStickyState.msg);
300
+ } else {
301
+ showDebateSticky("hide", null);
302
+ }
303
+ }
304
+
305
+ // ========================================================
306
+ // Internal: toggleLoopPopover
307
+ // ========================================================
308
+
309
+ function toggleLoopPopover() {
310
+ var existing = document.getElementById("loop-status-modal");
311
+ if (existing) {
312
+ existing.remove();
313
+ return;
314
+ }
315
+
316
+ var wizData = store.getState().wizardData || {};
317
+ var taskPreview = wizData.task || "\u2014";
318
+ if (taskPreview.length > 120) taskPreview = taskPreview.substring(0, 120) + "\u2026";
319
+ var _s = store.getState();
320
+ var statusText = "Iteration #" + _s.loopIteration + " / " + _s.loopMaxIterations;
321
+
322
+ var modal = document.createElement("div");
323
+ modal.id = "loop-status-modal";
324
+ modal.className = "loop-status-modal";
325
+ modal.innerHTML =
326
+ '<div class="loop-status-backdrop"></div>' +
327
+ '<div class="loop-status-dialog">' +
328
+ '<div class="loop-status-dialog-header">' +
329
+ '<span class="loop-status-dialog-icon">' + iconHtml("repeat") + '</span>' +
330
+ '<span class="loop-status-dialog-title">Loop</span>' +
331
+ '<button class="loop-status-dialog-close" title="Close">' + iconHtml("x") + '</button>' +
332
+ '</div>' +
333
+ '<div class="loop-status-dialog-body">' +
334
+ '<div class="loop-status-dialog-row">' +
335
+ '<span class="loop-status-dialog-label">Progress</span>' +
336
+ '<span class="loop-status-dialog-value">' + escapeHtml(statusText) + '</span>' +
337
+ '</div>' +
338
+ '<div class="loop-status-dialog-row">' +
339
+ '<span class="loop-status-dialog-label">Task</span>' +
340
+ '<span class="loop-status-dialog-value loop-status-dialog-task">' + escapeHtml(taskPreview) + '</span>' +
341
+ '</div>' +
342
+ '</div>' +
343
+ '<div class="loop-status-dialog-footer">' +
344
+ '<button class="loop-status-dialog-stop">' + iconHtml("square") + ' Stop loop</button>' +
345
+ '</div>' +
346
+ '</div>';
347
+
348
+ document.body.appendChild(modal);
349
+ refreshIcons();
350
+
351
+ function closeModal() { modal.remove(); }
352
+
353
+ modal.querySelector(".loop-status-backdrop").addEventListener("click", closeModal);
354
+ modal.querySelector(".loop-status-dialog-close").addEventListener("click", closeModal);
355
+
356
+ modal.querySelector(".loop-status-dialog-stop").addEventListener("click", function(e) {
357
+ e.stopPropagation();
358
+ closeModal();
359
+ showConfirm("Stop the running " + (store.getState().loopBannerName || "loop") + "?", function() {
360
+ var w = getWs();
361
+ if (w && w.readyState === 1) {
362
+ w.send(JSON.stringify({ type: "loop_stop" }));
363
+ }
364
+ });
365
+ });
366
+ }
367
+
368
+ // ========================================================
369
+ // Crafting / Approval bars (exported)
370
+ // ========================================================
371
+
372
+ export function showRalphCraftingBar(show) {
373
+ var stickyEl = document.getElementById("ralph-sticky");
374
+ if (!stickyEl) return;
375
+ if (!show) {
376
+ stickyEl.classList.add("hidden");
377
+ stickyEl.innerHTML = "";
378
+ return;
379
+ }
380
+ stickyEl.innerHTML =
381
+ '<div class="ralph-sticky-inner">' +
382
+ '<div class="ralph-sticky-header">' +
383
+ '<span class="ralph-sticky-icon">' + iconHtml("repeat") + '</span>' +
384
+ '<span class="ralph-sticky-label">Ralph</span>' +
385
+ '<span class="ralph-sticky-status">' + iconHtml("loader", "icon-spin") + ' Preparing\u2026</span>' +
386
+ '<button class="ralph-sticky-cancel" title="Cancel">' + iconHtml("x") + '</button>' +
387
+ '</div>' +
388
+ '</div>';
389
+ stickyEl.classList.remove("hidden");
390
+ refreshIcons();
391
+
392
+ var cancelBtn = stickyEl.querySelector(".ralph-sticky-cancel");
393
+ if (cancelBtn) {
394
+ cancelBtn.addEventListener("click", function(e) {
395
+ e.stopPropagation();
396
+ var ws = getWs();
397
+ if (ws && ws.readyState === 1) {
398
+ ws.send(JSON.stringify({ type: "ralph_cancel_crafting" }));
399
+ }
400
+ showRalphCraftingBar(false);
401
+ showRalphApprovalBar(false);
402
+ });
403
+ }
404
+ }
405
+
406
+ export function showRalphApprovalBar(show) {
407
+ var stickyEl = document.getElementById("ralph-sticky");
408
+ if (!stickyEl) return;
409
+ if (!show) {
410
+ // Only clear if we're in approval mode (don't clobber crafting)
411
+ if (store.getState().ralphPhase !== "crafting") {
412
+ stickyEl.classList.add("hidden");
413
+ stickyEl.innerHTML = "";
414
+ }
415
+ return;
416
+ }
417
+
418
+ var wizData = store.getState().wizardData || {};
419
+ var isSimple = wizData.loopMode === "simple";
420
+ var defaultIter = isSimple ? 5 : 20;
421
+ pendingIterations = defaultIter;
422
+
423
+ var runLabel = isSimple ? ("Run x" + defaultIter) : ("Start (max " + defaultIter + ")");
424
+
425
+ stickyEl.innerHTML =
426
+ '<div class="ralph-sticky-inner">' +
427
+ '<div class="ralph-sticky-header" id="ralph-sticky-header">' +
428
+ '<span class="ralph-sticky-icon">' + iconHtml("repeat") + '</span>' +
429
+ '<span class="ralph-sticky-label">Ralph</span>' +
430
+ '<span class="ralph-sticky-status" id="ralph-sticky-status">Ready</span>' +
431
+ '<input type="number" id="ralph-sticky-iterations" class="ralph-input-number ralph-sticky-iter" value="' + defaultIter + '" min="1" max="100">' +
432
+ '<button class="ralph-sticky-action ralph-sticky-preview" title="Preview files">' + iconHtml("eye") + '</button>' +
433
+ '<button class="ralph-sticky-action ralph-sticky-start" title="' + runLabel + '">' + iconHtml(wizData.cron ? "calendar-clock" : "play") + '</button>' +
434
+ '<button class="ralph-sticky-action ralph-sticky-dismiss" title="Cancel and discard">' + iconHtml("x") + '</button>' +
435
+ '</div>' +
436
+ '</div>';
437
+ stickyEl.classList.remove("hidden");
438
+ refreshIcons();
439
+
440
+ // Iteration input handler
441
+ var stickyIterInput = document.getElementById("ralph-sticky-iterations");
442
+ if (stickyIterInput) {
443
+ stickyIterInput.addEventListener("input", function () {
444
+ pendingIterations = parseInt(this.value, 10) || pendingIterations;
445
+ syncIterationsUi();
446
+ });
447
+ }
448
+
449
+ stickyEl.querySelector(".ralph-sticky-preview").addEventListener("click", function(e) {
450
+ e.stopPropagation();
451
+ var ws = getWs();
452
+ if (ws && ws.readyState === 1) {
453
+ ws.send(JSON.stringify({ type: "ralph_preview_files" }));
454
+ }
455
+ });
456
+
457
+ stickyEl.querySelector(".ralph-sticky-start").addEventListener("click", function(e) {
458
+ e.stopPropagation();
459
+ startLoopFromUi();
460
+ });
461
+
462
+ stickyEl.querySelector(".ralph-sticky-dismiss").addEventListener("click", function(e) {
463
+ e.stopPropagation();
464
+ showConfirm("Discard this Ralph Loop setup?", function() {
465
+ var ws = getWs();
466
+ if (ws && ws.readyState === 1) {
467
+ ws.send(JSON.stringify({ type: "ralph_wizard_cancel" }));
468
+ }
469
+ stickyEl.classList.add("hidden");
470
+ stickyEl.classList.remove("ralph-ready");
471
+ stickyEl.innerHTML = "";
472
+ });
473
+ });
474
+
475
+ updateRalphApprovalStatus();
476
+ }
477
+
478
+ export function updateRalphApprovalStatus() {
479
+ var stickyEl = document.getElementById("ralph-sticky");
480
+ var statusEl = document.getElementById("ralph-sticky-status");
481
+ var startBtn = document.querySelector(".ralph-sticky-start");
482
+ if (!statusEl) return;
483
+
484
+ var wizData = store.getState().wizardData || {};
485
+ var isSimple = wizData.loopMode === "simple";
486
+ var _rf = store.getState().ralphFilesReady;
487
+ var ready = isSimple ? _rf.promptReady : _rf.bothReady;
488
+
489
+ if (ready) {
490
+ statusEl.textContent = "Ready";
491
+ if (startBtn) startBtn.disabled = false;
492
+ if (stickyEl) stickyEl.classList.add("ralph-ready");
493
+ } else if (_rf.promptReady || _rf.judgeReady) {
494
+ statusEl.textContent = "Partial\u2026";
495
+ if (startBtn) startBtn.disabled = true;
496
+ if (stickyEl) stickyEl.classList.remove("ralph-ready");
497
+ } else {
498
+ statusEl.textContent = "Waiting\u2026";
499
+ if (startBtn) startBtn.disabled = true;
500
+ if (stickyEl) stickyEl.classList.remove("ralph-ready");
501
+ }
502
+ }
503
+
504
+ // ========================================================
505
+ // Preview modal (exported: openRalphPreviewModal, showExecModal)
506
+ // The preview modal doubles as the execution modal.
507
+ // showExecModal opens it with auto-popup flag set.
508
+ // ========================================================
509
+
510
+ export function showExecModal() {
511
+ store.setState({ execModalShown: true });
512
+ // Open the preview modal immediately, then request file content to fill in
513
+ openRalphPreviewModal();
514
+ var ws = getWs();
515
+ if (ws && ws.readyState === 1) {
516
+ ws.send(JSON.stringify({ type: "ralph_preview_files" }));
517
+ }
518
+ }
519
+
520
+ export function closeExecModal() {
521
+ closeRalphPreviewModal();
522
+ }
523
+
524
+ export function updateExecModalStatus() {
525
+ // no-op, preview modal updates on open
526
+ }
527
+
528
+ export function openRalphPreviewModal() {
529
+ var modal = document.getElementById("ralph-preview-modal");
530
+ if (!modal) return;
531
+
532
+ var wizData = store.getState().wizardData || {};
533
+ var isSimple = wizData.loopMode === "simple";
534
+
535
+ // Set defaults if not yet set
536
+ if (!pendingIterations || pendingIterations <= 0) {
537
+ pendingIterations = isSimple ? 5 : 20;
538
+ }
539
+
540
+ // Set name
541
+ var nameEl = document.getElementById("ralph-preview-name");
542
+ if (nameEl) {
543
+ nameEl.textContent = (wizData && wizData.name) || "Loop";
544
+ }
545
+
546
+ // Hide JUDGE.md tab for simple loop
547
+ var judgeTab = document.querySelector('#ralph-preview-modal .ralph-tab[data-tab="judge"]');
548
+ if (judgeTab) judgeTab.style.display = isSimple ? "none" : "";
549
+
550
+ // Update footer: iteration label + run button
551
+ var iterInput = document.getElementById("ralph-preview-iterations");
552
+ if (iterInput) iterInput.value = pendingIterations;
553
+
554
+ var comboLabel = document.getElementById("ralph-run-combo-label");
555
+ if (comboLabel) comboLabel.textContent = isSimple ? "Run x" : "Start max";
556
+
557
+ var runBtn = document.getElementById("ralph-preview-run");
558
+ if (runBtn) runBtn.disabled = !store.getState().ralphFilesReady.bothReady;
559
+
560
+ showRalphPreviewTab("prompt");
561
+ modal.classList.remove("hidden");
562
+ refreshIcons();
563
+ syncIterationsUi();
564
+ }
565
+
566
+ function closeRalphPreviewModal() {
567
+ var modal = document.getElementById("ralph-preview-modal");
568
+ if (modal) modal.classList.add("hidden");
569
+ }
570
+
571
+ function showRalphPreviewTab(tab) {
572
+ var tabs = document.querySelectorAll("#ralph-preview-modal .ralph-tab");
573
+ for (var i = 0; i < tabs.length; i++) {
574
+ if (tabs[i].getAttribute("data-tab") === tab) {
575
+ tabs[i].classList.add("active");
576
+ } else {
577
+ tabs[i].classList.remove("active");
578
+ }
579
+ }
580
+ var body = document.getElementById("ralph-preview-body");
581
+ if (!body) return;
582
+ var _rpc = store.getState().ralphPreviewContent;
583
+ var content = tab === "prompt" ? _rpc.prompt : _rpc.judge;
584
+ if (typeof marked !== "undefined" && marked.parse) {
585
+ body.innerHTML = '<div class="md-content">' + DOMPurify.sanitize(marked.parse(content)) + '</div>';
586
+ } else {
587
+ body.textContent = content;
588
+ }
589
+ }