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
package/lib/sdk-worker.js CHANGED
@@ -1,677 +1,2 @@
1
- // DEPRECATED: This file has been moved to lib/yoke/adapters/claude-worker.js
2
- // It is kept here temporarily for backward compatibility with running worker processes.
3
- // New worker spawns use the path from adapter.workerScriptPath.
4
- //
5
- // sdk-worker.js — Standalone worker process for OS-level user isolation.
6
- // Runs as a target Linux user, loads the Claude Agent SDK, and communicates
7
- // with the main Clay daemon over a Unix domain socket using JSON lines.
8
- //
9
- // Usage: node sdk-worker.js <socket-path>
10
-
11
- // Force IPv4-only for all child processes (including SDK CLI subprocess).
12
- // Without this, Node 22+ happy eyeballs tries IPv6 first (10s timeout on
13
- // servers without IPv6 outbound), causing massive cold-start delays.
14
- process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || "") + " --dns-result-order=ipv4first --no-network-family-autoselection";
15
-
16
- // Early diagnostic — writes directly to fd 2 to ensure output even if pipes close fast
17
- var _workerBootTs = Date.now();
18
- try { require("fs").writeSync(2, "[sdk-worker] BOOT pid=" + process.pid + " uid=" + (typeof process.getuid === "function" ? process.getuid() : "?") + " argv=" + process.argv.slice(1).join(" ") + " bootTs=" + _workerBootTs + "\n"); } catch (e) {}
19
-
20
- var net = require("net");
21
- var crypto = require("crypto");
22
- var path = require("path");
23
-
24
- var socketPath = process.argv[2];
25
- if (!socketPath) {
26
- console.error("[sdk-worker] Missing socket path argument");
27
- process.exit(1);
28
- }
29
-
30
- // --- State ---
31
- var sdkModule = null;
32
- var queryInstance = null;
33
- var messageQueue = null;
34
- var abortController = null;
35
- var pendingPermissions = {}; // requestId -> resolve
36
- var pendingAskUser = {}; // toolUseId -> resolve
37
- var pendingElicitations = {}; // requestId -> resolve
38
- var pendingMcpCalls = {}; // requestId -> { resolve, reject }
39
- var conn = null;
40
- var buffer = "";
41
-
42
- // --- Message queue (same implementation as sdk-bridge.js) ---
43
- function createMessageQueue() {
44
- var queue = [];
45
- var waiting = null;
46
- var ended = false;
47
- return {
48
- push: function(msg) {
49
- if (waiting) {
50
- var resolve = waiting;
51
- waiting = null;
52
- resolve({ value: msg, done: false });
53
- } else {
54
- queue.push(msg);
55
- }
56
- },
57
- end: function() {
58
- ended = true;
59
- if (waiting) {
60
- var resolve = waiting;
61
- waiting = null;
62
- resolve({ value: undefined, done: true });
63
- }
64
- },
65
- [Symbol.asyncIterator]: function() {
66
- return {
67
- next: function() {
68
- if (queue.length > 0) {
69
- return Promise.resolve({ value: queue.shift(), done: false });
70
- }
71
- if (ended) {
72
- return Promise.resolve({ value: undefined, done: true });
73
- }
74
- return new Promise(function(resolve) {
75
- waiting = resolve;
76
- });
77
- },
78
- };
79
- },
80
- };
81
- }
82
-
83
- // --- SDK loader ---
84
- function getSDK() {
85
- if (!sdkModule) sdkModule = import("@anthropic-ai/claude-agent-sdk");
86
- return sdkModule;
87
- }
88
-
89
- // --- IPC helpers ---
90
- function sendToDaemon(msg) {
91
- if (!conn || conn.destroyed) return;
92
- try {
93
- conn.write(JSON.stringify(msg) + "\n");
94
- } catch (e) {
95
- console.error("[sdk-worker] Failed to send message:", e.message);
96
- }
97
- }
98
-
99
- function handleMessage(msg) {
100
- try { require("fs").writeSync(2, "[sdk-worker] MSG: " + msg.type + "\n"); } catch (e) {}
101
- switch (msg.type) {
102
- case "query_start":
103
- handleQueryStart(msg);
104
- break;
105
- case "push_message":
106
- handlePushMessage(msg);
107
- break;
108
- case "end_messages":
109
- if (messageQueue) messageQueue.end();
110
- break;
111
- case "abort":
112
- if (abortController) abortController.abort();
113
- break;
114
- case "set_model":
115
- handleSetModel(msg);
116
- break;
117
- case "set_effort":
118
- handleSetEffort(msg);
119
- break;
120
- case "set_permission_mode":
121
- handleSetPermissionMode(msg);
122
- break;
123
- case "stop_task":
124
- handleStopTask(msg);
125
- break;
126
- case "permission_response":
127
- handlePermissionResponse(msg);
128
- break;
129
- case "ask_user_response":
130
- handleAskUserResponse(msg);
131
- break;
132
- case "elicitation_response":
133
- handleElicitationResponse(msg);
134
- break;
135
- case "mcp_tool_result":
136
- handleMcpToolResult(msg);
137
- break;
138
- case "warmup":
139
- handleWarmup(msg);
140
- break;
141
- case "shutdown":
142
- gracefulExit(0);
143
- break;
144
- default:
145
- console.error("[sdk-worker] Unknown message type:", msg.type);
146
- }
147
- }
148
-
149
- // --- canUseTool: delegates to daemon via IPC ---
150
- function canUseTool(toolName, input, opts) {
151
- var requestId = crypto.randomUUID();
152
- sendToDaemon({
153
- type: "permission_request",
154
- requestId: requestId,
155
- toolName: toolName,
156
- input: input,
157
- toolUseId: opts.toolUseID || "",
158
- decisionReason: opts.decisionReason || "",
159
- });
160
- return new Promise(function(resolve) {
161
- pendingPermissions[requestId] = resolve;
162
- if (opts.signal) {
163
- opts.signal.addEventListener("abort", function() {
164
- delete pendingPermissions[requestId];
165
- resolve({ behavior: "deny", message: "Cancelled" });
166
- });
167
- }
168
- });
169
- }
170
-
171
- // --- onElicitation: delegates to daemon via IPC ---
172
- function onElicitation(request, opts) {
173
- var requestId = crypto.randomUUID();
174
- sendToDaemon({
175
- type: "elicitation_request",
176
- requestId: requestId,
177
- serverName: request.serverName,
178
- message: request.message,
179
- mode: request.mode || "form",
180
- url: request.url || null,
181
- elicitationId: request.elicitationId || null,
182
- requestedSchema: request.requestedSchema || null,
183
- });
184
- return new Promise(function(resolve) {
185
- pendingElicitations[requestId] = resolve;
186
- if (opts.signal) {
187
- opts.signal.addEventListener("abort", function() {
188
- delete pendingElicitations[requestId];
189
- resolve({ action: "cancel" });
190
- });
191
- }
192
- });
193
- }
194
-
195
- function handlePermissionResponse(msg) {
196
- var resolve = pendingPermissions[msg.requestId];
197
- if (resolve) {
198
- delete pendingPermissions[msg.requestId];
199
- resolve(msg.result);
200
- }
201
- }
202
-
203
- function handleAskUserResponse(msg) {
204
- var resolve = pendingAskUser[msg.toolUseId];
205
- if (resolve) {
206
- delete pendingAskUser[msg.toolUseId];
207
- resolve(msg.result);
208
- }
209
- }
210
-
211
- function handleElicitationResponse(msg) {
212
- var resolve = pendingElicitations[msg.requestId];
213
- if (resolve) {
214
- delete pendingElicitations[msg.requestId];
215
- resolve(msg.result);
216
- }
217
- }
218
-
219
- function handleMcpToolResult(msg) {
220
- var pending = pendingMcpCalls[msg.requestId];
221
- if (!pending) return;
222
- delete pendingMcpCalls[msg.requestId];
223
- if (msg.error) {
224
- pending.reject(new Error(msg.error));
225
- } else {
226
- pending.resolve(msg.result);
227
- }
228
- }
229
-
230
- // Reconstruct MCP servers from serializable descriptors with IPC-proxied handlers.
231
- // Each tool call is forwarded to the daemon which has the real MCP server instances.
232
- function buildMcpServersFromDescriptors(sdk, descriptors) {
233
- if (!descriptors || descriptors.length === 0) return null;
234
- var z;
235
- try { z = require("zod").z; } catch (e) {
236
- try { z = require("zod"); } catch (e2) {
237
- console.error("[sdk-worker] Failed to load zod for MCP reconstruction:", e2.message);
238
- return null;
239
- }
240
- }
241
- var createSdkMcpServer = sdk.createSdkMcpServer;
242
- var toolFn = sdk.tool;
243
- if (!createSdkMcpServer || !toolFn) {
244
- console.error("[sdk-worker] SDK missing createSdkMcpServer or tool helper");
245
- return null;
246
- }
247
-
248
- var servers = {};
249
- for (var i = 0; i < descriptors.length; i++) {
250
- var desc = descriptors[i];
251
- var tools = [];
252
- for (var j = 0; j < desc.tools.length; j++) {
253
- var td = desc.tools[j];
254
- var shape = buildZodShape(z, td.inputSchema);
255
- tools.push(toolFn(
256
- td.name,
257
- td.description || td.name,
258
- shape,
259
- createMcpProxyHandler(desc.serverName, td.name)
260
- ));
261
- }
262
- if (tools.length > 0) {
263
- servers[desc.serverName] = createSdkMcpServer({
264
- name: desc.serverName,
265
- version: "1.0.0",
266
- tools: tools,
267
- });
268
- }
269
- }
270
- return Object.keys(servers).length > 0 ? servers : null;
271
- }
272
-
273
- function createMcpProxyHandler(serverName, toolName) {
274
- return function(args) {
275
- return new Promise(function(resolve, reject) {
276
- var requestId = "mcp_" + Date.now() + "_" + crypto.randomUUID().slice(0, 8);
277
- pendingMcpCalls[requestId] = { resolve: resolve, reject: reject };
278
- sendToDaemon({
279
- type: "mcp_tool_call",
280
- requestId: requestId,
281
- serverName: serverName,
282
- toolName: toolName,
283
- args: args,
284
- });
285
- });
286
- };
287
- }
288
-
289
- // Build a Zod shape from MCP JSON Schema inputSchema (mirrors project-mcp.js logic)
290
- function buildZodShape(z, inputSchema) {
291
- if (!inputSchema || !inputSchema.properties) return {};
292
- var shape = {};
293
- var props = inputSchema.properties;
294
- var required = inputSchema.required || [];
295
- var keys = Object.keys(props);
296
- for (var i = 0; i < keys.length; i++) {
297
- var k = keys[i];
298
- var p = props[k];
299
- var field;
300
- if (p.type === "number" || p.type === "integer") {
301
- field = z.number();
302
- } else if (p.type === "boolean") {
303
- field = z.boolean();
304
- } else if (p.type === "array") {
305
- field = z.array(z.any());
306
- } else if (p.type === "object") {
307
- field = z.record(z.any());
308
- } else if (p.enum) {
309
- field = z.enum(p.enum);
310
- } else {
311
- field = z.string();
312
- }
313
- if (p.description) field = field.describe(p.description);
314
- if (required.indexOf(k) === -1) field = field.optional();
315
- shape[k] = field;
316
- }
317
- return shape;
318
- }
319
-
320
- // --- Query handling ---
321
- async function handleQueryStart(msg) {
322
- var t0 = msg._perfT0 || Date.now();
323
- var localT0 = Date.now();
324
- function perf(label) { console.log("[PERF] sdk-worker: " + label + " +" + (Date.now() - t0) + "ms (local +" + (Date.now() - localT0) + "ms)"); }
325
- perf("handleQueryStart entered");
326
-
327
- var sdk;
328
- try {
329
- perf("loading SDK");
330
- sdk = await getSDK();
331
- perf("SDK loaded");
332
- } catch (e) {
333
- sendToDaemon({ type: "query_error", error: "Failed to load SDK: " + (e.message || e), exitCode: null, stderr: null });
334
- return;
335
- }
336
-
337
- messageQueue = createMessageQueue();
338
- abortController = new AbortController();
339
-
340
- // Push the initial user message
341
- if (msg.prompt) {
342
- messageQueue.push(msg.prompt);
343
- }
344
-
345
- // Reconstruct MCP servers from serializable descriptors (IPC-proxied handlers)
346
- if (msg.mcpDescriptors) {
347
- var _mcpServers = buildMcpServersFromDescriptors(sdk, msg.mcpDescriptors);
348
- if (_mcpServers) {
349
- perf("MCP servers reconstructed from descriptors (" + Object.keys(_mcpServers).length + " servers)");
350
- }
351
- }
352
-
353
- // Build query options (callbacks are local, everything else from daemon)
354
- var options = msg.options || {};
355
- if (_mcpServers) options.mcpServers = _mcpServers;
356
- options.abortController = abortController;
357
- options.debug = true;
358
- options.debugFile = "/tmp/clay-cli-debug-" + process.pid + ".log";
359
- // Override CLI subprocess spawn to inject NODE_OPTIONS for IPv4-first DNS.
360
- // The SDK constructs its own env for the CLI process, so worker env vars
361
- // like NODE_OPTIONS are not inherited. We intercept the spawn to fix this.
362
- options.spawnClaudeCodeProcess = function(spawnOpts) {
363
- // Force IPv4-only at every level: preload script patches dns.lookup to
364
- // only return IPv4, disables autoSelectFamily, and sets ipv4first order.
365
- // This is needed because the CLI's Axios-based HTTP client ignores
366
- // NODE_OPTIONS dns flags and still attempts IPv6 connections via its
367
- // custom TLS agent, causing 5-10s timeouts on IPv6-less servers.
368
- var preloadScript = require("path").join(__dirname, "ipv4-only.js");
369
- var extraOpts = " --require " + JSON.stringify(preloadScript);
370
- extraOpts += " --dns-result-order=ipv4first --no-network-family-autoselection";
371
- spawnOpts.env.NODE_OPTIONS = (spawnOpts.env.NODE_OPTIONS || "") + extraOpts;
372
- console.log("[sdk-worker] spawnClaudeCodeProcess called, command=" + spawnOpts.command);
373
- var cp = require("child_process").spawn(spawnOpts.command, spawnOpts.args, {
374
- cwd: spawnOpts.cwd,
375
- env: spawnOpts.env,
376
- stdio: ["pipe", "pipe", "pipe"],
377
- });
378
- // Capture ALL CLI stderr
379
- if (cp.stderr) {
380
- cp.stderr.on("data", function(chunk) {
381
- var lines = chunk.toString().split("\n");
382
- for (var li = 0; li < lines.length; li++) {
383
- var line = lines[li].trim();
384
- if (line) console.log("[CLI-STDERR] " + line.substring(0, 500));
385
- }
386
- });
387
- }
388
- return cp;
389
- };
390
- options.canUseTool = function(toolName, input, toolOpts) {
391
- // AskUserQuestion is handled specially: we send it as a separate IPC type
392
- // so the daemon can use its own AskUserQuestion handling logic
393
- if (toolName === "AskUserQuestion") {
394
- var toolUseId = toolOpts.toolUseID || "";
395
- sendToDaemon({
396
- type: "ask_user_request",
397
- toolUseId: toolUseId,
398
- input: input,
399
- });
400
- return new Promise(function(resolve) {
401
- pendingAskUser[toolUseId] = resolve;
402
- if (toolOpts.signal) {
403
- toolOpts.signal.addEventListener("abort", function() {
404
- delete pendingAskUser[toolUseId];
405
- resolve({ behavior: "deny", message: "Cancelled" });
406
- });
407
- }
408
- });
409
- }
410
- return canUseTool(toolName, input, toolOpts);
411
- };
412
- options.onElicitation = function(request, elicitOpts) {
413
- return onElicitation(request, elicitOpts);
414
- };
415
-
416
- perf("creating query instance");
417
- try {
418
- queryInstance = sdk.query({
419
- prompt: messageQueue,
420
- options: options,
421
- });
422
- perf("query instance created");
423
- } catch (e) {
424
- sendToDaemon({ type: "query_error", error: "Failed to create query: " + (e.message || e), exitCode: null, stderr: null });
425
- queryInstance = null;
426
- messageQueue = null;
427
- abortController = null;
428
- return;
429
- }
430
-
431
- // If single-turn, end the message queue immediately
432
- if (msg.singleTurn) {
433
- messageQueue.end();
434
- }
435
-
436
- // Stream events to daemon
437
- try {
438
- var firstEvent = true;
439
- var firstText = true;
440
- var eventCounts = {};
441
- for await (var event of queryInstance) {
442
- var etype = (event && event.type || "?");
443
- var esubtype = (event && event.subtype || "");
444
- eventCounts[etype] = (eventCounts[etype] || 0) + 1;
445
- if (firstEvent) {
446
- perf("FIRST event from SDK (type=" + etype + " subtype=" + esubtype + ")");
447
- firstEvent = false;
448
- }
449
- // Log every non-content event, and the first content/text event
450
- if (etype !== "content_block_delta" && etype !== "content_block_start" && etype !== "content_block_stop") {
451
- var extraInfo = "";
452
- if (esubtype === "api_retry") {
453
- // Dump full event to see all available fields
454
- try {
455
- var retryDump = JSON.stringify(event, function(k, v) {
456
- if (typeof v === "string" && v.length > 200) return v.substring(0, 200) + "...[truncated]";
457
- return v;
458
- });
459
- extraInfo = " FULL=" + retryDump;
460
- } catch (je) {
461
- extraInfo = " keys=" + Object.keys(event).join(",");
462
- }
463
- }
464
- perf("SDK event #" + eventCounts[etype] + " type=" + etype + " subtype=" + esubtype + extraInfo);
465
- }
466
- if (firstText && (etype === "content_block_delta" || etype === "assistant" || (etype === "content_block_start"))) {
467
- perf("FIRST TEXT/CONTENT event (type=" + etype + " subtype=" + esubtype + ")");
468
- firstText = false;
469
- }
470
- sendToDaemon({ type: "sdk_event", event: event });
471
- }
472
- perf("all events streamed (counts=" + JSON.stringify(eventCounts) + "), fetching context usage");
473
- // Fetch context usage breakdown before queryInstance is cleared
474
- try {
475
- if (queryInstance && typeof queryInstance.getContextUsage === "function") {
476
- var ctxUsage = await queryInstance.getContextUsage();
477
- sendToDaemon({ type: "context_usage", data: ctxUsage });
478
- perf("context usage sent");
479
- }
480
- } catch (e) {
481
- // Non-fatal: SDK may have already shut down
482
- console.error("[sdk-worker] getContextUsage failed (non-fatal):", e.message);
483
- }
484
- perf("sending query_done");
485
- sendToDaemon({ type: "query_done" });
486
- } catch (err) {
487
- var errMsg = err.message || String(err);
488
- sendToDaemon({
489
- type: "query_error",
490
- error: errMsg,
491
- exitCode: err.exitCode != null ? err.exitCode : null,
492
- stderr: err.stderr || null,
493
- });
494
- } finally {
495
- queryInstance = null;
496
- messageQueue = null;
497
- abortController = null;
498
- pendingPermissions = {};
499
- pendingAskUser = {};
500
- pendingElicitations = {};
501
- }
502
- }
503
-
504
- function handlePushMessage(msg) {
505
- if (!messageQueue) return;
506
- messageQueue.push(msg.content);
507
- }
508
-
509
- async function handleSetModel(msg) {
510
- if (!queryInstance) return;
511
- try {
512
- await queryInstance.setModel(msg.model);
513
- sendToDaemon({ type: "model_changed", model: msg.model });
514
- } catch (e) {
515
- sendToDaemon({ type: "worker_error", error: "Failed to set model: " + (e.message || e) });
516
- }
517
- }
518
-
519
- async function handleSetEffort(msg) {
520
- if (!queryInstance) return;
521
- try {
522
- await queryInstance.setEffort(msg.effort);
523
- sendToDaemon({ type: "effort_changed", effort: msg.effort });
524
- } catch (e) {
525
- sendToDaemon({ type: "worker_error", error: "Failed to set effort: " + (e.message || e) });
526
- }
527
- }
528
-
529
- async function handleSetPermissionMode(msg) {
530
- if (!queryInstance) return;
531
- try {
532
- await queryInstance.setPermissionMode(msg.mode);
533
- sendToDaemon({ type: "permission_mode_changed", mode: msg.mode });
534
- } catch (e) {
535
- sendToDaemon({ type: "worker_error", error: "Failed to set permission mode: " + (e.message || e) });
536
- }
537
- }
538
-
539
- async function handleStopTask(msg) {
540
- if (!queryInstance) return;
541
- try {
542
- await queryInstance.stopTask(msg.taskId);
543
- } catch (e) {
544
- console.error("[sdk-worker] stopTask error:", e.message);
545
- }
546
- // Also abort as fallback (matches daemon behavior)
547
- if (abortController) {
548
- abortController.abort();
549
- }
550
- }
551
-
552
- // --- Warmup ---
553
- async function handleWarmup(msg) {
554
- var sdk;
555
- try {
556
- sdk = await getSDK();
557
- } catch (e) {
558
- sendToDaemon({ type: "warmup_error", error: "Failed to load SDK: " + (e.message || e) });
559
- return;
560
- }
561
-
562
- var ac = new AbortController();
563
- var mq = createMessageQueue();
564
- mq.push({ type: "user", message: { role: "user", content: [{ type: "text", text: "hi" }] } });
565
- mq.end();
566
-
567
- var warmupOptions = msg.options || {};
568
- warmupOptions.abortController = ac;
569
-
570
- try {
571
- var stream = sdk.query({
572
- prompt: mq,
573
- options: warmupOptions,
574
- });
575
-
576
- for await (var event of stream) {
577
- if (event.type === "system" && event.subtype === "init") {
578
- var result = {
579
- slashCommands: event.slash_commands || [],
580
- model: event.model || "",
581
- skills: event.skills || [],
582
- fastModeState: event.fast_mode_state || null,
583
- };
584
-
585
- // Fetch available models before aborting
586
- try {
587
- var models = await stream.supportedModels();
588
- result.models = models || [];
589
- } catch (e) {
590
- result.models = [];
591
- }
592
-
593
- sendToDaemon({ type: "warmup_done", result: result });
594
- ac.abort();
595
- break;
596
- }
597
- }
598
- } catch (e) {
599
- if (e && e.name !== "AbortError" && !(e.message && e.message.indexOf("aborted") !== -1)) {
600
- sendToDaemon({ type: "warmup_error", error: "Warmup failed: " + (e.message || e) });
601
- }
602
- }
603
- }
604
-
605
- // --- Cleanup ---
606
- var _exitScheduled = false;
607
- function cleanup() {
608
- if (_keepAlive) {
609
- try { clearInterval(_keepAlive); } catch (e) {}
610
- }
611
- if (abortController) {
612
- try { abortController.abort(); } catch (e) {}
613
- }
614
- if (messageQueue) {
615
- try { messageQueue.end(); } catch (e) {}
616
- }
617
- if (conn && !conn.destroyed) {
618
- try { conn.end(); } catch (e) {}
619
- }
620
- }
621
-
622
- // Exit with a grace period so the SDK can flush session state to disk.
623
- // Without this, process.exit(0) kills pending async writes and the
624
- // session file may be incomplete, causing "no conversation found" on resume.
625
- function gracefulExit(code) {
626
- if (_exitScheduled) return;
627
- _exitScheduled = true;
628
- cleanup();
629
- setTimeout(function() { process.exit(code); }, 800);
630
- }
631
-
632
- // Keep event loop alive — without this, Node may exit if the socket handle
633
- // gets unreferenced (observed on Linux with uid/gid spawn)
634
- var _keepAlive = setInterval(function() {}, 30000);
635
-
636
- // --- Connect to daemon socket ---
637
- try { require("fs").writeSync(2, "[sdk-worker] Connecting to socket: " + socketPath + " +" + (Date.now() - _workerBootTs) + "ms since boot\n"); } catch (e) {}
638
- conn = net.connect(socketPath, function() {
639
- try { require("fs").writeSync(2, "[sdk-worker] Connected, sending ready +" + (Date.now() - _workerBootTs) + "ms since boot\n"); } catch (e) {}
640
- sendToDaemon({ type: "ready" });
641
- });
642
-
643
- conn.on("data", function(chunk) {
644
- buffer += chunk.toString();
645
- var lines = buffer.split("\n");
646
- buffer = lines.pop(); // keep incomplete line in buffer
647
- for (var i = 0; i < lines.length; i++) {
648
- if (!lines[i].trim()) continue;
649
- try {
650
- var msg = JSON.parse(lines[i]);
651
- handleMessage(msg);
652
- } catch (e) {
653
- console.error("[sdk-worker] Failed to parse message:", e.message);
654
- }
655
- }
656
- });
657
-
658
- conn.on("error", function(err) {
659
- console.error("[sdk-worker] Socket error:", err.message);
660
- gracefulExit(1);
661
- });
662
-
663
- conn.on("close", function() {
664
- try { require("fs").writeSync(2, "[sdk-worker] EXIT REASON: socket closed\n"); } catch (e) {}
665
- gracefulExit(0);
666
- });
667
-
668
- // Handle process signals
669
- process.on("SIGTERM", function() {
670
- try { require("fs").writeSync(2, "[sdk-worker] EXIT REASON: SIGTERM\n"); } catch (e) {}
671
- gracefulExit(0);
672
- });
673
-
674
- process.on("SIGINT", function() {
675
- try { require("fs").writeSync(2, "[sdk-worker] EXIT REASON: SIGINT\n"); } catch (e) {}
676
- gracefulExit(0);
677
- });
1
+ // Compatibility entrypoint for legacy isolated worker launches.
2
+ require("./yoke/adapters/claude-worker");
package/lib/server.js CHANGED
@@ -10,7 +10,7 @@ var dm = require("./dm");
10
10
  var mates = require("./mates");
