clay-server 5.2.0-beta.5 → 5.2.0-beta.6

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 (151) hide show
  1. package/lib/claude-hook-installer.js +1 -267
  2. package/lib/claude-jsonl-watcher.js +1 -204
  3. package/lib/clay-skill-install.js +1 -33
  4. package/lib/cli-sessions.js +1 -300
  5. package/lib/codex-defaults.js +1 -28
  6. package/lib/context-usage.js +3 -2
  7. package/lib/daemon.js +5 -55
  8. package/lib/default-ai-runtime.js +4 -8
  9. package/lib/email-mcp-server.js +1 -1
  10. package/lib/kiro-defaults.js +1 -22
  11. package/lib/mcp-proxy.js +2 -32
  12. package/lib/mcp-query-bridge.js +1 -1
  13. package/lib/mcp-source-adapters.js +1 -91
  14. package/lib/mcp-source-context.js +1 -42
  15. package/lib/mcp-source-discovery.js +1 -169
  16. package/lib/mcp-source-plugins.js +1 -68
  17. package/lib/mcp-source-restrictions.js +1 -47
  18. package/lib/mcp-tool-policy.js +1 -26
  19. package/lib/os-user-diagnostics.js +2 -4
  20. package/lib/os-users.js +4 -65
  21. package/lib/project-connection.js +8 -7
  22. package/lib/project-http.js +4 -4
  23. package/lib/project-mcp.js +2 -2
  24. package/lib/project-pair-result-delivery.js +2 -2
  25. package/lib/project-pair-usage.js +12 -13
  26. package/lib/project-pending-message-queue.js +3 -1
  27. package/lib/project-session-history-actions.js +5 -3
  28. package/lib/project-session-pair.js +1 -1
  29. package/lib/project-session-runtime-actions.js +2 -1
  30. package/lib/project-session-spawn.js +2 -3
  31. package/lib/project-session-vendor.js +2 -1
  32. package/lib/project-sessions.js +18 -26
  33. package/lib/project-user-message.js +1 -0
  34. package/lib/project-vendor-login.js +6 -36
  35. package/lib/public/app.js +1 -0
  36. package/lib/public/css/messages.css +6 -2
  37. package/lib/public/modules/app-favicon.js +11 -2
  38. package/lib/public/modules/app-header.js +4 -2
  39. package/lib/public/modules/app-loop-ui.js +2 -0
  40. package/lib/public/modules/app-messages.js +36 -5
  41. package/lib/public/modules/app-notifications.js +1 -2
  42. package/lib/public/modules/app-panels.js +61 -123
  43. package/lib/public/modules/app-rate-limit.js +7 -27
  44. package/lib/public/modules/chat-render-runtime.js +3 -0
  45. package/lib/public/modules/mcp-connection-view.js +1 -1
  46. package/lib/public/modules/permission-control.js +5 -4
  47. package/lib/public/modules/scheduler.js +2 -0
  48. package/lib/public/modules/session-create-form.js +10 -10
  49. package/lib/public/modules/settings-defaults.js +27 -28
  50. package/lib/public/modules/tools.js +2 -1
  51. package/lib/public/modules/tui-grab.js +1 -2
  52. package/lib/public/modules/vendor-login-browser.js +5 -3
  53. package/lib/public/modules/vendor-login.js +2 -2
  54. package/lib/public/modules/vendor-priority.js +15 -1
  55. package/lib/public/muse-avatar.svg +4 -0
  56. package/lib/safe-bash-commands.js +3 -45
  57. package/lib/sdk-bridge.js +127 -395
  58. package/lib/sdk-foreground-admission.js +16 -0
  59. package/lib/sdk-mcp-descriptors.js +2 -37
  60. package/lib/sdk-message-processor.js +50 -17
  61. package/lib/sdk-quota-snapshot.js +1 -1
  62. package/lib/sdk-response-recovery.js +1 -155
  63. package/lib/sdk-worker.js +2 -677
  64. package/lib/server.js +1 -1
  65. package/lib/session-fork-history.js +21 -0
  66. package/lib/session-hygiene.js +3 -35
  67. package/lib/session-permission-mode.js +4 -3
  68. package/lib/session-skill-scope.js +11 -0
  69. package/lib/sessions.js +17 -89
  70. package/lib/tool-llm.js +1 -1
  71. package/lib/tui-transcript-index.js +1 -132
  72. package/lib/vendor-login-browser.js +1 -11
  73. package/lib/yoke/acp-agent-profiles.js +5 -3
  74. package/lib/yoke/adapters/antigravity.js +3 -14
  75. package/lib/yoke/adapters/claude-query-contract.js +9 -0
  76. package/lib/yoke/adapters/claude.js +7 -42
  77. package/lib/yoke/adapters/codex-query-contract.js +24 -0
  78. package/lib/yoke/adapters/codex.js +1 -0
  79. package/lib/yoke/adapters/kiro.js +3 -1
  80. package/lib/yoke/adapters/muse-query-contract.js +7 -0
  81. package/lib/yoke/adapters/muse.js +122 -0
  82. package/lib/yoke/auth-events.js +17 -0
  83. package/lib/yoke/claude-hook-installer.js +267 -0
  84. package/lib/yoke/claude-jsonl-watcher.js +204 -0
  85. package/lib/yoke/claude-maintenance.js +55 -0
  86. package/lib/yoke/claude-permission-patterns.js +53 -0
  87. package/lib/yoke/claude-response-recovery.js +155 -0
  88. package/lib/yoke/claude-transcript-hygiene.js +34 -0
  89. package/lib/yoke/clay-skill-install.js +33 -0
  90. package/lib/yoke/cli-sessions.js +300 -0
  91. package/lib/yoke/codex-app-server.js +7 -12
  92. package/lib/yoke/configuration.js +13 -0
  93. package/lib/yoke/discovery.js +70 -0
  94. package/lib/yoke/errors.js +8 -0
  95. package/lib/yoke/host-environment.js +7 -0
  96. package/lib/yoke/index.js +9 -51
  97. package/lib/yoke/interface.js +116 -0
  98. package/lib/yoke/kiro-acp-server.js +5 -5
  99. package/lib/yoke/legacy-sessions.js +89 -0
  100. package/lib/yoke/login-protocol.js +25 -0
  101. package/lib/yoke/maintenance.js +12 -0
  102. package/lib/yoke/mcp-proxy.js +32 -0
  103. package/lib/yoke/mcp-source-adapters.js +91 -0
  104. package/lib/yoke/mcp-source-context.js +42 -0
  105. package/lib/yoke/mcp-source-discovery.js +169 -0
  106. package/lib/yoke/mcp-source-plugins.js +68 -0
  107. package/lib/yoke/mcp-source-restrictions.js +47 -0
  108. package/lib/yoke/mcp-tool-policy-normalize.js +28 -0
  109. package/lib/yoke/model-preferences.js +6 -0
  110. package/lib/yoke/muse-artifacts.js +36 -0
  111. package/lib/yoke/muse-controls.js +50 -0
  112. package/lib/yoke/muse-input.js +64 -0
  113. package/lib/yoke/muse-items.js +157 -0
  114. package/lib/yoke/muse-policy.js +52 -0
  115. package/lib/yoke/muse-query.js +428 -0
  116. package/lib/yoke/muse-runtime.js +211 -0
  117. package/lib/yoke/muse-services.js +79 -0
  118. package/lib/yoke/muse-skills.js +79 -0
  119. package/lib/yoke/muse-turns.js +71 -0
  120. package/lib/yoke/options/acp.js +2 -0
  121. package/lib/yoke/options/antigravity.js +2 -0
  122. package/lib/yoke/options/claude.js +21 -0
  123. package/lib/yoke/options/codex-defaults.js +29 -0
  124. package/lib/yoke/options/codex-settings.js +21 -0
  125. package/lib/yoke/options/codex.js +6 -0
  126. package/lib/yoke/options/kiro-defaults.js +22 -0
  127. package/lib/yoke/options/kiro.js +5 -0
  128. package/lib/yoke/options/muse.js +5 -0
  129. package/lib/yoke/provider-contract.js +5 -0
  130. package/lib/yoke/providers/antigravity-executable.js +8 -0
  131. package/lib/yoke/providers/claude-executable.js +40 -0
  132. package/lib/yoke/providers/claude-model-descriptions.js +14 -0
  133. package/lib/yoke/providers/claude-process.js +97 -0
  134. package/lib/yoke/providers/claude-provision.js +75 -0
  135. package/lib/yoke/providers/codex-errors.js +8 -0
  136. package/lib/yoke/providers/muse-executable.js +17 -0
  137. package/lib/yoke/provisioning.js +6 -0
  138. package/lib/yoke/query-options.js +10 -0
  139. package/lib/yoke/response-recovery.js +41 -0
  140. package/lib/yoke/sdk-mcp-descriptors.js +37 -0
  141. package/lib/yoke/sdk-server-factory.js +7 -0
  142. package/lib/yoke/session-history.js +8 -0
  143. package/lib/yoke/session-tool-transport.js +31 -13
  144. package/lib/yoke/settings.js +23 -0
  145. package/lib/yoke/source-locations.js +4 -0
  146. package/lib/yoke/title-query.js +18 -7
  147. package/lib/yoke/tui-transcript-index.js +132 -0
  148. package/lib/yoke/usage.js +48 -0
  149. package/lib/yoke/user-input.js +13 -0
  150. package/lib/yoke/vendor-registry.js +81 -1
  151. package/package.json +3 -2
