clay-server 2.6.0 → 2.7.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.
@@ -502,22 +502,13 @@ export function toggleDarkMode() {
502
502
  updateToggleIcon();
503
503
  }
504
504
 
505
- // Update the toggle button icon
505
+ // Update the toggle switch checkbox state
506
506
  function updateToggleIcon() {
507
- var btn = document.getElementById("theme-toggle-btn");
508
- if (!btn) return;
507
+ var checkbox = document.getElementById("theme-toggle-check");
508
+ if (!checkbox) return;
509
509
  var mode = getEffectiveMode();
510
- var iconName = mode === "dark" ? "moon" : "sun";
511
- // Replace the icon element entirely
512
- var existing = btn.querySelector(".lucide, [data-lucide]");
513
- if (existing) {
514
- var i = document.createElement("i");
515
- i.setAttribute("data-lucide", iconName);
516
- btn.replaceChild(i, existing);
517
- if (window.lucide && window.lucide.createIcons) {
518
- window.lucide.createIcons();
519
- }
520
- }
510
+ // checked = light mode (thumb slides right, covering moon)
511
+ checkbox.checked = (mode === "light");
521
512
  }
522
513
 
523
514
  // --- Theme picker UI ---
@@ -687,11 +678,10 @@ export function initTheme() {
687
678
  // Load all themes from server, then apply properly
688
679
  loadThemes();
689
680
 
690
- // Wire up title bar toggle button
691
- var toggleBtn = document.getElementById("theme-toggle-btn");
692
- if (toggleBtn) {
693
- toggleBtn.addEventListener("click", function (e) {
694
- e.stopPropagation();
681
+ // Wire up title bar toggle switch
682
+ var toggleCheck = document.getElementById("theme-toggle-check");
683
+ if (toggleCheck) {
684
+ toggleCheck.addEventListener("change", function () {
695
685
  toggleDarkMode();
696
686
  });
697
687
  }
@@ -189,6 +189,13 @@ export function renderAskUserQuestion(toolId, input) {
189
189
  var qDiv = document.createElement("div");
190
190
  qDiv.className = "ask-user-question";
191
191
 
192
+ if (q.header) {
193
+ var qHeader = document.createElement("div");
194
+ qHeader.className = "ask-user-question-header";
195
+ qHeader.textContent = q.header;
196
+ qDiv.appendChild(qHeader);
197
+ }
198
+
192
199
  var qText = document.createElement("div");
193
200
  qText.className = "ask-user-question-text";
194
201
  qText.textContent = q.question || "";
@@ -208,6 +215,12 @@ export function renderAskUserQuestion(toolId, input) {
208
215
  (opt.description ? '<div class="option-desc"></div>' : '');
209
216
  btn.querySelector(".option-label").textContent = opt.label;
210
217
  if (opt.description) btn.querySelector(".option-desc").textContent = opt.description;
218
+ if (opt.markdown) {
219
+ var pre = document.createElement("pre");
220
+ pre.className = "option-markdown";
221
+ pre.textContent = opt.markdown;
222
+ btn.appendChild(pre);
223
+ }
211
224
 
212
225
  btn.addEventListener("click", function () {
213
226
  if (container.classList.contains("answered")) return;
@@ -267,8 +280,9 @@ export function renderAskUserQuestion(toolId, input) {
267
280
  container.appendChild(qDiv);
268
281
  });
269
282
 
270
- // Single submit button at the bottom (only for multi-question)
271
- if (questions.length > 1) {
283
+ // Submit button: show for multi-question or any multi-select question
284
+ var hasMultiSelect = questions.some(function (q) { return q.multiSelect; });
285
+ if (questions.length > 1 || hasMultiSelect) {
272
286
  var submitBtn = document.createElement("button");
273
287
  submitBtn.className = "ask-user-submit";
274
288
  submitBtn.textContent = "Submit";
@@ -14,3 +14,4 @@
14
14
  @import url("css/sticky-notes.css");
15
15
  @import url("css/skills.css");
16
16
  @import url("css/mobile-nav.css");
17
+ @import url("css/loop.css");
package/lib/sdk-bridge.js CHANGED
@@ -479,6 +479,17 @@ function createSDKBridge(opts) {
479
479
  // --- SDK query lifecycle ---
480
480
 
481
481
  function handleCanUseTool(session, toolName, input, opts) {
482
+ // Ralph Loop: auto-approve all tools, deny interactive ones
483
+ if (session.loop && session.loop.active) {
484
+ if (toolName === "AskUserQuestion") {
485
+ return Promise.resolve({ behavior: "deny", message: "Autonomous mode. Make your own decision." });
486
+ }
487
+ if (toolName === "EnterPlanMode") {
488
+ return Promise.resolve({ behavior: "deny", message: "Do not enter plan mode. Execute directly." });
489
+ }
490
+ return Promise.resolve({ behavior: "allow", updatedInput: input });
491
+ }
492
+
482
493
  // AskUserQuestion: wait for user answers via WebSocket
483
494
  if (toolName === "AskUserQuestion") {
484
495
  return new Promise(function(resolve) {
@@ -666,6 +677,15 @@ function createSDKBridge(opts) {
666
677
  session.pendingPermissions = {};
667
678
  session.pendingAskUser = {};
668
679
  }
680
+ // Ralph Loop: notify completion so loop orchestrator can proceed
681
+ if (session.onQueryComplete) {
682
+ console.log("[sdk-bridge] Calling onQueryComplete for session " + session.localId + " (title: " + (session.title || "?") + ")");
683
+ try {
684
+ session.onQueryComplete(session);
685
+ } catch (err) {
686
+ console.error("[sdk-bridge] onQueryComplete error:", err.message || err);
687
+ }
688
+ }
669
689
  }
670
690
 
671
691
  async function getOrCreateRewindQuery(session) {
@@ -808,6 +828,14 @@ function createSDKBridge(opts) {
808
828
  return;
809
829
  }
810
830
 
831
+ // For single-turn sessions (Ralph Loop), end the message queue so the SDK
832
+ // query finishes after processing the one message. Without this, the query
833
+ // stream stays open forever waiting for more messages, and onQueryComplete
834
+ // never fires.
835
+ if (session.singleTurn) {
836
+ session.messageQueue.end();
837
+ }
838
+
811
839
  session.streamPromise = processQueryStream(session).catch(function(err) {
812
840
  });
813
841
  }
package/lib/sessions.js CHANGED
@@ -178,6 +178,7 @@ function createSessionManager(opts) {
178
178
  active: s.localId === activeSessionId,
179
179
  isProcessing: s.isProcessing,
180
180
  lastActivity: s.lastActivity || s.createdAt || 0,
181
+ loop: s.loop || null,
181
182
  };
182
183
  }),
183
184
  });
@@ -254,7 +255,7 @@ function createSessionManager(opts) {
254
255
  if (!session) return;
255
256
 
256
257
  activeSessionId = localId;
257
- send({ type: "session_switched", id: localId, cliSessionId: session.cliSessionId || null });
258
+ send({ type: "session_switched", id: localId, cliSessionId: session.cliSessionId || null, loop: session.loop || null });
258
259
  broadcastSessionList();
259
260
  replayHistory(session);
260
261
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "Web UI for Claude Code. Any device. Push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",