11
11
  var serverAuth = require("./server-auth");
12
12
  var serverSkills = require("./server-skills");
13
- var claudeHookInstaller = require("./claude-hook-installer");
13
+ var claudeHookInstaller = require("./yoke/claude-hook-installer");
14
14
  var serverDm = require("./server-dm");
15
15
  var serverMates = require("./server-mates");
16
16
  var serverHomeChat = require("./server-home-chat");
@@ -0,0 +1,21 @@
1
+ // Product transcript slicing for providers that include a completed turn.
2
+ function endIndex(session, uuid, boundary) {
3
+ if (boundary === undefined) return undefined;
4
+ if (boundary !== "completed-turn-inclusive") throw new Error("Unsupported local fork history boundary");
5
+ var history = session.history || [];
6
+ var markers = (session.messageUUIDs || []).filter(function(marker) { return marker.uuid === uuid && marker.type === "user"; });
7
+ var marker = markers.length === 1 && markers[0];
8
+ var index = marker && marker.historyIndex;
9
+ var entry = Number.isInteger(index) && history[index];
10
+ if (!entry || entry.type !== "message_uuid" || entry.uuid !== uuid) throw new Error("Cannot safely represent this completed-turn fork boundary in local history");
11
+ for (var i = index + 1; i < history.length; i++) {
12
+ entry = history[i];
13
+ if (entry.type === "user_message" || entry.type === "message_uuid" && entry.messageType === "user") break;
14
+ if (entry.type === "done") {
15
+ if (entry.code === 0) return i + 1;
16
+ break;
17
+ }
18
+ }
19
+ throw new Error("Cannot safely represent this completed-turn fork boundary in local history");
20
+ }
21
+ module.exports = { endIndex: endIndex };