@@ -0,0 +1,16 @@
1
+ // One next-message admission window, bound to the exact live provider query.
2
+ // This is not turn completion: processing and pending terminal counts survive.
3
+ function available(session) {
4
+ var credit = session && session._foregroundAdmission;
5
+ return !!(credit && session.queryInstance === credit.query
6
+ && Number(session._sdkQueryGeneration || 0) === credit.generation
7
+ && !session.taskStopRequested && !session._pendingMessageDrainPaused
8
+ && !(session.abortController && session.abortController.signal.aborted));
9
+ }
10
+ function offer(session) {
11
+ if (!session.queryInstance || session.taskStopRequested || session.abortController && session.abortController.signal.aborted) return false;
12
+ session._foregroundAdmission = { query: session.queryInstance, generation: Number(session._sdkQueryGeneration || 0) };
13
+ return true;
14
+ }
15
+ function take(session) { delete session._foregroundAdmission; }
16
+ module.exports = { available: available, offer: offer, take: take };
@@ -1,37 +1,2 @@
1
- // Extract serializable tool descriptors from MCP server instances.
2
- // Used for IPC to worker processes (McpSdkServerConfigWithInstance is not serializable).
3
- function extractMcpDescriptors(mcpServers) {
4
- if (!mcpServers) return null;
5
- // Claude's adapter uses Zod v3 for JSON Schema tools; other servers use v4.
6
- // Use the MCP SDK's version-aware converter for both kinds of registered tool.
7
- var toJSONSchema = require("@modelcontextprotocol/sdk/server/zod-json-schema-compat.js").toJsonSchemaCompat;
8
- var descriptors = [];
9
- var names = Object.keys(mcpServers);
10
- for (var i = 0; i < names.length; i++) {
11
- var serverName = names[i];
12
- var server = mcpServers[serverName];
13
- if (!server || !server.instance || !server.instance._registeredTools) continue;
14
- var tools = [];
15
- var toolNames = Object.keys(server.instance._registeredTools);
16
- for (var j = 0; j < toolNames.length; j++) {
17
- var toolName = toolNames[j];
18
- var toolDef = server.instance._registeredTools[toolName];
19
- var inputSchema = { type: "object", properties: {} };
20
- try {
21
- if (toolDef._clayInputSchema) inputSchema = toolDef._clayInputSchema;
22
- else if (toolDef.inputSchema) inputSchema = toJSONSchema(toolDef.inputSchema);
23
- } catch (e) {
24
- throw new Error("Cannot serialize MCP tool schema " + serverName + "/" + toolName + ": " + e.message);
25
- }
26
- tools.push({
27
- name: toolName,
28
- description: toolDef.description || toolName,
29
- inputSchema: inputSchema,
30
- });
31
- }
32
- if (tools.length > 0) descriptors.push({ serverName: serverName, tools: tools });
33
- }
34
- return descriptors.length > 0 ? descriptors : null;
35
- }
36
-
37
- module.exports = { extractMcpDescriptors: extractMcpDescriptors };
1
+ // Compatibility export; SDK serialization is owned by YOKE.
2
+ module.exports = require("./yoke/sdk-mcp-descriptors");
@@ -42,9 +42,20 @@ function attachMessageProcessor(ctx) {
42
42
  }
