clay-server 3.4.0-beta.20 → 3.4.0-beta.21

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.
@@ -98,10 +98,15 @@ function attachSessionPair(ctx) {
98
98
  token.delivered = true;
99
99
  var failure = token.failure || null;
100
100
  var response = token.response || "";
101
- var text = "A Worker task delegated through send_to_partner has finished.\n\n" +
102
- "Original task:\n" + token.message + "\n\n" +
103
- (failure ? "Worker error:\n" + failure : "Worker result:\n" + (response || "(No text response was recorded.)")) +
104
- "\n\nReview the result, verify it as needed, and continue the task.";
101
+ var text;
102
+ if (token.interrupted) {
103
+ text = "[Worker execution interrupted] The user interrupted the Worker mid-turn. Its work is PARTIAL and unverified — do not treat it as finished. Review what was done and decide next steps with the user.";
104
+ } else {
105
+ text = "A Worker task delegated through send_to_partner has finished.\n\n" +
106
+ "Original task:\n" + token.message + "\n\n" +
107
+ (failure ? "Worker error:\n" + failure : "Worker result:\n" + (response || "(No text response was recorded.)")) +
108
+ "\n\nReview the result, verify it as needed, and continue the task.";
109
+ }
105
110
  sm.sendAndRecord(caller, {
106
111
  type: "user_message",
107
112
  text: text,
@@ -146,6 +151,7 @@ function attachSessionPair(ctx) {
146
151
  }
147
152
  token.response = responseText(partner.history || [], token.startIndex);
148
153
  token.failure = errorSince(partner.history || [], token.startIndex);
154
+ token.interrupted = !token.failure && !!partner._lastTurnInterrupted;
149
155
  finishDelegation(group, caller, partner, token);
150
156
  return resumeDriverWithResult(caller, partner, token);
151
157
  }
@@ -165,8 +171,9 @@ function attachSessionPair(ctx) {
165
171
  clearInterval(timer);
166
172
  finishDelegation(group, caller, partner, token);
167
173
  var failure = errorSince(partner.history || [], token.startIndex);
174
+ var interrupted = !failure && !!partner._lastTurnInterrupted;
168
175
  resolve({
169
- status: failure ? "error" : "complete",
176
+ status: failure ? "error" : (interrupted ? "interrupted" : "complete"),
170
177
  response: responseText(partner.history || [], token.startIndex),
171
178
  error: failure || undefined,
172
179
  });
@@ -260,7 +267,7 @@ function attachSessionPair(ctx) {
260
267
  var count = Number.isFinite(args.lastTurns) ? Math.floor(args.lastTurns) : 1;
261
268
  count = Math.max(1, Math.min(5, count));
262
269
  return toolResult({
263
- status: resolved.partner.isProcessing ? "running" : "idle",
270
+ status: resolved.partner.isProcessing ? "running" : (resolved.partner._lastTurnInterrupted ? "interrupted" : "idle"),
264
271
  partnerId: resolved.partner.localId,
265
272
  title: resolved.partner.title || "New Session",
266
273
  turns: recentTurns(resolved.partner, count),
@@ -383,7 +390,7 @@ function attachSessionPair(ctx) {
383
390
  var group = store.groupForMember(session.localId);
384
391
  var pairPrompt = "";
385
392
  if (group && group.pair && group.pair.driverId === session.localId) {
386
- pairPrompt = "You are the Driver in a two-agent pair. The tools send_to_partner and read_partner are provided directly to you. Use send_to_partner to delegate concrete bounded work to the Worker. A completed Worker turn leaves the Worker session available for more work. If review or user feedback requires corrections to the Worker's implementation, delegate a follow-up turn to that Worker instead of editing the Worker-owned files yourself. If send_to_partner reports that the Worker is no longer available, create a replacement with spawn_sessions and delegate the remaining implementation rather than taking it over. If a non-waiting delegation finishes later, Clay pushes the result back and resumes you automatically; use read_partner only when you need an interim status check. Integrate and verify the final outcome. Do not search the project for implementations of these tools, and do not delegate work that must be performed sequentially in this same session.";
393
+ pairPrompt = "You are the Driver in a two-agent pair. The tools send_to_partner and read_partner are provided directly to you. Use send_to_partner to delegate concrete bounded work to the Worker. A completed Worker turn leaves the Worker session available for more work. If a Worker turn is interrupted, its work is partial and unverified; review it and decide next steps with the user. If review or user feedback requires corrections to the Worker's implementation, delegate a follow-up turn to that Worker instead of editing the Worker-owned files yourself. If send_to_partner reports that the Worker is no longer available, create a replacement with spawn_sessions and delegate the remaining implementation rather than taking it over. If a non-waiting delegation finishes later, Clay pushes the result back and resumes you automatically; use read_partner only when you need an interim status check. Integrate and verify the final outcome. Do not search the project for implementations of these tools, and do not delegate work that must be performed sequentially in this same session.";
387
394
  }
388
395
  return [pairPrompt, workerProposal.getSystemPrompt(session)].filter(Boolean).join("\n\n");
389
396
  }
@@ -319,7 +319,7 @@ function attachSessionSpawn(ctx) {
319
319
  statuses.push({
320
320
  localId: session.localId,
321
321
  title: session.title || "New Session",
322
- status: session.isProcessing ? "running" : (hasSessionError(session) ? "error" : "done"),
322
+ status: session.isProcessing ? "running" : (session._lastTurnInterrupted ? "interrupted" : (hasSessionError(session) ? "error" : "done")),
323
323
  turnCount: session.turnCount || 0,
324
324
  lastActivity: session.lastActivity || session.createdAt || 0,
325
325
  });
@@ -248,6 +248,8 @@ function attachWorkerProposal(ctx) {
248
248
  var followup;
249
249
  if (result.status === "complete") {
250
250
  followup = "[Worker execution completed]\nReview and verify the Worker's result. The Worker session remains available: if the implementation needs corrections or additional edits, send a follow-up with send_to_partner instead of taking over the Worker-owned files yourself. If that Worker is no longer available, create a replacement with spawn_sessions for the remaining implementation.\n\n" + (result.response || "The Worker completed without a text summary.");
251
+ } else if (result.status === "interrupted") {
252
+ followup = "[Worker execution interrupted]\nThe user interrupted the Worker mid-turn. Its work is PARTIAL and unverified — do not treat it as finished. Review what was done and decide next steps with the user.";
251
253
  } else if (result.status === "running") {
252
254
  followup = "[Worker execution is still running]\nUse read_partner to inspect progress before completing the task.";
253
255
  } else {
@@ -95,6 +95,7 @@ function statusLabel(status) {
95
95
  if (status === "starting") return "Starting Worker";
96
96
  if (status === "running") return "Worker running";
97
97
  if (status === "completed") return "Completed";
98
+ if (status === "interrupted") return "Interrupted";
98
99
  if (status === "declined") return "Continuing here";
99
100
  if (status === "error") return "Needs attention";
100
101
  return "Suggested by Fable";
package/lib/sdk-bridge.js CHANGED
@@ -885,6 +885,7 @@ function createSDKBridge(opts) {
885
885
  console.log("[sdk-bridge] processQueryStream ended: isProcessing=" + session.isProcessing + " taskStopRequested=" + session.taskStopRequested);
886
886
  var stillOwnsRuntime = session.queryInstance === myQueryInstance;
887
887
  if (session.isProcessing && session.taskStopRequested && stillOwnsRuntime) {
888
+ session._lastTurnInterrupted = true;
888
889
  session.isProcessing = false;
889
890
  session._awaitingTurnResult = false;
890
891
  session._queuedTurnCount = 0;
@@ -922,6 +923,7 @@ function createSDKBridge(opts) {
922
923
  session._queuedTurnCount = 0;
923
924
  onProcessingChanged();
924
925
  if (err.name === "AbortError" || (myAbortController && myAbortController.signal.aborted) || session.taskStopRequested) {
926
+ session._lastTurnInterrupted = true;
925
927
  if (!session.destroying) {
926
928
  sendAndRecord(session, { type: "thinking_stop" });
927
929
  var interruptMsg2 = (session.vendor && session.vendor !== "claude")
@@ -1189,6 +1191,7 @@ function createSDKBridge(opts) {
1189
1191
  // Wrapper: marks the boot window so pushMessage can buffer instead of
1190
1192
  // dropping messages that arrive while createQuery is still awaiting.
1191
1193
  async function startQuery(session, text, images, linuxUser) {
1194
+ delete session._lastTurnInterrupted;
1192
1195
  session._queryStarting = true;
1193
1196
  try {
1194
1197
  return await startQueryInner(session, text, images, linuxUser);
@@ -1707,6 +1710,7 @@ function createSDKBridge(opts) {
1707
1710
  console.error("[sdk-bridge] QueryHandle rejected message for session " + session.localId + ":", e.message || e);
1708
1711
  }
1709
1712
  if (delivered) {
1713
+ delete session._lastTurnInterrupted;
1710
1714
  if (session._awaitingTurnResult) {
1711
1715
  session._queuedTurnCount = (session._queuedTurnCount || 0) + 1;
1712
1716
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "3.4.0-beta.20",
3
+ "version": "3.4.0-beta.21",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",