clay-server 3.8.1 → 4.0.0-beta.1
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.
- package/lib/ask-user-mcp-server.js +5 -119
- package/lib/builtin-mates.js +6 -6
- package/lib/claude-hook-installer.js +15 -0
- package/lib/clay-history-mcp-server.js +62 -297
- package/lib/daemon.js +1 -1
- package/lib/debate-model-selection.js +119 -0
- package/lib/home-debate-tool-policy.js +164 -0
- package/lib/home-response-notifications.js +26 -0
- package/lib/mate-creation-mcp-server.js +55 -0
- package/lib/mate-ready-creation.js +95 -0
- package/lib/mates.js +2 -0
- package/lib/notes.js +2 -1
- package/lib/project-ask-user.js +64 -0
- package/lib/project-capsule-catalog.js +101 -0
- package/lib/project-debate-proposal.js +133 -17
- package/lib/project-debate.js +214 -110
- package/lib/project-delegated-follow-up.js +84 -0
- package/lib/project-delegated-session.js +53 -0
- package/lib/project-filesystem.js +7 -9
- package/lib/project-knowledge.js +1 -0
- package/lib/project-mate-creation-proposal.js +120 -0
- package/lib/project-memory.js +14 -7
- package/lib/project-models.js +157 -32
- package/lib/project-session-notes.js +2 -2
- package/lib/project-session-pair.js +8 -8
- package/lib/project-sessions.js +66 -51
- package/lib/project-worker-proposal.js +25 -25
- package/lib/project-workspace-query.js +144 -0
- package/lib/project.js +329 -102
- package/lib/public/app.js +85 -42
- package/lib/public/css/avatar-imprints.css +0 -1
- package/lib/public/css/base.css +4 -0
- package/lib/public/css/capsule-ui.css +389 -0
- package/lib/public/css/command-palette.css +306 -6
- package/lib/public/css/home-capsule-library.css +243 -0
- package/lib/public/css/home-capsule-source.css +192 -0
- package/lib/public/css/home-debate-live.css +146 -0
- package/lib/public/css/home-debate-planning.css +125 -0
- package/lib/public/css/home-debates-archive.css +281 -0
- package/lib/public/css/home-hub.css +896 -405
- package/lib/public/css/home-mate-creation.css +34 -0
- package/lib/public/css/home-mate-model-picker.css +131 -0
- package/lib/public/css/home-mate-settings.css +344 -0
- package/lib/public/css/home-session-actions.css +265 -0
- package/lib/public/css/home-sidebar.css +667 -0
- package/lib/public/css/icon-strip.css +1 -2
- package/lib/public/css/mates.css +7 -34
- package/lib/public/css/pane.css +1 -1
- package/lib/public/css/playbook.css +0 -80
- package/lib/public/css/sticky-notes.css +53 -117
- package/lib/public/css/tui-attention.css +0 -44
- package/lib/public/css/workspace-assignment.css +45 -0
- package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
- package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
- package/lib/public/index.html +93 -74
- package/lib/public/modules/app-connection.js +10 -1
- package/lib/public/modules/app-dm.js +43 -160
- package/lib/public/modules/app-home-hub.js +328 -585
- package/lib/public/modules/app-message-cards.js +242 -0
- package/lib/public/modules/app-message-router.js +84 -0
- package/lib/public/modules/app-messages.js +78 -20
- package/lib/public/modules/app-notifications.js +4 -2
- package/lib/public/modules/app-projects.js +9 -1
- package/lib/public/modules/app-rendering.js +21 -602
- package/lib/public/modules/avatar-imprint.js +11 -8
- package/lib/public/modules/chat-bubble-renderer.js +152 -0
- package/lib/public/modules/chat-render-runtime.js +198 -0
- package/lib/public/modules/command-palette.js +225 -436
- package/lib/public/modules/dm-render.js +82 -0
- package/lib/public/modules/home-capsule-creation-intent.js +31 -0
- package/lib/public/modules/home-capsule-library.js +146 -0
- package/lib/public/modules/home-capsule-source.js +383 -0
- package/lib/public/modules/home-chat-identity.js +35 -0
- package/lib/public/modules/home-chat-scroll.js +77 -0
- package/lib/public/modules/home-chat-stream-state.js +34 -0
- package/lib/public/modules/home-composer-focus.js +25 -0
- package/lib/public/modules/home-conversations-sheet.js +179 -0
- package/lib/public/modules/home-debate-activity.js +48 -0
- package/lib/public/modules/home-debate-controls.js +295 -0
- package/lib/public/modules/home-debate-launch.js +41 -0
- package/lib/public/modules/home-debate-live.js +284 -0
- package/lib/public/modules/home-debate-models.js +73 -0
- package/lib/public/modules/home-debate-planning.js +335 -0
- package/lib/public/modules/home-debates-archive.js +279 -0
- package/lib/public/modules/home-dock-resize.js +74 -0
- package/lib/public/modules/home-dock.js +446 -0
- package/lib/public/modules/home-mate-chat.js +496 -0
- package/lib/public/modules/home-mate-creation.js +109 -0
- package/lib/public/modules/home-mate-model-picker.js +281 -0
- package/lib/public/modules/home-mate-selection.js +38 -0
- package/lib/public/modules/home-mate-settings-menu.js +131 -0
- package/lib/public/modules/home-mate-settings.js +383 -0
- package/lib/public/modules/home-session-actions.js +373 -0
- package/lib/public/modules/home-session-model-confirmation.js +13 -0
- package/lib/public/modules/home-shell.js +9 -0
- package/lib/public/modules/home-sidebar-chat-list.js +63 -0
- package/lib/public/modules/home-sidebar.js +158 -0
- package/lib/public/modules/home-sub-surface.js +19 -0
- package/lib/public/modules/home-surface-boot.js +68 -0
- package/lib/public/modules/home-surface.js +141 -0
- package/lib/public/modules/home-tools.js +271 -0
- package/lib/public/modules/markdown.js +50 -2
- package/lib/public/modules/mate-management.js +86 -0
- package/lib/public/modules/mate-wizard.js +0 -7
- package/lib/public/modules/mention.js +17 -19
- package/lib/public/modules/profile.js +4 -5
- package/lib/public/modules/project-settings.js +34 -28
- package/lib/public/modules/project-switcher.js +11 -5
- package/lib/public/modules/project-workspace-assignment.js +23 -0
- package/lib/public/modules/search-clay-chat.js +402 -0
- package/lib/public/modules/server-settings.js +14 -12
- package/lib/public/modules/session-actions.js +1 -1
- package/lib/public/modules/sidebar-mates.js +56 -552
- package/lib/public/modules/sidebar-mobile.js +7 -64
- package/lib/public/modules/sidebar-presence.js +37 -0
- package/lib/public/modules/split-pair-ui.js +13 -13
- package/lib/public/modules/sticky-notes.js +7 -6
- package/lib/public/modules/tool-input-composition.js +48 -0
- package/lib/public/modules/tool-llm-status.js +167 -0
- package/lib/public/modules/tool-renderer-advanced.js +285 -0
- package/lib/public/modules/tool-renderer-chart.js +158 -0
- package/lib/public/modules/tool-renderer-semantics.js +35 -0
- package/lib/public/modules/tool-renderer.js +464 -0
- package/lib/public/modules/tool-runtime.js +309 -0
- package/lib/public/modules/tool-ui-evaluator.js +97 -0
- package/lib/public/modules/tool-ui-tree.js +21 -0
- package/lib/public/modules/user-settings.js +16 -23
- package/lib/public/modules/worker-proposal.js +2 -2
- package/lib/public/modules/workspace-assignment-card.js +149 -0
- package/lib/public/modules/workspace-assignment-routing.js +4 -0
- package/lib/public/style.css +13 -2
- package/lib/runtime-env.js +65 -0
- package/lib/sdk-bridge.js +120 -32
- package/lib/sdk-message-processor.js +10 -6
- package/lib/server-home-capsule-creation.js +36 -0
- package/lib/server-home-chat-events.js +287 -0
- package/lib/server-home-chat.js +518 -0
- package/lib/server-home-clay-entry.js +65 -0
- package/lib/server-home-clay-session-links.js +26 -0
- package/lib/server-home-debate-planning.js +214 -0
- package/lib/server-home-debates.js +137 -0
- package/lib/server-home-mate-creation.js +261 -0
- package/lib/server-home-models.js +241 -0
- package/lib/server-home-preferences.js +61 -0
- package/lib/server-mates.js +37 -0
- package/lib/server-tools.js +377 -0
- package/lib/server.js +111 -59
- package/lib/session-pair-mcp-server.js +6 -6
- package/lib/sessions.js +18 -3
- package/lib/tool-capsule-source.js +142 -0
- package/lib/tool-control-mcp-server.js +126 -0
- package/lib/tool-llm.js +48 -0
- package/lib/tool-storage.js +77 -0
- package/lib/tool-ui-spec-advanced.js +82 -0
- package/lib/tool-ui-spec.js +261 -0
- package/lib/tools-registry.js +276 -0
- package/lib/users-home-dock-preferences.js +77 -0
- package/lib/users-home-surface-preferences.js +99 -0
- package/lib/users-preferences.js +4 -7
- package/lib/users.js +12 -0
- package/lib/whats-new-content.js +3 -3
- package/lib/workspace-assignment-service.js +420 -0
- package/lib/workspace-query-mcp-server.js +154 -0
- package/lib/workspace-query-service.js +492 -0
- package/lib/ws-schema.js +86 -12
- package/lib/yoke/adapters/acp.js +2 -1
- package/lib/yoke/adapters/antigravity.js +2 -1
- package/lib/yoke/adapters/claude-worker.js +11 -4
- package/lib/yoke/adapters/claude.js +73 -43
- package/lib/yoke/adapters/codex.js +37 -6
- package/lib/yoke/adapters/kiro.js +2 -1
- package/lib/yoke/claude-user-input.js +38 -0
- package/lib/yoke/codex-user-input.js +64 -0
- package/lib/yoke/complete-once.js +164 -0
- package/lib/yoke/index.js +5 -1
- package/lib/yoke/interface.js +19 -0
- package/lib/yoke/mcp-bridge-server.js +2 -6
- package/lib/yoke/user-input.js +254 -0
- package/package.json +2 -1
- package/lib/public/css/home-chat.css +0 -380
- package/lib/public/modules/home-chat.js +0 -438
- package/lib/server-clay-home.js +0 -245
|
@@ -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 };
|
|
@@ -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
|
};
|
package/lib/yoke/interface.js
CHANGED
|
@@ -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.
|
|
106
|
-
//
|
|
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
|
+
"version": "4.0.0-beta.1",
|
|
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",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"@anthropic-ai/claude-agent-sdk": "^0.3.241",
|
|
53
53
|
"@lydell/node-pty": "^1.2.0-beta.3",
|
|
54
54
|
"@openai/codex": "^0.147.0",
|
|
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",
|