43
43
 
44
44
  function sendAndRecord(session, obj, clientObj) {
45
+ obj = require("./yoke/usage").clientEvent(obj, session.vendor || "claude");
45
46
  sm.sendAndRecord(session, obj, clientObj);
46
47
  }
47
48
 
49
+ function recordTurnActivity(session, activity) {
50
+ // An authoritative input card owns progress until its request settles.
51
+ var awaitingInput = ["pendingPermissions", "pendingAskUser", "pendingElicitations", "pendingUserDialogs"].some(function (key) {
52
+ return Object.keys(session[key] || {}).length > 0;
53
+ });
54
+ if (!awaitingInput && ["waiting", "running", "background"].indexOf(activity.phase) !== -1) sendAndRecord(session, {
55
+ type: "turn_activity", phase: activity.phase, turnId: activity.turnId || null, disposition: activity.disposition || null
56
+ });
57
+ }
58
+
48
59
  function parseGeneratedImage(parsed) {
49
60
  var data = parsed.data || "";
50
61
  var mediaType = parsed.mediaType || "image/png";
@@ -77,8 +88,8 @@ function attachMessageProcessor(ctx) {
77
88
  || !!authLinuxUser
78
89
  || (authUser && authUser.role === "admin");
79
90
  var vendorInfo = yoke.getVendorInfo(session.vendor);
80
- var authTitle = ((vendorInfo && vendorInfo.displayName) || "Claude Code") + " is not logged in.";
81
- var loginCommand = (vendorInfo && vendorInfo.loginCommand) || "claude login";
91
+ var authTitle = ((vendorInfo && vendorInfo.displayName) || "The provider") + " is not logged in.";
92
+ var loginCommand = (vendorInfo && vendorInfo.loginCommand) || "";
82
93
  var _nmLogin = getNotificationsModule();
83
94
  // Authentication recovery is transient UI state, not transcript content.
84
95
  // Recording it makes history replay look like a fresh auth failure and
@@ -163,6 +174,7 @@ function attachMessageProcessor(ctx) {
163
174
  }
164
175
 
165
176
  function processSDKMessage(session, parsed) {
177
+ parsed = require("./yoke/auth-events").normalize(parsed, session.responsePreview, session.vendor || "claude");
166
178
  // Timing: log key SDK milestones relative to query start
167
179
  if (session._queryStartTs) {
168
180
  var _elapsed = Date.now() - session._queryStartTs;
@@ -378,7 +390,7 @@ function attachMessageProcessor(ctx) {
378
390
  }) : [];
379
391
  sendAndRecord(session, {
380
392
  type: "tool_executing",
381
- id: parsed.turnId || "codex-plan",
393
+ id: parsed.turnId || "provider-plan",
382
394
  name: "TodoWrite",
383
395
  input: {
384
396
  todos: todos,
@@ -546,7 +558,32 @@ function attachMessageProcessor(ctx) {
546
558
  }
547
559
  }
548
560
 
561
+ } else if (parsed.yokeType === "turn_activity") {
562
+ recordTurnActivity(session, parsed);
563
+ } else if (parsed.yokeType === "foreground_available") {
564
+ // A provider observed an admission boundary while background work still
565
+ // owns its terminal. Do not synthesize success or clear processing/tasks.
566
+ // Persist the presentation boundary before a synchronous next admission.
567
+ sendAndRecord(session, { type: "assistant_boundary" });
568
+ recordTurnActivity(session, { phase: "background" });
569
+ if (require('./sdk-foreground-admission').offer(session) && opts && typeof opts.onSessionAvailable === 'function') opts.onSessionAvailable(session);
549
570
  } else if (parsed.yokeType === "result") {
571
+ if (parsed.backgroundOnly === true) {
572
+ // Observed terminal of an older foreground turn. Account for the
573
+ // outstanding send without settling a newer turn's reviews/callbacks.
574
+ session._queuedTurnCount = turnQueue.pendingSends(session, parsed);
575
+ if (session._queuedTurnCount > 0) session._queuedTurnCount--;
576
+ else {
577
+ session.isProcessing = false;
578
+ session._awaitingTurnResult = false;
579
+ onProcessingChanged();
580
+ sendAndRecord(session, { type: "done", code: 0 });
581
+ }
582
+ sendAndRecord(session, { type: "background_result", usage: require('./yoke/usage').accounting(parsed.usage), cost: parsed.cost, duration: parsed.duration });
583
+ sm.broadcastSessionList();
584
+ if (opts && typeof opts.onSessionAvailable === 'function') opts.onSessionAvailable(session);
585
+ return;
586
+ }
550
587
  // SDK 0.3.274 batches queued background-task completion wakes into one
551
588
  // model call. Every wake still emits a result, but all results before
552
589
  // the final wake are successful task-notification-origin placeholders
@@ -616,7 +653,7 @@ function attachMessageProcessor(ctx) {
616
653
  session._queuedTurnCount = 0;
617
654
  taskLifecycle.clearAll(session);
618
655
  onProcessingChanged();
619
- sendAndRecord(session, { type: "error", text: "Claude failed to start: " + parsed.startupFailureReason });
656
+ sendAndRecord(session, { type: "error", text: "The provider failed to start: " + parsed.startupFailureReason });
620
657
  sendAndRecord(session, { type: "done", code: 1 });
621
658
  sm.broadcastSessionList();
622
659
  return;
@@ -657,12 +694,6 @@ function attachMessageProcessor(ctx) {
657
694
  onProcessingChanged();
658
695
  }
659
696
  // Recognize only short, zero-cost CLI login responses, not ordinary prose.
660
- var previewTrimmed = (session.responsePreview || "").trim();
661
- var isZeroCost = !parsed.cost || parsed.cost === 0;
662
- var isLoginPrompt = isZeroCost && previewTrimmed.length < 100
663
- && ((/not logged in/i.test(previewTrimmed) && /\/login/i.test(previewTrimmed))
664
- || ((session.vendor || "claude") === "claude"
665
- && /^Failed to authenticate: OAuth session expired and could not be refreshed\.?$/i.test(previewTrimmed)));
666
697
  // Fetch rich context usage breakdown (fire-and-forget, non-blocking)
667
698
  if (session.queryInstance && typeof session.queryInstance.getContextUsage === "function") {
668
699
  var contextQuery = session.queryInstance;
@@ -684,7 +715,8 @@ function attachMessageProcessor(ctx) {
684
715
  type: "result",
685
716
  cost: parsed.cost,
686
717
  duration: parsed.duration,
687
- usage: parsed.usage || null,
718
+ usage: require("./yoke/usage").accounting(parsed.usage),
719
+ context: require("./yoke/usage").turnContext(Object.assign({}, parsed, { lastStreamInputTokens: lastStreamInput })),
688
720
  modelUsage: parsed.modelUsage || null,
689
721
  sessionId: parsed.sessionId,
690
722
  lastStreamInputTokens: lastStreamInput,
@@ -699,7 +731,7 @@ function attachMessageProcessor(ctx) {
699
731
  }
700
732
  // Recover both missing login and expired OAuth sessions from the SDK.
701
733
  // This is a short canned response with zero cost, not actual AI output.
702
- if (isLoginPrompt) {
734
+ if (parsed.authenticationRequired === true) {
703
735
  emitAuthRequired(session);
704
736
  }
705
737
  sendAndRecord(session, { type: "done", code: 0 });
@@ -887,7 +919,7 @@ function attachMessageProcessor(ctx) {
887
919
  resetsAt: info.resetsAt ? info.resetsAt * 1000 : null,
888
920
  rateLimitType: info.rateLimitType || null,
889
921
  utilization: info.utilization || null,
890
- isUsingOverage: info.isUsingOverage === true || info.overageInUse === true,
922
+ isUsingOverage: info.isUsingOverage === true,
891
923
  overageEnabled: typeof info.overageEnabled === "boolean" ? info.overageEnabled : null,
892
924
  overageStatus: info.overageStatus || null,
893
925
  overageResetsAt: info.overageResetsAt ? info.overageResetsAt * 1000 : null,
@@ -979,6 +1011,7 @@ function attachMessageProcessor(ctx) {
979
1011
  }
980
1012
 
981
1013
  } else if (parsed.yokeType === "commands_changed") {
1014
+ if (Array.isArray(parsed.skills)) session.skillNames = parsed.skills.slice();
982
1015
  // Mid-session command list change: rebuild the union with skills (same as
983
1016
  // init) and push a full replacement to the client.
984
1017
  var cmdSeen = new Set();
@@ -1053,12 +1086,12 @@ function attachMessageProcessor(ctx) {
1053
1086
  // processQueryStream can finish the turn when the iterator closes.
1054
1087
  var adapterErrorText = parsed.text || parsed.message || parsed.error || "Agent runtime error";
1055
1088
  session._lastAdapterError = adapterErrorText;
1056
- var isSessionWriterConflict = /thread-store conflict|already has an active writer/i.test(adapterErrorText);
1057
- if (isSessionWriterConflict) {
1089
+ var classifiedError = require("./yoke/errors").classify(session.vendor || "claude", adapterErrorText);
1090
+ if (classifiedError && classifiedError.kind === "session_writer_conflict") {
1058
1091
  sendAndRecord(session, {
1059
1092
  type: "session_writer_conflict",
1060
- vendor: session.vendor || "codex",
1061
- text: "This Codex session is already open in another Clay or Codex process. Stop the other server or close the other session, then try again.",
1093
+ vendor: session.vendor || "claude",
1094
+ text: classifiedError.text,
1062
1095
  });
1063
1096
  } else {
1064
1097
  sendAndRecord(session, { type: "error", text: adapterErrorText });
@@ -9,6 +9,7 @@ function recordQuotaSnapshot(session, info, sendAndRecord) {
9
9
  return Object.assign({}, window, { resetsAt: window.resetsAt ? window.resetsAt * 1000 : null });
10
10
  }),
11
11
  };
12
+ if (Number.isFinite(info.observedAtMs)) message.observedAtMs = info.observedAtMs;
12
13
  var snapshots = session._quotaSnapshots || (session._quotaSnapshots = Object.create(null));
13
14
  var key = JSON.stringify([session.ownerId, session.vendor, info.limitId]);
14
15
  var serialized = JSON.stringify(message);
@@ -20,7 +21,6 @@ function recordQuotaSnapshot(session, info, sendAndRecord) {
20
21
  function attachQuotaSnapshots(sm) {
21
22
  if (typeof sm.addOnSessionViewed !== "function") return;
22
23
  sm.addOnSessionViewed(function (session, ws, send) {
23
- if (session.vendor !== "codex") return;
24
24
  var seen = Object.create(null);
25
25
  var history = session.history || [];
26
26
  // A deduplicated snapshot may precede the initial page of chat history.
@@ -1,155 +1 @@
1
- // Claude delivery watchdog. Only an unanswered foreground turn is replayable.
2
- function attachResponseRecovery(ctx) {
3
- var states = new Map();
4
- var schedule = ctx.setTimeout || setTimeout;
5
- var unschedule = ctx.clearTimeout || clearTimeout;
6
- var waitMs = ctx.waitMs || 180000;
7
- var stopMs = ctx.stopMs || 10000;
8
-
9
- function live(session, state) {
10
- return states.get(session) === state && !session.destroying && !session.taskStopRequested
11
- && state.ownerId === session.ownerId && state.vendor === session.vendor
12
- && (!ctx.sessions || ctx.sessions.get(session.localId) === session);
13
- }
14
- function clearTimer(state) {
15
- if (state.timer) unschedule(state.timer);
16
- state.timer = null;
17
- }
18
- function cancel(session, expected) {
19
- var state = states.get(session);
20
- if (expected && state !== expected) return;
21
- if (state) clearTimer(state);
22
- states.delete(session);
23
- }
24
- function protectedWait(session) {
25
- return [session.pendingPermissions, session.pendingAskUser, session.pendingElicitations,
26
- session.pendingUserDialogs, session.activeTaskToolIds].some(function(map) {
27
- return map && Object.keys(map).length > 0;
28
- }) || (session.activeBackgroundTasks && session.activeBackgroundTasks.length > 0);
29
- }
30
- function arm(session, state) {
31
- clearTimer(state);
32
- if (!state.turns.length || state.turns[0].progress || state.stopping) return;
33
- state.timer = schedule(function() { expire(session, state); }, waitMs);
34
- if (state.timer && state.timer.unref) state.timer.unref();
35
- }
36
- function snapshot(payload) {
37
- return Object.assign({}, payload, { images: payload.images ? JSON.parse(JSON.stringify(payload.images)) : payload.images });
38
- }
39
- function begin(session, payload, retryState) {
40
- if (retryState) return retryState;
41
- cancel(session);
42
- var state = { turns: [snapshot(payload)], timer: null, stopping: false, retried: false, ownerId: session.ownerId, vendor: session.vendor };
43
- states.set(session, state);
44
- arm(session, state);
45
- return state;
46
- }
47
- function enqueue(session, payload) {
48
- var state = states.get(session);
49
- if (!state) return begin(session, payload);
50
- state.turns.push(snapshot(payload));
51
- return state;
52
- }
53
- function progress(session) {
54
- var state = states.get(session);
55
- if (!state || state.stopping || !state.turns.length) return;
56
- state.turns[0].progress = true;
57
- clearTimer(state);
58
- }
59
- function event(session, msg) {
60
- var state = states.get(session);
61
- if (!state || state.stopping || !msg) return;
62
- var type = msg.yokeType;
63
- if (type === 'error' || type === 'interrupted') { cancel(session); return; }
64
- if (type === 'result') {
65
- if (msg.subtype === 'success' && !msg.is_error && msg.numTurns === 0
66
- && msg.origin && msg.origin.kind === 'task-notification'
67
- && !msg.localCommand && msg.resultText === '') return;
68
- var remaining = require('./sdk-turn-queue').pendingSends(session, msg);
69
- state.turns = remaining ? state.turns.slice(-remaining) : [];
70
- state.retried = false;
71
- arm(session, state);
72
- if (!state.turns.length) cancel(session);
73
- } else if ((type === 'text_delta' || type === 'thinking_delta') && (msg.text || msg.delta)
74
- || type === 'tool_start' || type === 'tool_input_delta' || type === 'tool_progress'
75
- || type === 'task_started' || type === 'task_progress' || type === 'subagent_message'
76
- || type === 'message' && msg.messageRole === 'assistant' && msg.content && msg.content.length) {
77
- progress(session);
78
- }
79
- }
80
- function finish(session, state, text) {
81
- if (!live(session, state)) { cancel(session, state); return; }
82
- cancel(session);
83
- // Fence late output when a transport cannot confirm termination.
84
- session._sdkQueryGeneration = Number(session._sdkQueryGeneration || 0) + 1;
85
- session.isProcessing = false;
86
- session._awaitingTurnResult = false;
87
- session._queuedTurnCount = 0;
88
- ctx.changed();
89
- ctx.record(session, { type: 'thinking_stop' });
90
- ctx.record(session, { type: 'error', text: text });
91
- ctx.record(session, { type: 'done', code: 1 });
92
- ctx.broadcast();
93
- }
94
- async function expire(session, state) {
95
- clearTimer(state);
96
- if (!live(session, state)) { cancel(session, state); return; }
97
- // Never stop a known tool or a human decision because prose is absent.
98
- if (protectedWait(session)) { progress(session); return; }
99
- state.stopping = true;
100
- ctx.record(session, { type: 'info', text: 'Claude has not responded for three minutes. Stopping this attempt safely before retrying.' });
101
- var timeout;
102
- var stopped = false;
103
- try {
104
- var stopAttempt = (async function() {
105
- var handle = session.queryInstance || state.handle;
106
- if (!handle && session.abortController) session.abortController.abort();
107
- if (!handle && state.starting) await state.starting;
108
- handle = handle || session.queryInstance || state.handle;
109
- if (handle) {
110
- if (typeof handle.stopAndWait !== 'function') return false;
111
- await handle.stopAndWait();
112
- if (session.streamPromise) await session.streamPromise;
113
- }
114
- return true;
115
- })();
116
- session._responseRecoveryStopPending = stopAttempt;
117
- stopAttempt.then(function(confirmed) {
118
- if (confirmed && session._responseRecoveryStopPending === stopAttempt) session._responseRecoveryStopPending = null;
119
- }, function() {});
120
- stopped = await Promise.race([
121
- stopAttempt,
122
- new Promise(function(resolve) { timeout = schedule(function() { resolve(false); }, stopMs); }),
123
- ]);
124
- } catch (e) { stopped = false; }
125
- if (timeout) unschedule(timeout);
126
- if (!live(session, state)) { cancel(session, state); return; }
127
- if (!stopped) {
128
- finish(session, state, 'Claude could not be confirmed stopped. Automatic retry was cancelled to avoid running your request twice. Check your Claude connection before trying again.');
129
- return;
130
- }
131
- if (state.retried || state.turns.length !== 1) {
132
- finish(session, state, state.retried
133
- ? 'Claude still has not responded after one automatic retry. Please send your message again or check your Claude connection.'
134
- : 'Claude did not respond. Several messages were queued, so automatic replay was cancelled to avoid duplicating work. Please send the unanswered messages again.');
135
- return;
136
- }
137
- state.retried = true;
138
- state.stopping = false;
139
- session.isProcessing = true;
140
- ctx.changed();
141
- ctx.record(session, { type: 'info', text: 'The previous attempt has stopped. Retrying your original message once.' });
142
- ctx.send(session, { type: 'status', status: 'processing' });
143
- arm(session, state);
144
- var payload = state.turns[0];
145
- await ctx.restart(session, payload, state);
146
- }
147
- return {
148
- begin: begin, enqueue: enqueue, event: event, progress: progress, cancel: cancel,
149
- current: function(session) { return states.get(session); },
150
- valid: live,
151
- stopping: function(session) { var state = states.get(session); return !!(state && state.stopping); },
152
- dispose: function() { states.forEach(function(state) { clearTimer(state); }); states.clear(); },
153
- };
154
- }
155
- module.exports = { attachResponseRecovery: attachResponseRecovery };
1
+ module.exports = require("./yoke/claude-response-recovery");