clay-server 3.8.1 → 4.0.0-beta.2

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 (189) hide show
  1. package/lib/ask-user-mcp-server.js +5 -119
  2. package/lib/builtin-mates.js +6 -6
  3. package/lib/claude-hook-installer.js +15 -0
  4. package/lib/clay-history-mcp-server.js +62 -297
  5. package/lib/daemon.js +1 -1
  6. package/lib/debate-model-selection.js +119 -0
  7. package/lib/home-debate-tool-policy.js +164 -0
  8. package/lib/home-response-notifications.js +26 -0
  9. package/lib/mate-creation-mcp-server.js +55 -0
  10. package/lib/mate-ready-creation.js +95 -0
  11. package/lib/mates.js +2 -0
  12. package/lib/notes.js +2 -1
  13. package/lib/project-ask-user.js +64 -0
  14. package/lib/project-capsule-catalog.js +101 -0
  15. package/lib/project-connection.js +4 -0
  16. package/lib/project-debate-proposal.js +133 -17
  17. package/lib/project-debate.js +214 -110
  18. package/lib/project-delegated-follow-up.js +84 -0
  19. package/lib/project-delegated-session.js +53 -0
  20. package/lib/project-filesystem.js +7 -9
  21. package/lib/project-knowledge.js +1 -0
  22. package/lib/project-mate-creation-proposal.js +120 -0
  23. package/lib/project-memory.js +14 -7
  24. package/lib/project-models.js +157 -32
  25. package/lib/project-session-notes.js +2 -2
  26. package/lib/project-session-pair.js +8 -8
  27. package/lib/project-sessions.js +66 -51
  28. package/lib/project-vendor-login.js +397 -0
  29. package/lib/project-worker-proposal.js +25 -25
  30. package/lib/project-workspace-query.js +144 -0
  31. package/lib/project.js +349 -102
  32. package/lib/public/app.js +85 -42
  33. package/lib/public/css/avatar-imprints.css +0 -1
  34. package/lib/public/css/base.css +4 -0
  35. package/lib/public/css/capsule-ui.css +389 -0
  36. package/lib/public/css/command-palette.css +306 -6
  37. package/lib/public/css/home-capsule-library.css +243 -0
  38. package/lib/public/css/home-capsule-source.css +192 -0
  39. package/lib/public/css/home-debate-live.css +146 -0
  40. package/lib/public/css/home-debate-planning.css +125 -0
  41. package/lib/public/css/home-debates-archive.css +281 -0
  42. package/lib/public/css/home-hub.css +896 -405
  43. package/lib/public/css/home-mate-creation.css +34 -0
  44. package/lib/public/css/home-mate-model-picker.css +131 -0
  45. package/lib/public/css/home-mate-settings.css +344 -0
  46. package/lib/public/css/home-session-actions.css +265 -0
  47. package/lib/public/css/home-sidebar.css +667 -0
  48. package/lib/public/css/icon-strip.css +1 -2
  49. package/lib/public/css/mates.css +7 -34
  50. package/lib/public/css/pane.css +1 -1
  51. package/lib/public/css/playbook.css +0 -80
  52. package/lib/public/css/sticky-notes.css +53 -117
  53. package/lib/public/css/tui-attention.css +0 -44
  54. package/lib/public/css/workspace-assignment.css +45 -0
  55. package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
  56. package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
  57. package/lib/public/index.html +93 -74
  58. package/lib/public/modules/app-connection.js +10 -1
  59. package/lib/public/modules/app-dm.js +43 -160
  60. package/lib/public/modules/app-home-hub.js +328 -585
  61. package/lib/public/modules/app-message-cards.js +242 -0
  62. package/lib/public/modules/app-message-router.js +84 -0
  63. package/lib/public/modules/app-messages.js +102 -37
  64. package/lib/public/modules/app-notifications.js +8 -119
  65. package/lib/public/modules/app-projects.js +9 -1
  66. package/lib/public/modules/app-rendering.js +21 -602
  67. package/lib/public/modules/avatar-imprint.js +11 -8
  68. package/lib/public/modules/chat-bubble-renderer.js +152 -0
  69. package/lib/public/modules/chat-render-runtime.js +198 -0
  70. package/lib/public/modules/command-palette.js +225 -436
  71. package/lib/public/modules/dm-render.js +82 -0
  72. package/lib/public/modules/home-capsule-creation-intent.js +31 -0
  73. package/lib/public/modules/home-capsule-library.js +146 -0
  74. package/lib/public/modules/home-capsule-source.js +383 -0
  75. package/lib/public/modules/home-chat-identity.js +35 -0
  76. package/lib/public/modules/home-chat-scroll.js +77 -0
  77. package/lib/public/modules/home-chat-stream-state.js +34 -0
  78. package/lib/public/modules/home-composer-focus.js +25 -0
  79. package/lib/public/modules/home-conversations-sheet.js +179 -0
  80. package/lib/public/modules/home-debate-activity.js +48 -0
  81. package/lib/public/modules/home-debate-controls.js +295 -0
  82. package/lib/public/modules/home-debate-launch.js +41 -0
  83. package/lib/public/modules/home-debate-live.js +284 -0
  84. package/lib/public/modules/home-debate-models.js +73 -0
  85. package/lib/public/modules/home-debate-planning.js +335 -0
  86. package/lib/public/modules/home-debates-archive.js +279 -0
  87. package/lib/public/modules/home-dock-resize.js +74 -0
  88. package/lib/public/modules/home-dock.js +446 -0
  89. package/lib/public/modules/home-mate-chat.js +496 -0
  90. package/lib/public/modules/home-mate-creation.js +109 -0
  91. package/lib/public/modules/home-mate-model-picker.js +281 -0
  92. package/lib/public/modules/home-mate-selection.js +38 -0
  93. package/lib/public/modules/home-mate-settings-menu.js +131 -0
  94. package/lib/public/modules/home-mate-settings.js +383 -0
  95. package/lib/public/modules/home-session-actions.js +373 -0
  96. package/lib/public/modules/home-session-model-confirmation.js +13 -0
  97. package/lib/public/modules/home-shell.js +9 -0
  98. package/lib/public/modules/home-sidebar-chat-list.js +63 -0
  99. package/lib/public/modules/home-sidebar.js +158 -0
  100. package/lib/public/modules/home-sub-surface.js +19 -0
  101. package/lib/public/modules/home-surface-boot.js +68 -0
  102. package/lib/public/modules/home-surface.js +141 -0
  103. package/lib/public/modules/home-tools.js +271 -0
  104. package/lib/public/modules/markdown.js +50 -2
  105. package/lib/public/modules/mate-management.js +86 -0
  106. package/lib/public/modules/mate-wizard.js +0 -7
  107. package/lib/public/modules/mention.js +17 -19
  108. package/lib/public/modules/pane-bridge.js +9 -0
  109. package/lib/public/modules/profile.js +4 -5
  110. package/lib/public/modules/project-settings.js +34 -28
  111. package/lib/public/modules/project-switcher.js +11 -5
  112. package/lib/public/modules/project-workspace-assignment.js +23 -0
  113. package/lib/public/modules/search-clay-chat.js +402 -0
  114. package/lib/public/modules/server-settings.js +14 -12
  115. package/lib/public/modules/session-actions.js +1 -1
  116. package/lib/public/modules/sidebar-mates.js +56 -552
  117. package/lib/public/modules/sidebar-mobile.js +7 -64
  118. package/lib/public/modules/sidebar-presence.js +37 -0
  119. package/lib/public/modules/split-pair-ui.js +13 -13
  120. package/lib/public/modules/split-view.js +7 -0
  121. package/lib/public/modules/sticky-notes.js +7 -6
  122. package/lib/public/modules/tool-input-composition.js +48 -0
  123. package/lib/public/modules/tool-llm-status.js +167 -0
  124. package/lib/public/modules/tool-renderer-advanced.js +285 -0
  125. package/lib/public/modules/tool-renderer-chart.js +158 -0
  126. package/lib/public/modules/tool-renderer-semantics.js +35 -0
  127. package/lib/public/modules/tool-renderer.js +464 -0
  128. package/lib/public/modules/tool-runtime.js +309 -0
  129. package/lib/public/modules/tool-ui-evaluator.js +97 -0
  130. package/lib/public/modules/tool-ui-tree.js +21 -0
  131. package/lib/public/modules/tui-attention.js +10 -0
  132. package/lib/public/modules/user-settings.js +16 -23
  133. package/lib/public/modules/vendor-login.js +287 -0
  134. package/lib/public/modules/worker-proposal.js +2 -2
  135. package/lib/public/modules/workspace-assignment-card.js +149 -0
  136. package/lib/public/modules/workspace-assignment-routing.js +4 -0
  137. package/lib/public/style.css +13 -2
  138. package/lib/runtime-env.js +65 -0
  139. package/lib/sdk-bridge.js +120 -32
  140. package/lib/sdk-message-processor.js +10 -6
  141. package/lib/server-home-capsule-creation.js +36 -0
  142. package/lib/server-home-chat-events.js +287 -0
  143. package/lib/server-home-chat.js +518 -0
  144. package/lib/server-home-clay-entry.js +65 -0
  145. package/lib/server-home-clay-session-links.js +26 -0
  146. package/lib/server-home-debate-planning.js +214 -0
  147. package/lib/server-home-debates.js +137 -0
  148. package/lib/server-home-mate-creation.js +261 -0
  149. package/lib/server-home-models.js +241 -0
  150. package/lib/server-home-preferences.js +61 -0
  151. package/lib/server-mates.js +37 -0
  152. package/lib/server-tools.js +377 -0
  153. package/lib/server.js +111 -59
  154. package/lib/session-pair-mcp-server.js +6 -6
  155. package/lib/sessions.js +18 -3
  156. package/lib/tool-capsule-source.js +142 -0
  157. package/lib/tool-control-mcp-server.js +126 -0
  158. package/lib/tool-llm.js +48 -0
  159. package/lib/tool-storage.js +77 -0
  160. package/lib/tool-ui-spec-advanced.js +82 -0
  161. package/lib/tool-ui-spec.js +261 -0
  162. package/lib/tools-registry.js +286 -0
  163. package/lib/users-home-dock-preferences.js +77 -0
  164. package/lib/users-home-surface-preferences.js +99 -0
  165. package/lib/users-preferences.js +4 -7
  166. package/lib/users.js +12 -0
  167. package/lib/whats-new-content.js +3 -3
  168. package/lib/workspace-assignment-service.js +420 -0
  169. package/lib/workspace-query-mcp-server.js +154 -0
  170. package/lib/workspace-query-service.js +492 -0
  171. package/lib/ws-schema.js +93 -12
  172. package/lib/yoke/adapters/acp.js +2 -1
  173. package/lib/yoke/adapters/antigravity.js +2 -1
  174. package/lib/yoke/adapters/claude-worker.js +11 -4
  175. package/lib/yoke/adapters/claude.js +73 -43
  176. package/lib/yoke/adapters/codex.js +40 -10
  177. package/lib/yoke/adapters/kiro.js +2 -1
  178. package/lib/yoke/claude-user-input.js +38 -0
  179. package/lib/yoke/codex-app-server.js +20 -2
  180. package/lib/yoke/codex-user-input.js +64 -0
  181. package/lib/yoke/complete-once.js +164 -0
  182. package/lib/yoke/index.js +5 -1
  183. package/lib/yoke/interface.js +19 -0
  184. package/lib/yoke/mcp-bridge-server.js +2 -6
  185. package/lib/yoke/user-input.js +254 -0
  186. package/package.json +3 -2
  187. package/lib/public/css/home-chat.css +0 -380
  188. package/lib/public/modules/home-chat.js +0 -438
  189. package/lib/server-clay-home.js +0 -245
