clay-server 2.34.0-beta.3 → 2.34.0-beta.5
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 +120 -0
- package/lib/daemon.js +97 -38
- package/lib/mates.js +2 -2
- package/lib/project-connection.js +15 -0
- package/lib/project-sessions.js +73 -4
- package/lib/project.js +75 -8
- package/lib/public/css/mates.css +26 -11
- package/lib/public/index.html +17 -19
- package/lib/public/modules/app-dm.js +0 -2
- package/lib/public/modules/app-messages.js +2 -0
- package/lib/public/modules/mate-sidebar.js +0 -9
- package/lib/public/modules/mate-wizard.js +15 -15
- package/lib/public/modules/tools.js +20 -2
- package/lib/sdk-bridge.js +23 -19
- package/lib/sdk-message-processor.js +14 -3
- package/lib/server.js +28 -72
- package/lib/sessions.js +67 -20
- package/lib/yoke/adapters/claude-worker.js +108 -0
- package/lib/yoke/adapters/claude.js +17 -3
- package/lib/yoke/adapters/codex.js +318 -54
- package/lib/yoke/index.js +40 -28
- package/lib/yoke/mcp-bridge-server.js +14 -6
- package/package.json +1 -1
|
@@ -31,6 +31,7 @@ var abortController = null;
|
|
|
31
31
|
var pendingPermissions = {}; // requestId -> resolve
|
|
32
32
|
var pendingAskUser = {}; // toolUseId -> resolve
|
|
33
33
|
var pendingElicitations = {}; // requestId -> resolve
|
|
34
|
+
var pendingMcpToolCalls = {}; // requestId -> { resolve, reject }
|
|
34
35
|
var conn = null;
|
|
35
36
|
var buffer = "";
|
|
36
37
|
|
|
@@ -81,6 +82,90 @@ function getSDK() {
|
|
|
81
82
|
return sdkModule;
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
function buildZodShape(z, inputSchema) {
|
|
86
|
+
if (!inputSchema || !inputSchema.properties) return {};
|
|
87
|
+
var shape = {};
|
|
88
|
+
var props = inputSchema.properties;
|
|
89
|
+
var required = inputSchema.required || [];
|
|
90
|
+
var keys = Object.keys(props);
|
|
91
|
+
|
|
92
|
+
for (var i = 0; i < keys.length; i++) {
|
|
93
|
+
var key = keys[i];
|
|
94
|
+
var prop = props[key];
|
|
95
|
+
var field;
|
|
96
|
+
|
|
97
|
+
if (prop.type === "number" || prop.type === "integer") {
|
|
98
|
+
field = z.number();
|
|
99
|
+
} else if (prop.type === "boolean") {
|
|
100
|
+
field = z.boolean();
|
|
101
|
+
} else if (prop.type === "array") {
|
|
102
|
+
field = z.array(z.any());
|
|
103
|
+
} else if (prop.type === "object") {
|
|
104
|
+
field = z.record(z.any());
|
|
105
|
+
} else if (prop.enum) {
|
|
106
|
+
field = z.enum(prop.enum);
|
|
107
|
+
} else {
|
|
108
|
+
field = z.string();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (prop.description) field = field.describe(prop.description);
|
|
112
|
+
if (required.indexOf(key) === -1) field = field.optional();
|
|
113
|
+
shape[key] = field;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return shape;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createWorkerMcpToolHandler(serverName, toolName) {
|
|
120
|
+
return function(args) {
|
|
121
|
+
var requestId = crypto.randomUUID();
|
|
122
|
+
sendToDaemon({
|
|
123
|
+
type: "mcp_tool_call",
|
|
124
|
+
requestId: requestId,
|
|
125
|
+
serverName: serverName,
|
|
126
|
+
toolName: toolName,
|
|
127
|
+
args: args || {},
|
|
128
|
+
});
|
|
129
|
+
return new Promise(function(resolve, reject) {
|
|
130
|
+
pendingMcpToolCalls[requestId] = { resolve: resolve, reject: reject };
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildMcpServersFromDescriptors(descriptors, sdk) {
|
|
136
|
+
if (!descriptors || !descriptors.length) return null;
|
|
137
|
+
var z;
|
|
138
|
+
try { z = require("zod").z; } catch (e) {
|
|
139
|
+
try { z = require("zod"); } catch (e2) { return null; }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
var servers = {};
|
|
143
|
+
for (var i = 0; i < descriptors.length; i++) {
|
|
144
|
+
var descriptor = descriptors[i];
|
|
145
|
+
if (!descriptor || !descriptor.serverName || !descriptor.tools || !descriptor.tools.length) continue;
|
|
146
|
+
var tools = [];
|
|
147
|
+
for (var j = 0; j < descriptor.tools.length; j++) {
|
|
148
|
+
var toolDescriptor = descriptor.tools[j];
|
|
149
|
+
if (!toolDescriptor || !toolDescriptor.name) continue;
|
|
150
|
+
tools.push(sdk.tool(
|
|
151
|
+
toolDescriptor.name,
|
|
152
|
+
toolDescriptor.description || toolDescriptor.name,
|
|
153
|
+
buildZodShape(z, toolDescriptor.inputSchema),
|
|
154
|
+
createWorkerMcpToolHandler(descriptor.serverName, toolDescriptor.name)
|
|
155
|
+
));
|
|
156
|
+
}
|
|
157
|
+
if (tools.length > 0) {
|
|
158
|
+
servers[descriptor.serverName] = sdk.createSdkMcpServer({
|
|
159
|
+
name: descriptor.serverName,
|
|
160
|
+
version: "1.0.0",
|
|
161
|
+
tools: tools,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return Object.keys(servers).length > 0 ? servers : null;
|
|
167
|
+
}
|
|
168
|
+
|
|
84
169
|
// --- IPC helpers ---
|
|
85
170
|
function sendToDaemon(msg) {
|
|
86
171
|
if (!conn || conn.destroyed) return;
|
|
@@ -127,6 +212,9 @@ function handleMessage(msg) {
|
|
|
127
212
|
case "elicitation_response":
|
|
128
213
|
handleElicitationResponse(msg);
|
|
129
214
|
break;
|
|
215
|
+
case "mcp_tool_result":
|
|
216
|
+
handleMcpToolResult(msg);
|
|
217
|
+
break;
|
|
130
218
|
case "warmup":
|
|
131
219
|
handleWarmup(msg);
|
|
132
220
|
break;
|
|
@@ -208,6 +296,17 @@ function handleElicitationResponse(msg) {
|
|
|
208
296
|
}
|
|
209
297
|
}
|
|
210
298
|
|
|
299
|
+
function handleMcpToolResult(msg) {
|
|
300
|
+
var pending = pendingMcpToolCalls[msg.requestId];
|
|
301
|
+
if (!pending) return;
|
|
302
|
+
delete pendingMcpToolCalls[msg.requestId];
|
|
303
|
+
if (msg.error) {
|
|
304
|
+
pending.reject(new Error(msg.error));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
pending.resolve(msg.result);
|
|
308
|
+
}
|
|
309
|
+
|
|
211
310
|
// --- Query handling ---
|
|
212
311
|
async function handleQueryStart(msg) {
|
|
213
312
|
var t0 = msg._perfT0 || Date.now();
|
|
@@ -238,6 +337,15 @@ async function handleQueryStart(msg) {
|
|
|
238
337
|
options.abortController = abortController;
|
|
239
338
|
options.debug = true;
|
|
240
339
|
options.debugFile = "/tmp/clay-cli-debug-" + process.pid + ".log";
|
|
340
|
+
if (options.mcpServerDescriptors && options.mcpServerDescriptors.length) {
|
|
341
|
+
try {
|
|
342
|
+
var mcpServers = buildMcpServersFromDescriptors(options.mcpServerDescriptors, sdk);
|
|
343
|
+
if (mcpServers) options.mcpServers = mcpServers;
|
|
344
|
+
} catch (e) {
|
|
345
|
+
console.error("[sdk-worker] Failed to build MCP servers:", e.message || e);
|
|
346
|
+
}
|
|
347
|
+
delete options.mcpServerDescriptors;
|
|
348
|
+
}
|
|
241
349
|
// Override CLI subprocess spawn to inject NODE_OPTIONS for IPv4-first DNS.
|
|
242
350
|
// The SDK constructs its own env for the CLI process, so worker env vars
|
|
243
351
|
// like NODE_OPTIONS are not inherited. We intercept the spawn to fix this.
|
|
@@ -648,7 +648,7 @@ function cleanupWorker(worker) {
|
|
|
648
648
|
// in-process QueryHandle. This allows processQueryStream to iterate a worker
|
|
649
649
|
// query identically to an in-process query.
|
|
650
650
|
|
|
651
|
-
function createWorkerQueryHandle(worker, canUseTool, onElicitation) {
|
|
651
|
+
function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool) {
|
|
652
652
|
// Async iterable state
|
|
653
653
|
var iterQueue = [];
|
|
654
654
|
var iterWaiting = null;
|
|
@@ -741,6 +741,20 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation) {
|
|
|
741
741
|
}
|
|
742
742
|
break;
|
|
743
743
|
|
|
744
|
+
case "mcp_tool_call":
|
|
745
|
+
if (callMcpTool) {
|
|
746
|
+
callMcpTool(msg.serverName, msg.toolName, msg.args || {}).then(function(result) {
|
|
747
|
+
worker.send({ type: "mcp_tool_result", requestId: msg.requestId, result: result });
|
|
748
|
+
}).catch(function(e) {
|
|
749
|
+
worker.send({
|
|
750
|
+
type: "mcp_tool_result",
|
|
751
|
+
requestId: msg.requestId,
|
|
752
|
+
error: (e && e.message) ? e.message : String(e),
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
break;
|
|
757
|
+
|
|
744
758
|
case "context_usage":
|
|
745
759
|
case "model_changed":
|
|
746
760
|
case "effort_changed":
|
|
@@ -1231,7 +1245,7 @@ function createClaudeAdapter(opts) {
|
|
|
1231
1245
|
}
|
|
1232
1246
|
|
|
1233
1247
|
// Create the worker query handle (sets up message handler on worker)
|
|
1234
|
-
var handle = createWorkerQueryHandle(worker, queryOpts.canUseTool, queryOpts.onElicitation);
|
|
1248
|
+
var handle = createWorkerQueryHandle(worker, queryOpts.canUseTool, queryOpts.onElicitation, queryOpts.callMcpTool);
|
|
1235
1249
|
|
|
1236
1250
|
// Wait for worker to be ready before sending query_start
|
|
1237
1251
|
if (!reusingWorker) {
|
|
@@ -1254,7 +1268,7 @@ function createClaudeAdapter(opts) {
|
|
|
1254
1268
|
if (claudeOpts.allowDangerouslySkipPermissions) queryOptions.allowDangerouslySkipPermissions = true;
|
|
1255
1269
|
if (claudeOpts.settings) queryOptions.settings = claudeOpts.settings;
|
|
1256
1270
|
|
|
1257
|
-
if (queryOpts.
|
|
1271
|
+
if (queryOpts.toolServerDescriptors) queryOptions.mcpServerDescriptors = queryOpts.toolServerDescriptors;
|
|
1258
1272
|
if (queryOpts.model) queryOptions.model = queryOpts.model;
|
|
1259
1273
|
if (queryOpts.effort) queryOptions.effort = queryOpts.effort;
|
|
1260
1274
|
if (queryOpts.resumeSessionId) queryOptions.resume = queryOpts.resumeSessionId;
|
|
@@ -89,6 +89,56 @@ function generateUuid() {
|
|
|
89
89
|
return "codex-" + ts + "-" + cnt + "-" + rnd;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
function waitMs(ms) {
|
|
93
|
+
return new Promise(function(resolve) {
|
|
94
|
+
setTimeout(resolve, ms);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function waitForProcessExit(proc, timeoutMs) {
|
|
99
|
+
return new Promise(function(resolve) {
|
|
100
|
+
if (!proc) {
|
|
101
|
+
resolve(true);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
106
|
+
resolve(true);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
var done = false;
|
|
111
|
+
var timer = null;
|
|
112
|
+
|
|
113
|
+
function cleanup() {
|
|
114
|
+
if (done) return;
|
|
115
|
+
done = true;
|
|
116
|
+
if (timer) clearTimeout(timer);
|
|
117
|
+
proc.removeListener("exit", onDone);
|
|
118
|
+
proc.removeListener("close", onDone);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function onDone() {
|
|
122
|
+
cleanup();
|
|
123
|
+
resolve(true);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
proc.once("exit", onDone);
|
|
127
|
+
proc.once("close", onDone);
|
|
128
|
+
|
|
129
|
+
timer = setTimeout(function() {
|
|
130
|
+
cleanup();
|
|
131
|
+
resolve(false);
|
|
132
|
+
}, timeoutMs || 5000);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function createShutdownError() {
|
|
137
|
+
var err = new Error("Codex adapter is shutting down, retry shortly");
|
|
138
|
+
err.code = "CODEX_ADAPTER_SHUTTING_DOWN";
|
|
139
|
+
return err;
|
|
140
|
+
}
|
|
141
|
+
|
|
92
142
|
function normalizePlanStatus(status) {
|
|
93
143
|
if (status === "inProgress") return "in_progress";
|
|
94
144
|
if (status === "completed") return "completed";
|
|
@@ -310,16 +360,33 @@ function flattenEvent(notification, state) {
|
|
|
310
360
|
state.thinkingBlocks[item.id] = "blk_" + state.blockCounter;
|
|
311
361
|
events.push({ yokeType: "thinking_start", blockId: "blk_" + state.blockCounter });
|
|
312
362
|
}
|
|
313
|
-
|
|
363
|
+
// Codex reasoning items may expose plain text via `text`, a short
|
|
364
|
+
// `summary`, or nested `content` parts. Prefer whichever is present;
|
|
365
|
+
// many turns arrive with only encrypted reasoning and no readable
|
|
366
|
+
// text at all, in which case the UI will hide the expand affordance.
|
|
367
|
+
var reasoningText = "";
|
|
368
|
+
if (typeof item.text === "string" && item.text.length > 0) {
|
|
369
|
+
reasoningText = item.text;
|
|
370
|
+
} else if (typeof item.summary === "string" && item.summary.length > 0) {
|
|
371
|
+
reasoningText = item.summary;
|
|
372
|
+
} else if (Array.isArray(item.content)) {
|
|
373
|
+
var parts = [];
|
|
374
|
+
for (var rpi = 0; rpi < item.content.length; rpi++) {
|
|
375
|
+
var rp = item.content[rpi];
|
|
376
|
+
if (rp && typeof rp.text === "string") parts.push(rp.text);
|
|
377
|
+
}
|
|
378
|
+
reasoningText = parts.join("\n");
|
|
379
|
+
}
|
|
380
|
+
if (reasoningText) {
|
|
314
381
|
var thinkBlockId = state.thinkingBlocks[item.id];
|
|
315
382
|
var prevThinkLen = state.thinkingLengths[item.id] || 0;
|
|
316
|
-
if (
|
|
383
|
+
if (reasoningText.length > prevThinkLen) {
|
|
317
384
|
events.push({
|
|
318
385
|
yokeType: "thinking_delta",
|
|
319
386
|
blockId: thinkBlockId,
|
|
320
|
-
text:
|
|
387
|
+
text: reasoningText.substring(prevThinkLen),
|
|
321
388
|
});
|
|
322
|
-
state.thinkingLengths[item.id] =
|
|
389
|
+
state.thinkingLengths[item.id] = reasoningText.length;
|
|
323
390
|
}
|
|
324
391
|
}
|
|
325
392
|
if (evtPhase === "completed") {
|
|
@@ -503,6 +570,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
503
570
|
var systemPrompt = queryOpts.systemPrompt || "";
|
|
504
571
|
var canUseTool = queryOpts.canUseTool || null;
|
|
505
572
|
var onElicitation = queryOpts.onElicitation || null;
|
|
573
|
+
var onFinished = queryOpts.onFinished || null;
|
|
506
574
|
|
|
507
575
|
// Check if the query was cancelled (either via handle.abort() or direct signal abort)
|
|
508
576
|
function isCancelled() {
|
|
@@ -533,6 +601,19 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
533
601
|
var eventBuffer = [];
|
|
534
602
|
var eventWaiting = null;
|
|
535
603
|
var iteratorDone = false;
|
|
604
|
+
var finishedNotified = false;
|
|
605
|
+
|
|
606
|
+
function notifyFinished() {
|
|
607
|
+
if (finishedNotified) return;
|
|
608
|
+
finishedNotified = true;
|
|
609
|
+
if (typeof onFinished === "function") {
|
|
610
|
+
try {
|
|
611
|
+
onFinished();
|
|
612
|
+
} catch (e) {
|
|
613
|
+
console.error("[yoke/codex] onFinished error:", e.message || e);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
536
617
|
|
|
537
618
|
function pushEvent(evt) {
|
|
538
619
|
if (iteratorDone) return;
|
|
@@ -552,6 +633,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
552
633
|
eventWaiting = null;
|
|
553
634
|
resolve({ value: undefined, done: true });
|
|
554
635
|
}
|
|
636
|
+
notifyFinished();
|
|
555
637
|
}
|
|
556
638
|
|
|
557
639
|
// Message queue for multi-turn
|
|
@@ -936,48 +1018,182 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
936
1018
|
|
|
937
1019
|
function createCodexAdapter(opts) {
|
|
938
1020
|
var _cwd = (opts && opts.cwd) || process.cwd();
|
|
1021
|
+
var _slug = (opts && opts.slug) || "";
|
|
1022
|
+
var _defaultInitOpts = Object.assign({}, opts || {});
|
|
939
1023
|
var _cachedModels = [];
|
|
940
1024
|
var _appServer = null;
|
|
941
1025
|
var _initPromise = null;
|
|
942
|
-
var
|
|
1026
|
+
var _shutdownPromise = null;
|
|
1027
|
+
var _refCount = 0;
|
|
1028
|
+
var _lastActiveAt = Date.now();
|
|
1029
|
+
var _shuttingDown = false;
|
|
1030
|
+
var _activeQueries = [];
|
|
1031
|
+
|
|
1032
|
+
function updateLastActiveAt() {
|
|
1033
|
+
_lastActiveAt = Date.now();
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function registerActiveQuery(entry) {
|
|
1037
|
+
_activeQueries.push(entry);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function removeActiveQuery(entry) {
|
|
1041
|
+
var next = [];
|
|
1042
|
+
for (var i = 0; i < _activeQueries.length; i++) {
|
|
1043
|
+
if (_activeQueries[i] !== entry) next.push(_activeQueries[i]);
|
|
1044
|
+
}
|
|
1045
|
+
_activeQueries = next;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function decrementRefCount() {
|
|
1049
|
+
if (_refCount > 0) {
|
|
1050
|
+
_refCount--;
|
|
1051
|
+
} else {
|
|
1052
|
+
console.error("[yoke/codex] refCount negative, bug!");
|
|
1053
|
+
_refCount = 0;
|
|
1054
|
+
}
|
|
1055
|
+
updateLastActiveAt();
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function buildReadyResponse(skillNames) {
|
|
1059
|
+
return {
|
|
1060
|
+
models: _cachedModels,
|
|
1061
|
+
defaultModel: "gpt-5.4",
|
|
1062
|
+
skills: skillNames || [],
|
|
1063
|
+
slashCommands: skillNames || [],
|
|
1064
|
+
fastModeState: null,
|
|
1065
|
+
capabilities: {
|
|
1066
|
+
thinking: true,
|
|
1067
|
+
betas: false,
|
|
1068
|
+
rewind: false,
|
|
1069
|
+
sessionResume: true,
|
|
1070
|
+
promptSuggestions: true,
|
|
1071
|
+
elicitation: true,
|
|
1072
|
+
fileCheckpointing: false,
|
|
1073
|
+
contextCompacting: false,
|
|
1074
|
+
toolPolicy: ["ask", "allow-all"],
|
|
1075
|
+
},
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function clearRuntimeState() {
|
|
1080
|
+
_appServer = null;
|
|
1081
|
+
_initPromise = null;
|
|
1082
|
+
_cachedModels = [];
|
|
1083
|
+
_refCount = 0;
|
|
1084
|
+
_activeQueries = [];
|
|
1085
|
+
updateLastActiveAt();
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
function waitForRefCount(targetCount, timeoutMs) {
|
|
1089
|
+
var deadline = Date.now() + (timeoutMs || 5000);
|
|
1090
|
+
return new Promise(function(resolve) {
|
|
1091
|
+
function tick() {
|
|
1092
|
+
if (_refCount <= targetCount) {
|
|
1093
|
+
resolve(true);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
if (Date.now() >= deadline) {
|
|
1097
|
+
resolve(false);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
setTimeout(tick, 50);
|
|
1101
|
+
}
|
|
1102
|
+
tick();
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
function stopAppServer(deadlineMs) {
|
|
1107
|
+
var proc = _appServer && _appServer.proc ? _appServer.proc : null;
|
|
1108
|
+
if (!_appServer) return Promise.resolve(true);
|
|
1109
|
+
try {
|
|
1110
|
+
_appServer.stop();
|
|
1111
|
+
} catch (e) {
|
|
1112
|
+
console.error("[yoke/codex] App-server stop error:", e.message || e);
|
|
1113
|
+
}
|
|
1114
|
+
if (!proc) return Promise.resolve(true);
|
|
1115
|
+
var remaining = (typeof deadlineMs === "number") ? Math.max(0, deadlineMs - Date.now()) : 5000;
|
|
1116
|
+
return waitForProcessExit(proc, remaining).then(function(exited) {
|
|
1117
|
+
if (!exited) {
|
|
1118
|
+
try {
|
|
1119
|
+
proc.kill("SIGKILL");
|
|
1120
|
+
} catch (e) {}
|
|
1121
|
+
}
|
|
1122
|
+
return exited;
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function beginShutdown(force, idleMs) {
|
|
1127
|
+
if (_shutdownPromise) return _shutdownPromise;
|
|
1128
|
+
if (_shuttingDown) return null;
|
|
1129
|
+
|
|
1130
|
+
_shuttingDown = true;
|
|
1131
|
+
|
|
1132
|
+
_shutdownPromise = (async function() {
|
|
1133
|
+
var deadline = Date.now() + 5000;
|
|
1134
|
+
var shouldAbort = !!force;
|
|
1135
|
+
|
|
1136
|
+
if (_initPromise) {
|
|
1137
|
+
try {
|
|
1138
|
+
await Promise.race([
|
|
1139
|
+
_initPromise.catch(function() { return null; }),
|
|
1140
|
+
waitMs(Math.max(0, deadline - Date.now())),
|
|
1141
|
+
]);
|
|
1142
|
+
} catch (e) {}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
if (shouldAbort && _activeQueries.length > 0) {
|
|
1146
|
+
var active = _activeQueries.slice();
|
|
1147
|
+
for (var i = 0; i < active.length; i++) {
|
|
1148
|
+
try {
|
|
1149
|
+
if (active[i] && active[i].abort) active[i].abort();
|
|
1150
|
+
} catch (e) {}
|
|
1151
|
+
}
|
|
1152
|
+
await waitForRefCount(0, Math.max(0, deadline - Date.now()));
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
if (_appServer) {
|
|
1156
|
+
await stopAppServer(deadline);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
clearRuntimeState();
|
|
1160
|
+
_shuttingDown = false;
|
|
1161
|
+
_shutdownPromise = null;
|
|
1162
|
+
return true;
|
|
1163
|
+
})().catch(function(err) {
|
|
1164
|
+
clearRuntimeState();
|
|
1165
|
+
_shuttingDown = false;
|
|
1166
|
+
_shutdownPromise = null;
|
|
1167
|
+
throw err;
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
return _shutdownPromise;
|
|
1171
|
+
}
|
|
943
1172
|
|
|
944
1173
|
var adapter = {
|
|
945
1174
|
vendor: "codex",
|
|
946
1175
|
|
|
947
1176
|
init: function(initOpts) {
|
|
1177
|
+
if (_shuttingDown) {
|
|
1178
|
+
return Promise.reject(createShutdownError());
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
var effectiveInitOpts = Object.assign({}, _defaultInitOpts, initOpts || {});
|
|
1182
|
+
|
|
948
1183
|
// Already initialized - return cached result
|
|
949
1184
|
if (_appServer && _appServer.started && _cachedModels.length > 0) {
|
|
950
|
-
return Promise.resolve(
|
|
951
|
-
models: _cachedModels,
|
|
952
|
-
defaultModel: "gpt-5.4",
|
|
953
|
-
skills: [],
|
|
954
|
-
slashCommands: [],
|
|
955
|
-
fastModeState: null,
|
|
956
|
-
capabilities: {
|
|
957
|
-
thinking: true,
|
|
958
|
-
betas: false,
|
|
959
|
-
rewind: false,
|
|
960
|
-
sessionResume: true,
|
|
961
|
-
promptSuggestions: true,
|
|
962
|
-
elicitation: true,
|
|
963
|
-
fileCheckpointing: false,
|
|
964
|
-
contextCompacting: false,
|
|
965
|
-
toolPolicy: ["ask", "allow-all"],
|
|
966
|
-
},
|
|
967
|
-
});
|
|
1185
|
+
return Promise.resolve(buildReadyResponse([]));
|
|
968
1186
|
}
|
|
969
1187
|
|
|
970
1188
|
// Deduplicate concurrent init calls
|
|
971
1189
|
if (_initPromise) return _initPromise;
|
|
972
1190
|
|
|
973
1191
|
_initPromise = (async function() {
|
|
974
|
-
_initOpts = initOpts;
|
|
975
|
-
|
|
976
1192
|
var serverOpts = { cwd: _cwd };
|
|
977
1193
|
|
|
978
1194
|
// Extract adapter options
|
|
979
|
-
if (
|
|
980
|
-
var co =
|
|
1195
|
+
if (effectiveInitOpts && effectiveInitOpts.adapterOptions && effectiveInitOpts.adapterOptions.CODEX) {
|
|
1196
|
+
var co = effectiveInitOpts.adapterOptions.CODEX;
|
|
981
1197
|
if (co.apiKey) serverOpts.env = Object.assign({}, serverOpts.env || {}, { OPENAI_API_KEY: co.apiKey });
|
|
982
1198
|
if (co.baseUrl) serverOpts.env = Object.assign({}, serverOpts.env || {}, { OPENAI_BASE_URL: co.baseUrl });
|
|
983
1199
|
if (co.config) serverOpts.config = co.config;
|
|
@@ -1005,10 +1221,10 @@ function createCodexAdapter(opts) {
|
|
|
1005
1221
|
|
|
1006
1222
|
// Track 2: Add clay-tools bridge server for in-app + remote MCP tools.
|
|
1007
1223
|
var bridgePath = require("path").join(__dirname, "..", "mcp-bridge-server.js");
|
|
1008
|
-
var clayPort =
|
|
1009
|
-
var clayTls =
|
|
1010
|
-
var clayAuthToken =
|
|
1011
|
-
var claySlug =
|
|
1224
|
+
var clayPort = effectiveInitOpts.clayPort || process.env.CLAY_PORT || 2633;
|
|
1225
|
+
var clayTls = effectiveInitOpts.clayTls || false;
|
|
1226
|
+
var clayAuthToken = effectiveInitOpts.clayAuthToken || "";
|
|
1227
|
+
var claySlug = effectiveInitOpts.slug || _slug || "";
|
|
1012
1228
|
try {
|
|
1013
1229
|
if (require("fs").existsSync(bridgePath)) {
|
|
1014
1230
|
var bridgeArgs = [bridgePath, "--port", String(clayPort), "--slug", claySlug];
|
|
@@ -1049,6 +1265,11 @@ function createCodexAdapter(opts) {
|
|
|
1049
1265
|
});
|
|
1050
1266
|
_appServer.notify("initialized", {});
|
|
1051
1267
|
|
|
1268
|
+
if (_shuttingDown) {
|
|
1269
|
+
await stopAppServer(Date.now() + 1000);
|
|
1270
|
+
throw createShutdownError();
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1052
1273
|
console.log("[codex] App-server initialized, models: gpt-5.4, gpt-5.4-mini, gpt-5.3-codex, gpt-5.3-codex-spark, gpt-5.2");
|
|
1053
1274
|
|
|
1054
1275
|
_cachedModels = [
|
|
@@ -1097,26 +1318,15 @@ function createCodexAdapter(opts) {
|
|
|
1097
1318
|
console.error("[codex] Failed to discover skills:", e.message);
|
|
1098
1319
|
}
|
|
1099
1320
|
|
|
1321
|
+
if (_shuttingDown) {
|
|
1322
|
+
await stopAppServer(Date.now() + 1000);
|
|
1323
|
+
throw createShutdownError();
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1100
1326
|
_initPromise = null;
|
|
1327
|
+
updateLastActiveAt();
|
|
1101
1328
|
|
|
1102
|
-
return
|
|
1103
|
-
models: _cachedModels,
|
|
1104
|
-
defaultModel: "gpt-5.4",
|
|
1105
|
-
skills: skillNames,
|
|
1106
|
-
slashCommands: skillNames,
|
|
1107
|
-
fastModeState: null,
|
|
1108
|
-
capabilities: {
|
|
1109
|
-
thinking: true,
|
|
1110
|
-
betas: false,
|
|
1111
|
-
rewind: false,
|
|
1112
|
-
sessionResume: true,
|
|
1113
|
-
promptSuggestions: true,
|
|
1114
|
-
elicitation: true,
|
|
1115
|
-
fileCheckpointing: false,
|
|
1116
|
-
contextCompacting: false,
|
|
1117
|
-
toolPolicy: ["ask", "allow-all"],
|
|
1118
|
-
},
|
|
1119
|
-
};
|
|
1329
|
+
return buildReadyResponse(skillNames);
|
|
1120
1330
|
})();
|
|
1121
1331
|
|
|
1122
1332
|
return _initPromise;
|
|
@@ -1134,12 +1344,31 @@ function createCodexAdapter(opts) {
|
|
|
1134
1344
|
},
|
|
1135
1345
|
|
|
1136
1346
|
createQuery: async function(queryOpts) {
|
|
1347
|
+
if (_shuttingDown) {
|
|
1348
|
+
throw createShutdownError();
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
if (!_appServer || !_appServer.started) {
|
|
1352
|
+
await adapter.init(queryOpts || {});
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
if (_shuttingDown) {
|
|
1356
|
+
throw createShutdownError();
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1137
1359
|
if (!_appServer || !_appServer.started) {
|
|
1138
1360
|
throw new Error("[yoke/codex] Adapter not initialized. Call init() first.");
|
|
1139
1361
|
}
|
|
1140
1362
|
|
|
1141
1363
|
var model = queryOpts.model || "gpt-5.4";
|
|
1142
1364
|
var ac = queryOpts.abortController || new AbortController();
|
|
1365
|
+
var activeEntry = {
|
|
1366
|
+
abort: function() {
|
|
1367
|
+
try {
|
|
1368
|
+
ac.abort();
|
|
1369
|
+
} catch (e) {}
|
|
1370
|
+
},
|
|
1371
|
+
};
|
|
1143
1372
|
|
|
1144
1373
|
// Map YOKE options to Codex thread options
|
|
1145
1374
|
var codexOpts = (queryOpts.adapterOptions && queryOpts.adapterOptions.CODEX) || {};
|
|
@@ -1176,7 +1405,33 @@ function createCodexAdapter(opts) {
|
|
|
1176
1405
|
|
|
1177
1406
|
console.log("[yoke/codex] createQuery: model=" + model + " approval=" + handleOpts.approvalPolicy + " sandbox=" + handleOpts.sandboxMode);
|
|
1178
1407
|
|
|
1179
|
-
|
|
1408
|
+
_refCount++;
|
|
1409
|
+
registerActiveQuery(activeEntry);
|
|
1410
|
+
|
|
1411
|
+
var handle;
|
|
1412
|
+
try {
|
|
1413
|
+
handleOpts.onFinished = function() {
|
|
1414
|
+
removeActiveQuery(activeEntry);
|
|
1415
|
+
decrementRefCount();
|
|
1416
|
+
};
|
|
1417
|
+
handle = createCodexQueryHandle(_appServer, handleOpts);
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
removeActiveQuery(activeEntry);
|
|
1420
|
+
decrementRefCount();
|
|
1421
|
+
throw e;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
activeEntry.handle = handle;
|
|
1425
|
+
activeEntry.abort = function() {
|
|
1426
|
+
try {
|
|
1427
|
+
if (handle && typeof handle.abort === "function") {
|
|
1428
|
+
handle.abort();
|
|
1429
|
+
} else {
|
|
1430
|
+
ac.abort();
|
|
1431
|
+
}
|
|
1432
|
+
} catch (e) {}
|
|
1433
|
+
};
|
|
1434
|
+
|
|
1180
1435
|
return handle;
|
|
1181
1436
|
},
|
|
1182
1437
|
|
|
@@ -1243,10 +1498,19 @@ function createCodexAdapter(opts) {
|
|
|
1243
1498
|
|
|
1244
1499
|
// Shutdown the app-server process
|
|
1245
1500
|
shutdown: function() {
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1501
|
+
return beginShutdown(true);
|
|
1502
|
+
},
|
|
1503
|
+
|
|
1504
|
+
shutdownIfIdle: function(idleMs) {
|
|
1505
|
+
if (_shuttingDown || _shutdownPromise) return Promise.resolve(false);
|
|
1506
|
+
if (_initPromise) return Promise.resolve(false);
|
|
1507
|
+
if (!_appServer) return Promise.resolve(false);
|
|
1508
|
+
if (_refCount > 0) return Promise.resolve(false);
|
|
1509
|
+
if (Date.now() - _lastActiveAt < (idleMs || 0)) return Promise.resolve(false);
|
|
1510
|
+
return beginShutdown(false).then(function() {
|
|
1511
|
+
console.log("[yoke/codex] Reclaimed idle adapter for project " + (_slug || _cwd));
|
|
1512
|
+
return true;
|
|
1513
|
+
});
|
|
1250
1514
|
},
|
|
1251
1515
|
};
|
|
1252
1516
|
|