@@ -1065,7 +1065,7 @@ function createKiroAdapter(opts) {
1065
1065
  var model = queryOpts.model || _defaultModel || "auto";
1066
1066
  var ac = queryOpts.abortController || new AbortController();
1067
1067
  var kiroOpts = (queryOpts.adapterOptions && queryOpts.adapterOptions.KIRO) || {};
1068
- var sharedSkills = skillDiscovery.discoverSkills(queryOpts.cwd || _cwd);
1068
+ var sharedSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd || _cwd);
1069
1069
  var skillIndex = skillDiscovery.buildSkillIndex(sharedSkills);
1070
1070
 
1071
1071
  var activeEntry = { abort: function() { try { ac.abort(); } catch (e) {} } };
@@ -1123,6 +1123,7 @@ function createKiroAdapter(opts) {
1123
1123
  var ac = new AbortController();
1124
1124
  var handle = await adapter.createQuery({
1125
1125
  cwd: (opts && opts.cwd) || _cwd,
1126
+ env: opts && opts.env,
1126
1127
  systemPrompt: systemPrompt,
1127
1128
  model: "auto",
1128
1129
  abortController: ac,
@@ -0,0 +1,38 @@
1
+ // Claude SDK mappings for YOKE structured user input.
2
+ var userInput = require("./user-input");
3
+
4
+ function ask(handler, input, opts) {
5
+ opts = opts || {};
6
+ return userInput.dispatchUserInput(handler, input, {
7
+ requestId: opts.toolUseID || opts.requestId,
8
+ signal: opts.signal,
9
+ source: "claude_ask_user_question",
10
+ native: true,
11
+ provider: "claude",
12
+ }).then(function (result) { return userInput.claudePermissionResult(input, result); });
13
+ }
14
+
15
+ function elicitation(handler, request, opts) {
16
+ opts = opts || {};
17
+ return userInput.dispatchUserInput(handler, userInput.questionsFromElicitation(request), {
18
+ requestId: opts.requestId || request.elicitationId,
19
+ signal: opts.signal,
20
+ source: "claude_elicitation",
21
+ native: true,
22
+ provider: "claude",
23
+ presentation: "elicitation",
24
+ diagnostics: { elicitation: request },
25
+ }).then(userInput.elicitationResponse);
26
+ }
27
+
28
+ function canUseTool(queryOpts) {
29
+ var original = queryOpts.canUseTool;
30
+ return function (toolName, input, opts) {
31
+ if (toolName === "AskUserQuestion" && typeof queryOpts.onUserInputRequest === "function") return ask(queryOpts.onUserInputRequest, input, opts);
32
+ if (toolName === "AskUserQuestion") return Promise.resolve({ behavior: "deny", message: "Use the query's structured input fallback tool." });
33
+ if (original) return original(toolName, input, opts);
34
+ return Promise.resolve({ behavior: "deny", message: "No tool permission handler is available." });
35
+ };
36
+ }
37
+
38
+ module.exports = { ask: ask, elicitation: elicitation, canUseTool: canUseTool };
@@ -13,7 +13,13 @@ var { buildUserEnv } = require("../build-user-env");
13
13
  var { wrapSpawnAsUser } = require("../os-users");
14
14
 
15
15
  // --- Find the codex binary path ---
16
- // Mirrors the logic from @openai/codex-sdk findCodexPath()
16
+ // Mirrors the logic from @openai/codex-sdk findCodexPath().
17
+ //
18
+ // The bundled @openai/codex platform package is the default. Set the
19
+ // CLAY_CODEX_PATH env var to an existing executable to run a different codex
20
+ // build (local dev build, system install, pinned older release); the chosen
21
+ // binary is logged on every resolution. An unset or non-existent
22
+ // CLAY_CODEX_PATH silently falls back to the bundled resolution.
17
23
 
18
24
  var PLATFORM_PACKAGE_BY_TARGET = {
19
25
  "aarch64-apple-darwin": "@openai/codex-darwin-arm64",
@@ -39,6 +45,15 @@ function getTargetTriple() {
39
45
  }
40
46
 
41
47
  function findCodexPath() {
48
+ var override = process.env.CLAY_CODEX_PATH;
49
+ if (override) {
50
+ if (fs.existsSync(override)) {
51
+ console.log("[codex-app-server] Using CLAY_CODEX_PATH binary:", override);
52
+ return override;
53
+ }
54
+ console.warn("[codex-app-server] CLAY_CODEX_PATH does not exist, falling back to bundled codex:", override);
55
+ }
56
+
42
57
  var triple = getTargetTriple();
43
58
  if (!triple) throw new Error("Unsupported platform: " + process.platform + "/" + process.arch);
44
59
 
@@ -59,7 +74,10 @@ function findCodexPath() {
59
74
  path.join(vendorRoot, triple, "codex", binaryName),
60
75
  ];
61
76
  for (var i = 0; i < candidates.length; i++) {
62
- if (fs.existsSync(candidates[i])) return candidates[i];
77
+ if (fs.existsSync(candidates[i])) {
78
+ console.log("[codex-app-server] Using bundled codex binary:", candidates[i]);
79
+ return candidates[i];
80
+ }
63
81
  }
64
82
  throw new Error("codex binary not found in any known layout under " + path.join(vendorRoot, triple));
65
83
  } catch (e) {
@@ -0,0 +1,64 @@
1
+ // Codex app-server 0.147 structured user-input protocol mapping.
2
+ var userInput = require("./user-input");
3
+
4
+ function nativeInput(params) {
5
+ return {
6
+ questions: (params.questions || []).map(function (question) {
7
+ return {
8
+ id: question.id,
9
+ header: question.header || "",
10
+ question: question.question,
11
+ allowOther: question.isOther !== false,
12
+ secret: question.isSecret === true,
13
+ options: Array.isArray(question.options) ? question.options : [],
14
+ };
15
+ }),
16
+ };
17
+ }
18
+
19
+ function handleNative(appServer, msg, handler, expectedThreadId, signal) {
20
+ var params = msg.params || {};
21
+ if (!params.threadId || (expectedThreadId && params.threadId !== expectedThreadId)) {
22
+ appServer.respondError(msg.id, -32001, "Structured input request does not belong to this query thread.");
23
+ return;
24
+ }
25
+ userInput.dispatchUserInput(handler, nativeInput(params), {
26
+ requestId: params.itemId || String(msg.id),
27
+ signal: signal,
28
+ source: "codex_request_user_input",
29
+ native: true,
30
+ provider: "codex",
31
+ diagnostics: { threadId: params.threadId, turnId: params.turnId || null, itemId: params.itemId || null },
32
+ }).then(function (result) {
33
+ appServer.respond(msg.id, userInput.codexResponse(result));
34
+ }).catch(function (error) {
35
+ appServer.respondError(msg.id, -32602, error.message);
36
+ });
37
+ }
38
+
39
+ function handleElicitation(appServer, msg, handler, signal) {
40
+ var params = msg.params || {};
41
+ var request = {
42
+ serverName: params.serverName || (params._meta && params._meta.tool) || "Tool",
43
+ message: params.message || params.prompt || "",
44
+ url: params.url || null,
45
+ elicitationId: params.elicitationId || null,
46
+ requestedSchema: params.requestedSchema || null,
47
+ };
48
+ var input = Array.isArray(params.questions) ? nativeInput(params) : userInput.questionsFromElicitation(request);
49
+ userInput.dispatchUserInput(handler, input, {
50
+ requestId: request.elicitationId || String(msg.id),
51
+ signal: signal,
52
+ source: "codex_mcp_elicitation",
53
+ native: true,
54
+ provider: "codex",
55
+ presentation: "elicitation",
56
+ diagnostics: { threadId: params.threadId || null, elicitation: request },
57
+ }).then(function (result) {
58
+ appServer.respond(msg.id, userInput.elicitationResponse(result));
59
+ }).catch(function (error) {
60
+ appServer.respondError(msg.id, -32602, error.message);
61
+ });
62
+ }
63
+
64
+ module.exports = { nativeInput: nativeInput, handleNative: handleNative, handleElicitation: handleElicitation };
@@ -0,0 +1,164 @@
1
+ // Canonical single-turn completion lifecycle for YOKE adapters.
2
+
3
+ function assistantMessageText(event) {
4
+ if (!event || event.yokeType !== "message" || event.messageRole !== "assistant") return "";
5
+ if (!Array.isArray(event.content)) return "";
6
+ var text = "";
7
+ for (var i = 0; i < event.content.length; i++) {
8
+ if (event.content[i] && event.content[i].type === "text" && event.content[i].text) {
9
+ text += event.content[i].text;
10
+ }
11
+ }
12
+ return text;
13
+ }
14
+
15
+ function normalizedText(streamedText, finalText) {
16
+ if (!streamedText) return finalText;
17
+ if (!finalText) return streamedText;
18
+ if (finalText.indexOf(streamedText) === 0) return finalText;
19
+ if (streamedText.indexOf(finalText) === 0) return streamedText;
20
+ return streamedText;
21
+ }
22
+
23
+ function oneShotError(event) {
24
+ if (!event) return null;
25
+ if (event.yokeType === "error") return new Error(event.text || "One-shot completion failed.");
26
+ if (event.yokeType === "auth_required") {
27
+ return new Error("The " + (event.vendor || "selected") + " provider requires sign-in.");
28
+ }
29
+ return null;
30
+ }
31
+
32
+ async function createOneShot(adapter, queryOpts, allowUnknownPersistence) {
33
+ if (typeof adapter.createOneShotQuery === "function") {
34
+ var created = await adapter.createOneShotQuery(queryOpts);
35
+ if (!created || !created.handle) throw new Error("YOKE adapter returned an invalid one-shot query.");
36
+ if (created.backendPersistence !== "ephemeral") {
37
+ if (!allowUnknownPersistence) {
38
+ if (typeof created.handle.abort === "function") created.handle.abort();
39
+ if (typeof created.handle.close === "function") created.handle.close();
40
+ throw new Error("The " + adapter.vendor + " adapter did not confirm ephemeral one-shot execution.");
41
+ }
42
+ created.backendPersistence = "unknown";
43
+ }
44
+ return created;
45
+ }
46
+ if (!allowUnknownPersistence) {
47
+ throw new Error("The " + adapter.vendor + " adapter does not guarantee ephemeral one-shot execution.");
48
+ }
49
+ return {
50
+ handle: await adapter.createQuery(Object.assign({}, queryOpts, { persistSession: false })),
51
+ backendPersistence: "unknown",
52
+ };
53
+ }
54
+
55
+ async function completeOnce(adapter, opts) {
56
+ opts = opts || {};
57
+ if (!adapter || typeof adapter.createQuery !== "function") throw new Error("A YOKE adapter is required.");
58
+ if (typeof opts.prompt !== "string" || !opts.prompt.trim()) throw new Error("A one-shot prompt is required.");
59
+
60
+ var timeoutMs = opts.timeoutMs || 60000;
61
+ var controller = new AbortController();
62
+ var handle = null;
63
+ var timedOut = false;
64
+ var externallyAborted = false;
65
+ var timer = null;
66
+ var externalSignal = opts.signal || null;
67
+ var abortHandle = function() {
68
+ controller.abort();
69
+ if (handle && typeof handle.abort === "function") handle.abort();
70
+ };
71
+ var onExternalAbort = function() {
72
+ externallyAborted = true;
73
+ abortHandle();
74
+ };
75
+
76
+ if (externalSignal) {
77
+ if (externalSignal.aborted) onExternalAbort();
78
+ else externalSignal.addEventListener("abort", onExternalAbort, { once: true });
79
+ }
80
+ timer = setTimeout(function() {
81
+ timedOut = true;
82
+ abortHandle();
83
+ }, timeoutMs);
84
+
85
+ var streamedText = "";
86
+ var finalText = "";
87
+ var resultEvent = null;
88
+ var interactiveRequested = false;
89
+ var created;
90
+ try {
91
+ var queryOpts = Object.assign({}, opts.query || {}, {
92
+ cwd: opts.cwd,
93
+ model: opts.model,
94
+ systemPrompt: opts.systemPrompt,
95
+ abortController: controller,
96
+ onUserInputRequest: function (request, respond) {
97
+ interactiveRequested = true;
98
+ respond.cancel("One-shot completions cannot request interactive user input.");
99
+ },
100
+ });
101
+ var creationFinished = false;
102
+ var rejectCreation;
103
+ var creationAbort = new Promise(function(resolve, reject) { rejectCreation = reject; });
104
+ var onCreationAbort = function() { rejectCreation(new Error("One-shot completion aborted.")); };
105
+ controller.signal.addEventListener("abort", onCreationAbort, { once: true });
106
+ var creation = createOneShot(adapter, queryOpts, !!opts.allowUnknownBackendPersistence);
107
+ creation.then(function(lateCreated) {
108
+ if (!creationFinished && controller.signal.aborted && lateCreated && lateCreated.handle) {
109
+ if (typeof lateCreated.handle.abort === "function") lateCreated.handle.abort();
110
+ if (typeof lateCreated.handle.close === "function") lateCreated.handle.close();
111
+ }
112
+ }).catch(function() {});
113
+ try {
114
+ created = await Promise.race([creation, creationAbort]);
115
+ creationFinished = true;
116
+ } finally {
117
+ controller.signal.removeEventListener("abort", onCreationAbort);
118
+ }
119
+ handle = created.handle;
120
+ if (controller.signal.aborted) {
121
+ if (typeof handle.abort === "function") handle.abort();
122
+ throw new Error("One-shot completion aborted.");
123
+ }
124
+ if (handle.pushMessage(opts.prompt) !== true) {
125
+ throw new Error("YOKE one-shot query rejected its prompt.");
126
+ }
127
+ handle.endInput();
128
+
129
+ for await (var event of handle) {
130
+ var eventError = oneShotError(event);
131
+ if (eventError) throw eventError;
132
+ if (event.yokeType === "text_delta" && event.text) streamedText += event.text;
133
+ var messageText = assistantMessageText(event);
134
+ if (messageText) finalText = messageText;
135
+ if (event.yokeType === "result") {
136
+ resultEvent = event;
137
+ break;
138
+ }
139
+ }
140
+ if (timedOut) throw new Error("YOKE one-shot timed out after " + timeoutMs + " ms.");
141
+ if (externallyAborted) throw new Error("YOKE one-shot was aborted.");
142
+ if (interactiveRequested) throw new Error("YOKE one-shot completion requested interactive user input.");
143
+ return {
144
+ text: normalizedText(streamedText, finalText).trim(),
145
+ backendPersistence: created.backendPersistence || "unknown",
146
+ vendor: adapter.vendor,
147
+ model: opts.model || null,
148
+ result: resultEvent,
149
+ };
150
+ } catch (error) {
151
+ if (timedOut) throw new Error("YOKE one-shot timed out after " + timeoutMs + " ms.");
152
+ if (externallyAborted) throw new Error("YOKE one-shot was aborted.");
153
+ throw error;
154
+ } finally {
155
+ clearTimeout(timer);
156
+ if (externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
157
+ if (handle && typeof handle.close === "function") handle.close();
158
+ }
159
+ }
160
+
161
+ module.exports = {
162
+ completeOnce: completeOnce,
163
+ normalizedText: normalizedText,
164
+ };
package/lib/yoke/index.js CHANGED
@@ -4,6 +4,8 @@
4
4
  var iface = require("./interface");
5
5
  var instructions = require("./instructions");
6
6
  var vendorRegistry = require("./vendor-registry");
7
+ var completeOnce = require("./complete-once").completeOnce;
8
+ var userInput = require("./user-input");
7
9
  var createClaudeAdapter = require("./adapters/claude").createClaudeAdapter;
8
10
  var createCodexAdapter = require("./adapters/codex").createCodexAdapter;
9
11
  var createAntigravityAdapter = require("./adapters/antigravity").createAntigravityAdapter;
@@ -47,7 +49,7 @@ function wrapCreateQuery(adapter, defaultCwd) {
47
49
  adapter.createQuery = function(queryOpts) {
48
50
  queryOpts = queryOpts || {};
49
51
  var projectDir = (queryOpts && queryOpts.cwd) || defaultCwd;
50
- var merged = instructions.scanAndMerge(projectDir, adapter.vendor);
52
+ var merged = queryOpts.skipProjectInstructions ? "" : instructions.scanAndMerge(projectDir, adapter.vendor);
51
53
 
52
54
  if (merged) {
53
55
  var parts = [];
@@ -473,4 +475,6 @@ module.exports = {
473
475
  TOOL_POLICIES: iface.TOOL_POLICIES,
474
476
  validateAdapter: iface.validateAdapter,
475
477
  validateQueryHandle: iface.validateQueryHandle,
478
+ completeOnce: completeOnce,
479
+ userInput: userInput,
476
480
  };
@@ -40,6 +40,25 @@ var INITIALIZE_TIMEOUT_MS = 90000;
40
40
  * .createToolServer(def): ToolServer (opaque)
41
41
  * .createQuery(opts): QueryHandle
42
42
  *
43
+ * Optional one-shot capability:
44
+ * .createOneShotQuery(opts): Promise<{ handle, backendPersistence }>
45
+ * backendPersistence is "ephemeral" only when the adapter can verify the
46
+ * provider does not materialize the backend conversation. Adapters without
47
+ * this method may only use completeOnce's explicit unknown-persistence path.
48
+ *
49
+ * Optional structured user-input capability:
50
+ * .userInputCapability: { mode: "native"|"fallback", native: boolean, transport: string }
51
+ * createQuery({ userInputMode: "auto"|"native"|"fallback",
52
+ * onUserInputRequest(request, respond) })
53
+ * request.presentation is "questions" or "elicitation";
54
+ * request.questions contain 1-3 { id, header, question, multiSelect,
55
+ * allowOther, secret, options }; options contain zero or 2-6 safe
56
+ * { label, description } entries. respond(answerMap),
57
+ * respond.submitContent(typedMap), and respond.cancel(reason) settle
58
+ * exactly once. submitContent preserves typed MCP elicitation forms.
59
+ * Adapters with mode "native" translate their provider protocol directly.
60
+ * Other adapters use createFallbackServer() from yoke/user-input.
61
+ *
43
62
  * Lightweight utilities:
44
63
  * .generateTitle(messages, opts) : Promise<string> - generate a short session title
45
64
  * messages: string[] - user messages to derive the title from
@@ -102,12 +102,8 @@ function postJson(urlPath, body) {
102
102
  reject(err);
103
103
  });
104
104
 
105
- // 10 min safety ceiling. ask_user_questions is stateless (returns
106
- // immediately; the user's answer arrives on the next turn as a new
107
- // user message), so no tool call should legitimately block long.
108
- // The generous ceiling is just a belt-and-suspenders against slow
109
- // browser automation or external MCP servers, not the primary
110
- // mechanism for human-in-the-loop tools.
105
+ // 10 min safety ceiling. Structured user input may legitimately wait
106
+ // for a person, while other tools normally return much sooner.
111
107
  req.setTimeout(600000, function () {
112
108
  req.destroy(new Error("Request timed out"));
113
109
  });
@@ -0,0 +1,254 @@
1
+ // Provider-neutral structured user-input lifecycle for YOKE queries.
2
+
3
+ var crypto = require("crypto");
4
+ var z;
5
+ try { z = require("zod"); } catch (e) { z = null; }
6
+
7
+ var TOOL_NAME = "ask_user_questions";
8
+
9
+ function cleanText(value, field, max) {
10
+ if (typeof value !== "string" || !value.trim()) throw new Error(field + " must be a non-empty string.");
11
+ var text = value.trim();
12
+ if (text.length > max) throw new Error(field + " must be at most " + max + " characters.");
13
+ return text;
14
+ }
15
+
16
+ function normalizeOptions(options, questionIndex) {
17
+ if (options == null) return [];
18
+ if (!Array.isArray(options)) throw new Error("Question " + (questionIndex + 1) + " options must be an array.");
19
+ if (options.length === 1 || options.length > 6) {
20
+ throw new Error("Question " + (questionIndex + 1) + " must use zero options or 2-6 options.");
21
+ }
22
+ var result = [];
23
+ for (var i = 0; i < options.length; i++) {
24
+ var option = options[i] || {};
25
+ result.push({
26
+ label: cleanText(option.label || option.value, "Option label", 60),
27
+ description: typeof option.description === "string" ? option.description.trim().substring(0, 160) : "",
28
+ });
29
+ }
30
+ return result;
31
+ }
32
+
33
+ function normalizeQuestions(input) {
34
+ var source = input && Array.isArray(input.questions) ? input.questions : null;
35
+ if (!source || source.length < 1 || source.length > 3) throw new Error("Structured input requires 1-3 questions.");
36
+ var ids = {};
37
+ var questions = [];
38
+ for (var i = 0; i < source.length; i++) {
39
+ var item = source[i] || {};
40
+ var id = typeof item.id === "string" && item.id.trim() ? item.id.trim() : "question_" + (i + 1);
41
+ if (id.length > 128 || ids[id]) throw new Error("Question IDs must be unique strings of at most 128 characters.");
42
+ ids[id] = true;
43
+ questions.push({
44
+ id: id,
45
+ header: typeof item.header === "string" ? item.header.trim().substring(0, 40) : "",
46
+ question: cleanText(item.question || item.prompt, "Question", 2000),
47
+ multiSelect: item.multiSelect === true,
48
+ allowOther: item.allowOther !== false && item.isOther !== false,
49
+ secret: item.secret === true || item.isSecret === true,
50
+ options: normalizeOptions(item.options, i),
51
+ });
52
+ }
53
+ return questions;
54
+ }
55
+
56
+ function questionsFromElicitation(request) {
57
+ var schema = request && request.requestedSchema;
58
+ var properties = schema && schema.properties && typeof schema.properties === "object" ? schema.properties : null;
59
+ var questions = [];
60
+ if (properties) {
61
+ Object.keys(properties).forEach(function (id) {
62
+ var property = properties[id] || {};
63
+ var values = Array.isArray(property.enum) ? property.enum : [];
64
+ questions.push({
65
+ id: id,
66
+ header: request.serverName || "Input",
67
+ question: property.description || request.message || id,
68
+ options: values.map(function (value) { return { label: String(value), description: "" }; }),
69
+ });
70
+ });
71
+ }
72
+ if (!questions.length) questions.push({ id: "response", header: request.serverName || "Input", question: request.message || "What should the tool use?", options: [] });
73
+ return { questions: questions };
74
+ }
75
+
76
+ function answerValues(value) {
77
+ if (value && Array.isArray(value.answers)) value = value.answers;
78
+ if (!Array.isArray(value)) value = value == null ? [] : [value];
79
+ return value.map(function (entry) { return String(entry).trim(); }).filter(Boolean);
80
+ }
81
+
82
+ function normalizeAnswers(questions, raw) {
83
+ raw = raw || {};
84
+ var answers = {};
85
+ for (var i = 0; i < questions.length; i++) {
86
+ var question = questions[i];
87
+ var value = Object.prototype.hasOwnProperty.call(raw, question.id) ? raw[question.id] : raw[i];
88
+ var values = answerValues(value);
89
+ if (!values.length) throw new Error("An answer is required for " + question.id + ".");
90
+ if (!question.multiSelect && values.length > 1) throw new Error("Only one answer is allowed for " + question.id + ".");
91
+ answers[question.id] = values;
92
+ }
93
+ return answers;
94
+ }
95
+
96
+ /**
97
+ * Start one provider-neutral structured-input request.
98
+ *
99
+ * handler receives:
100
+ * request: { id, questions, source, native, provider, diagnostics }
101
+ * respond(answers): submits once; respond.cancel(reason) cancels once.
102
+ *
103
+ * The returned promise resolves to { status, answers|reason }. Provider
104
+ * adapters translate that result back to their own wire protocol. Nothing in
105
+ * this lifecycle is persisted; Clay callers may persist sanitized events.
106
+ */
107
+ function dispatchUserInput(handler, input, opts) {
108
+ opts = opts || {};
109
+ var questions;
110
+ try { questions = normalizeQuestions(input); } catch (e) { return Promise.reject(e); }
111
+ if (typeof handler !== "function") return Promise.reject(new Error("This query cannot request structured user input."));
112
+ var id = opts.requestId || (input && input.id) || "yoke_input_" + crypto.randomUUID();
113
+ var settled = false;
114
+ var abortHandler = null;
115
+ var settleListeners = [];
116
+ return new Promise(function (resolve, reject) {
117
+ function cleanup(result) {
118
+ if (opts.signal && abortHandler && typeof opts.signal.removeEventListener === "function") opts.signal.removeEventListener("abort", abortHandler);
119
+ for (var li = 0; li < settleListeners.length; li++) {
120
+ try { settleListeners[li](result); } catch (e) {}
121
+ }
122
+ }
123
+ function finish(result) {
124
+ if (settled) return false;
125
+ settled = true;
126
+ cleanup(result);
127
+ resolve(result);
128
+ return true;
129
+ }
130
+ function fail(error) {
131
+ if (settled) return false;
132
+ settled = true;
133
+ cleanup({ status: "error", error: error.message || String(error) });
134
+ reject(error);
135
+ return true;
136
+ }
137
+ var respond = function (answers) {
138
+ try { return finish({ status: "submitted", answers: normalizeAnswers(questions, answers) }); }
139
+ catch (e) { return fail(e); }
140
+ };
141
+ respond.submitContent = function (content) {
142
+ if (!content || typeof content !== "object" || Array.isArray(content)) return fail(new Error("Structured elicitation content must be an object."));
143
+ return finish({ status: "submitted", answers: {}, content: content });
144
+ };
145
+ respond.cancel = function (reason) { return finish({ status: "cancelled", reason: reason || "Cancelled" }); };
146
+ respond.isPending = function () { return !settled; };
147
+ respond.onSettle = function (listener) {
148
+ if (typeof listener === "function" && !settled) settleListeners.push(listener);
149
+ return respond;
150
+ };
151
+ abortHandler = function () { respond.cancel("Aborted"); };
152
+ if (opts.signal) {
153
+ if (opts.signal.aborted) return abortHandler();
154
+ opts.signal.addEventListener("abort", abortHandler, { once: true });
155
+ }
156
+ var request = {
157
+ id: String(id),
158
+ questions: questions,
159
+ presentation: opts.presentation || "questions",
160
+ source: opts.source || "unknown",
161
+ native: opts.native === true,
162
+ provider: opts.provider || null,
163
+ diagnostics: opts.diagnostics || null,
164
+ };
165
+ try {
166
+ var returned = handler(request, respond);
167
+ if (returned && typeof returned.then === "function") returned.catch(fail);
168
+ } catch (error) {
169
+ fail(error);
170
+ }
171
+ });
172
+ }
173
+
174
+ function claudePermissionResult(input, result) {
175
+ if (!result || result.status !== "submitted") return { behavior: "deny", message: (result && result.reason) || "Cancelled" };
176
+ var questions = normalizeQuestions(input);
177
+ var byText = {};
178
+ for (var i = 0; i < questions.length; i++) byText[questions[i].question] = result.answers[questions[i].id].join(", ");
179
+ return { behavior: "allow", updatedInput: Object.assign({}, input, { answers: byText }) };
180
+ }
181
+
182
+ function codexResponse(result) {
183
+ var answers = {};
184
+ if (result && result.status === "submitted") {
185
+ Object.keys(result.answers).forEach(function (id) { answers[id] = { answers: result.answers[id] }; });
186
+ }
187
+ return { answers: answers };
188
+ }
189
+
190
+ function elicitationResponse(result) {
191
+ if (!result || result.status !== "submitted") return { action: "reject" };
192
+ if (result.content) return { action: "accept", content: result.content };
193
+ var content = {};
194
+ Object.keys(result.answers).forEach(function (id) { content[id] = result.answers[id].length === 1 ? result.answers[id][0] : result.answers[id]; });
195
+ return { action: "accept", content: content };
196
+ }
197
+
198
+ function buildQuestionShape() {
199
+ if (!z) return {};
200
+ var option = z.object({ label: z.string().min(1).max(60), description: z.string().max(160).optional() }).passthrough();
201
+ var question = z.object({
202
+ id: z.string().max(128).optional(), header: z.string().max(40).optional(), question: z.string().min(1),
203
+ multiSelect: z.boolean().optional(), options: z.array(option).max(6).refine(function (items) { return items.length === 0 || items.length >= 2; }),
204
+ }).passthrough();
205
+ return { questions: z.array(question).min(1).max(3) };
206
+ }
207
+
208
+ function fallbackToolDefs(handler) {
209
+ return [{
210
+ name: TOOL_NAME,
211
+ description: "Ask the user 1-3 structured questions. Use zero options for freeform or 2-6 options for choices; never one option.",
212
+ inputSchema: buildQuestionShape(),
213
+ handler: function (input) {
214
+ return dispatchUserInput(handler, input, { source: "mcp_fallback", native: false }).then(function (result) {
215
+ if (result.status !== "submitted") return { content: [{ type: "text", text: "The user cancelled this question." }], isError: true };
216
+ return { content: [{ type: "text", text: JSON.stringify(result.answers) }] };
217
+ }).catch(function (error) { return { content: [{ type: "text", text: "Error: " + error.message }], isError: true }; });
218
+ },
219
+ }];
220
+ }
221
+
222
+ function capability(adapter) {
223
+ return adapter && adapter.userInputCapability ? adapter.userInputCapability : { mode: "fallback", native: false };
224
+ }
225
+
226
+ function selectMode(adapter, requestedMode) {
227
+ if (requestedMode === "fallback") return "fallback";
228
+ if (requestedMode === "native" && capability(adapter).mode !== "native") {
229
+ throw new Error("This provider does not support native structured user input.");
230
+ }
231
+ return capability(adapter).mode === "native" ? "native" : "fallback";
232
+ }
233
+
234
+ function createFallbackServer(adapter, handler) {
235
+ var opts = arguments.length > 2 && arguments[2] ? arguments[2] : {};
236
+ if (!adapter || typeof adapter.createToolServer !== "function") return null;
237
+ if (capability(adapter).mode === "native" && opts.force !== true) return null;
238
+ return adapter.createToolServer({ name: "yoke-user-input", version: "1.0.0", tools: fallbackToolDefs(handler) });
239
+ }
240
+
241
+ module.exports = {
242
+ TOOL_NAME: TOOL_NAME,
243
+ normalizeQuestions: normalizeQuestions,
244
+ questionsFromElicitation: questionsFromElicitation,
245
+ normalizeAnswers: normalizeAnswers,
246
+ dispatchUserInput: dispatchUserInput,
247
+ claudePermissionResult: claudePermissionResult,
248
+ codexResponse: codexResponse,
249
+ elicitationResponse: elicitationResponse,
250
+ fallbackToolDefs: fallbackToolDefs,
251
+ createFallbackServer: createFallbackServer,
252
+ capability: capability,
253
+ selectMode: selectMode,
254
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "3.8.1",
3
+ "version": "4.0.0-beta.2",
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",
@@ -51,7 +51,8 @@
51
51
  "dependencies": {
52
52
  "@anthropic-ai/claude-agent-sdk": "^0.3.241",
53
53
  "@lydell/node-pty": "^1.2.0-beta.3",
54
- "@openai/codex": "^0.147.0",
54
+ "@openai/codex": "^0.152.1",
55
+ "@seald-io/nedb": "^4.1.2",
55
56
  "imapflow": "^1.3.1",
56
57
  "nodemailer": "^9.0.5",
57
58
  "qrcode-terminal": "^0.12.0",