clay-server 3.4.0-beta.2 → 3.4.0-beta.20
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/daemon-projects.js +3 -3
- package/lib/daemon.js +59 -59
- package/lib/project-connection.js +35 -7
- package/lib/project-debate.js +4 -0
- package/lib/project-mate-interaction.js +21 -1
- package/lib/project-memory.js +3 -0
- package/lib/project-models.js +220 -0
- package/lib/project-session-handoff.js +162 -0
- package/lib/project-sessions.js +13 -45
- package/lib/project-worker-proposal.js +49 -14
- package/lib/project.js +38 -80
- package/lib/public/antigravity-avatar.png +0 -0
- package/lib/public/app.js +29 -0
- package/lib/public/copilot-avatar.svg +5 -0
- package/lib/public/css/command-palette.css +22 -2
- package/lib/public/css/icon-strip.css +29 -13
- package/lib/public/css/input.css +56 -0
- package/lib/public/css/mates.css +6 -0
- package/lib/public/css/menus.css +38 -25
- package/lib/public/css/messages.css +9 -0
- package/lib/public/css/pane.css +16 -3
- package/lib/public/css/pwa-mobile.css +17 -0
- package/lib/public/css/session-actions.css +98 -0
- package/lib/public/css/title-bar.css +19 -0
- package/lib/public/grok-avatar.svg +4 -0
- package/lib/public/index.html +29 -7
- package/lib/public/junie-avatar.svg +11 -0
- package/lib/public/kimi-avatar.svg +4 -0
- package/lib/public/modules/agent-config-selects.js +69 -0
- package/lib/public/modules/app-header.js +4 -22
- package/lib/public/modules/app-messages.js +54 -11
- package/lib/public/modules/app-panels.js +36 -83
- package/lib/public/modules/app-projects.js +3 -1
- package/lib/public/modules/app-rendering.js +19 -4
- package/lib/public/modules/background-tasks-ui.js +55 -0
- package/lib/public/modules/mate-sidebar.js +11 -3
- package/lib/public/modules/model-picker.js +263 -0
- package/lib/public/modules/notifications.js +7 -1
- package/lib/public/modules/pane-bridge.js +11 -0
- package/lib/public/modules/pane-links.js +23 -0
- package/lib/public/modules/project-switcher.js +7 -6
- package/lib/public/modules/session-actions.js +294 -0
- package/lib/public/modules/sidebar-mates.js +1 -1
- package/lib/public/modules/sidebar-mobile.js +2 -1
- package/lib/public/modules/sidebar-projects.js +34 -43
- package/lib/public/modules/sidebar-sessions.js +20 -6
- package/lib/public/modules/split-pair-ui.js +16 -77
- package/lib/public/modules/split-view.js +3 -0
- package/lib/public/modules/tools.js +6 -1
- package/lib/public/modules/vendor-priority.js +14 -0
- package/lib/public/modules/vendor-selection.js +20 -0
- package/lib/public/modules/worker-proposal.js +5 -1
- package/lib/public/modules/worktree-location.js +17 -0
- package/lib/public/qwen-avatar.svg +4 -0
- package/lib/public/style.css +4 -2
- package/lib/sdk-bridge.js +99 -56
- package/lib/sdk-message-processor.js +26 -4
- package/lib/session-handoff-context.js +110 -0
- package/lib/session-notes-mcp-server.js +8 -1
- package/lib/sessions.js +4 -0
- package/lib/worktree.js +9 -4
- package/lib/ws-schema.js +9 -1
- package/lib/yoke/acp-agent-profiles.js +64 -14
- package/lib/yoke/adapters/antigravity.js +417 -0
- package/lib/yoke/adapters/claude.js +34 -0
- package/lib/yoke/adapters/codex.js +101 -5
- package/lib/yoke/adapters/copilot.js +7 -0
- package/lib/yoke/adapters/grok.js +7 -0
- package/lib/yoke/adapters/junie.js +7 -0
- package/lib/yoke/adapters/kimi.js +7 -0
- package/lib/yoke/adapters/kiro.js +2 -2
- package/lib/yoke/adapters/qwen.js +7 -0
- package/lib/yoke/codex-app-server.js +25 -8
- package/lib/yoke/index.js +67 -28
- package/lib/yoke/instructions.js +3 -0
- package/lib/yoke/interface.js +12 -0
- package/lib/yoke/vendor-registry.js +61 -6
- package/package.json +1 -1
- package/lib/public/gemini-avatar.svg +0 -11
- package/lib/yoke/adapters/gemini.js +0 -7
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
var childProcess = require("child_process");
|
|
2
|
+
var readline = require("readline");
|
|
3
|
+
var skillDiscovery = require("../skill-discovery");
|
|
4
|
+
|
|
5
|
+
function findBinary() {
|
|
6
|
+
if (process.env.ANTIGRAVITY_CLI_PATH) return process.env.ANTIGRAVITY_CLI_PATH;
|
|
7
|
+
try {
|
|
8
|
+
var command = process.platform === "win32" ? "where" : "which";
|
|
9
|
+
return childProcess.execFileSync(command, ["agy"], {
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
timeout: 3000,
|
|
12
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
13
|
+
}).trim().split(/\r?\n/)[0] || null;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function modelValue(model) {
|
|
20
|
+
if (typeof model === "string") return model;
|
|
21
|
+
return model && (model.id || model.slug || model.value || model.model);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseModels(stdout) {
|
|
25
|
+
try {
|
|
26
|
+
var parsed = JSON.parse(String(stdout || ""));
|
|
27
|
+
var list = Array.isArray(parsed) ? parsed : (parsed.models || parsed.data || []);
|
|
28
|
+
var models = [];
|
|
29
|
+
for (var i = 0; i < list.length; i++) {
|
|
30
|
+
var value = modelValue(list[i]);
|
|
31
|
+
if (value && models.indexOf(value) === -1) models.push(value);
|
|
32
|
+
}
|
|
33
|
+
return models;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
var lines = String(stdout || "").split(/\r?\n/);
|
|
36
|
+
var fallback = [];
|
|
37
|
+
for (var j = 0; j < lines.length; j++) {
|
|
38
|
+
var match = lines[j].trim().match(/^([^\s]+)\s+/);
|
|
39
|
+
if (match && fallback.indexOf(match[1]) === -1) fallback.push(match[1]);
|
|
40
|
+
}
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function fetchModels(binaryPath, cwd) {
|
|
46
|
+
return new Promise(function(resolve) {
|
|
47
|
+
childProcess.execFile(binaryPath, ["models", "--output-format", "json"], {
|
|
48
|
+
cwd: cwd || process.cwd(),
|
|
49
|
+
timeout: 20000,
|
|
50
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
51
|
+
}, function(err, stdout) {
|
|
52
|
+
if (err || !stdout) { resolve([]); return; }
|
|
53
|
+
resolve(parseModels(stdout));
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isAuthError(value) {
|
|
59
|
+
return /authentication required|not authenticated|sign in|log ?in|credentials|unauthorized|forbidden|\b401\b/i.test(String(value || ""));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function toolName(name) {
|
|
63
|
+
var value = String(name || "").toLowerCase();
|
|
64
|
+
if (value === "run_command" || value === "shell" || value === "bash") return "Bash";
|
|
65
|
+
if (value.indexOf("read") !== -1) return "Read";
|
|
66
|
+
if (value.indexOf("write") !== -1 || value.indexOf("create") !== -1) return "Write";
|
|
67
|
+
if (value.indexOf("edit") !== -1 || value.indexOf("replace") !== -1 || value.indexOf("delete") !== -1) return "Edit";
|
|
68
|
+
if (value.indexOf("search_web") !== -1 || value.indexOf("web_search") !== -1) return "WebSearch";
|
|
69
|
+
if (value.indexOf("fetch") !== -1 || value.indexOf("url") !== -1) return "WebFetch";
|
|
70
|
+
if (value.indexOf("search") !== -1 || value.indexOf("grep") !== -1) return "Grep";
|
|
71
|
+
if (value.indexOf("glob") !== -1 || value.indexOf("find") !== -1) return "Glob";
|
|
72
|
+
if (value.indexOf("subagent") !== -1 || value.indexOf("task") !== -1) return "Task";
|
|
73
|
+
return name || "Tool";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function createAntigravityQueryHandle(binaryPath, queryOpts, onFinished) {
|
|
77
|
+
var processHandle = null;
|
|
78
|
+
var outputReader = null;
|
|
79
|
+
var events = [];
|
|
80
|
+
var eventWaiter = null;
|
|
81
|
+
var messages = [];
|
|
82
|
+
var started = false;
|
|
83
|
+
var activeTurn = false;
|
|
84
|
+
var ended = false;
|
|
85
|
+
var inputEnded = false;
|
|
86
|
+
var finished = false;
|
|
87
|
+
var stderr = "";
|
|
88
|
+
var sessionId = queryOpts.resumeSessionId || null;
|
|
89
|
+
var model = queryOpts.model || "auto";
|
|
90
|
+
var effort = queryOpts.effort || null;
|
|
91
|
+
var toolPolicy = queryOpts.dangerouslySkipPermissions ? "allow-all" : "ask";
|
|
92
|
+
var blockCounter = 0;
|
|
93
|
+
var textBlockId = null;
|
|
94
|
+
var toolBlocks = {};
|
|
95
|
+
var latestUsage = null;
|
|
96
|
+
var firstMessage = true;
|
|
97
|
+
|
|
98
|
+
function notifyFinished() {
|
|
99
|
+
if (finished) return;
|
|
100
|
+
finished = true;
|
|
101
|
+
if (typeof onFinished === "function") onFinished();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function pushEvent(event) {
|
|
105
|
+
if (ended) return;
|
|
106
|
+
if (eventWaiter) {
|
|
107
|
+
var resolve = eventWaiter;
|
|
108
|
+
eventWaiter = null;
|
|
109
|
+
resolve({ value: event, done: false });
|
|
110
|
+
} else {
|
|
111
|
+
events.push(event);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function endEvents() {
|
|
116
|
+
if (ended) return;
|
|
117
|
+
ended = true;
|
|
118
|
+
if (eventWaiter) {
|
|
119
|
+
var resolve = eventWaiter;
|
|
120
|
+
eventWaiter = null;
|
|
121
|
+
resolve({ value: undefined, done: true });
|
|
122
|
+
}
|
|
123
|
+
notifyFinished();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildPrompt(text, images) {
|
|
127
|
+
var prompt = text || "";
|
|
128
|
+
if (firstMessage) {
|
|
129
|
+
var systemParts = [queryOpts.systemPrompt, queryOpts.appendSystemPrompt].filter(function(part) { return !!part; });
|
|
130
|
+
if (systemParts.length) prompt = systemParts.join("\n\n") + "\n\n" + prompt;
|
|
131
|
+
firstMessage = false;
|
|
132
|
+
}
|
|
133
|
+
if (images && images.length) {
|
|
134
|
+
prompt += "\n\n[Clay could not forward " + images.length + " attached image(s) because Antigravity CLI stream input currently accepts text only.]";
|
|
135
|
+
}
|
|
136
|
+
return prompt;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function writeNextMessage() {
|
|
140
|
+
if (!processHandle || !processHandle.stdin || activeTurn || !messages.length) return;
|
|
141
|
+
activeTurn = true;
|
|
142
|
+
textBlockId = null;
|
|
143
|
+
toolBlocks = {};
|
|
144
|
+
pushEvent({ yokeType: "turn_start", messageType: "user" });
|
|
145
|
+
processHandle.stdin.write(JSON.stringify({
|
|
146
|
+
event: "user",
|
|
147
|
+
message: { content: messages.shift() },
|
|
148
|
+
}) + "\n");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function finishTurn(result) {
|
|
152
|
+
result = result || {};
|
|
153
|
+
if (result.conversation_id) sessionId = result.conversation_id;
|
|
154
|
+
if (result.usage) latestUsage = result.usage;
|
|
155
|
+
var status = result.status || "SUCCESS";
|
|
156
|
+
if (status !== "SUCCESS") {
|
|
157
|
+
if (status === "CANCELED" || status === "INTERRUPTED") {
|
|
158
|
+
pushEvent({ yokeType: "interrupted" });
|
|
159
|
+
} else {
|
|
160
|
+
var errorText = result.error || "Antigravity CLI ended the turn with status " + status;
|
|
161
|
+
pushEvent(isAuthError(errorText)
|
|
162
|
+
? { yokeType: "auth_required", vendor: "antigravity" }
|
|
163
|
+
: { yokeType: "error", text: errorText });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
pushEvent({
|
|
167
|
+
yokeType: "result",
|
|
168
|
+
messageType: "assistant",
|
|
169
|
+
cost: null,
|
|
170
|
+
duration: typeof result.duration_seconds === "number" ? result.duration_seconds * 1000 : null,
|
|
171
|
+
usage: latestUsage ? {
|
|
172
|
+
input_tokens: latestUsage.input_tokens || 0,
|
|
173
|
+
output_tokens: latestUsage.output_tokens || 0,
|
|
174
|
+
cache_read_input_tokens: latestUsage.cache_read_tokens || 0,
|
|
175
|
+
cache_creation_input_tokens: 0,
|
|
176
|
+
} : null,
|
|
177
|
+
sessionId: sessionId,
|
|
178
|
+
lastStreamInputTokens: latestUsage ? latestUsage.input_tokens : null,
|
|
179
|
+
});
|
|
180
|
+
activeTurn = false;
|
|
181
|
+
if (messages.length) writeNextMessage();
|
|
182
|
+
else if (inputEnded && processHandle && processHandle.stdin) processHandle.stdin.end();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleStep(step) {
|
|
186
|
+
if (!step) return;
|
|
187
|
+
if (step.step_type === "agent_response") {
|
|
188
|
+
if (!textBlockId) {
|
|
189
|
+
textBlockId = "agy_blk_" + (++blockCounter);
|
|
190
|
+
pushEvent({ yokeType: "text_start", blockId: textBlockId });
|
|
191
|
+
}
|
|
192
|
+
if (step.text_delta) pushEvent({ yokeType: "text_delta", blockId: textBlockId, text: step.text_delta });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (step.step_type !== "tool") return;
|
|
196
|
+
var info = step.tool_info || {};
|
|
197
|
+
var id = "agy_tool_" + step.step_index;
|
|
198
|
+
var name = toolName(step.tool_name || info.name);
|
|
199
|
+
if (!toolBlocks[id]) {
|
|
200
|
+
toolBlocks[id] = "agy_blk_" + (++blockCounter);
|
|
201
|
+
pushEvent({ yokeType: "tool_start", blockId: toolBlocks[id], toolId: id, toolName: name });
|
|
202
|
+
pushEvent({ yokeType: "tool_executing", blockId: toolBlocks[id], toolId: id, toolName: name, input: info.parameters || {} });
|
|
203
|
+
}
|
|
204
|
+
if (step.state === "DONE") {
|
|
205
|
+
var error = info.error;
|
|
206
|
+
pushEvent({
|
|
207
|
+
yokeType: "tool_result",
|
|
208
|
+
blockId: toolBlocks[id],
|
|
209
|
+
toolId: id,
|
|
210
|
+
content: error ? (error.message || String(error)) : (info.output || ""),
|
|
211
|
+
isError: !!error,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function handleOutput(line) {
|
|
217
|
+
var event;
|
|
218
|
+
try { event = JSON.parse(line); } catch (e) { return; }
|
|
219
|
+
if (event.event === "init") {
|
|
220
|
+
sessionId = event.conversation_id || (event.init && event.init.conversation_id) || sessionId;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (event.event === "step_update") {
|
|
224
|
+
handleStep(event.step_update);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (event.event === "result") finishTurn(event.result);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function start() {
|
|
231
|
+
if (started || ended) return;
|
|
232
|
+
started = true;
|
|
233
|
+
var args = ["--input-format", "stream-json", "--output-format", "stream-json"];
|
|
234
|
+
if (sessionId) args.push("--conversation", sessionId);
|
|
235
|
+
if (model && model !== "auto") args.push("--model", model);
|
|
236
|
+
if (effort) args.push("--effort", effort);
|
|
237
|
+
if (toolPolicy === "allow-all") args.push("--dangerously-skip-permissions");
|
|
238
|
+
var spawnProcess = queryOpts._spawn || childProcess.spawn;
|
|
239
|
+
processHandle = spawnProcess(binaryPath, args, {
|
|
240
|
+
cwd: queryOpts.cwd || process.cwd(),
|
|
241
|
+
env: Object.assign({}, process.env, queryOpts.env || {}),
|
|
242
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
243
|
+
});
|
|
244
|
+
outputReader = readline.createInterface({ input: processHandle.stdout });
|
|
245
|
+
outputReader.on("line", handleOutput);
|
|
246
|
+
processHandle.stderr.on("data", function(chunk) {
|
|
247
|
+
stderr += String(chunk);
|
|
248
|
+
if (stderr.length > 65536) stderr = stderr.slice(-65536);
|
|
249
|
+
});
|
|
250
|
+
processHandle.on("error", function(err) {
|
|
251
|
+
pushEvent({ yokeType: "error", text: "Failed to start Antigravity CLI: " + err.message });
|
|
252
|
+
endEvents();
|
|
253
|
+
});
|
|
254
|
+
processHandle.on("exit", function(code, signal) {
|
|
255
|
+
if (!ended && activeTurn) {
|
|
256
|
+
var message = stderr.trim() || "Antigravity CLI exited before completing the turn";
|
|
257
|
+
pushEvent(isAuthError(message)
|
|
258
|
+
? { yokeType: "auth_required", vendor: "antigravity" }
|
|
259
|
+
: { yokeType: "error", text: message + (code ? " (exit " + code + ")" : signal ? " (" + signal + ")" : "") });
|
|
260
|
+
}
|
|
261
|
+
endEvents();
|
|
262
|
+
});
|
|
263
|
+
writeNextMessage();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
var handle = {
|
|
267
|
+
[Symbol.asyncIterator]: function() {
|
|
268
|
+
return {
|
|
269
|
+
next: function() {
|
|
270
|
+
if (events.length) return Promise.resolve({ value: events.shift(), done: false });
|
|
271
|
+
if (ended) return Promise.resolve({ value: undefined, done: true });
|
|
272
|
+
return new Promise(function(resolve) { eventWaiter = resolve; });
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
},
|
|
276
|
+
pushMessage: function(text, images) {
|
|
277
|
+
if (ended || inputEnded) return false;
|
|
278
|
+
messages.push(buildPrompt(text, images));
|
|
279
|
+
if (!started) start();
|
|
280
|
+
else writeNextMessage();
|
|
281
|
+
return true;
|
|
282
|
+
},
|
|
283
|
+
setModel: function(value) {
|
|
284
|
+
if (started) return Promise.reject(new Error("Antigravity CLI cannot switch models after a streaming session starts"));
|
|
285
|
+
model = value || "auto";
|
|
286
|
+
return Promise.resolve();
|
|
287
|
+
},
|
|
288
|
+
setEffort: function(value) {
|
|
289
|
+
if (started) return Promise.reject(new Error("Antigravity CLI cannot switch effort after a streaming session starts"));
|
|
290
|
+
effort = value || null;
|
|
291
|
+
return Promise.resolve();
|
|
292
|
+
},
|
|
293
|
+
setToolPolicy: function(policy) {
|
|
294
|
+
if (started) return Promise.reject(new Error("Antigravity CLI cannot switch tool policy after a streaming session starts"));
|
|
295
|
+
toolPolicy = policy === "allow-all" ? "allow-all" : "ask";
|
|
296
|
+
return Promise.resolve();
|
|
297
|
+
},
|
|
298
|
+
stopTask: function() { return Promise.resolve(); },
|
|
299
|
+
getContextUsage: function() {
|
|
300
|
+
if (!latestUsage) return Promise.resolve(null);
|
|
301
|
+
return Promise.resolve({
|
|
302
|
+
input_tokens: (latestUsage.input_tokens || 0) + (latestUsage.cache_read_tokens || 0),
|
|
303
|
+
contextWindow: null,
|
|
304
|
+
});
|
|
305
|
+
},
|
|
306
|
+
endInput: function() {
|
|
307
|
+
inputEnded = true;
|
|
308
|
+
if (!activeTurn && processHandle && processHandle.stdin) processHandle.stdin.end();
|
|
309
|
+
},
|
|
310
|
+
abort: function() {
|
|
311
|
+
if (ended) return;
|
|
312
|
+
pushEvent({ yokeType: "interrupted" });
|
|
313
|
+
if (processHandle) processHandle.kill("SIGINT");
|
|
314
|
+
endEvents();
|
|
315
|
+
},
|
|
316
|
+
close: function() {
|
|
317
|
+
inputEnded = true;
|
|
318
|
+
if (processHandle && processHandle.stdin) processHandle.stdin.end();
|
|
319
|
+
endEvents();
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
return handle;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function createAntigravityAdapter(opts) {
|
|
326
|
+
opts = opts || {};
|
|
327
|
+
var cwd = opts.cwd || process.cwd();
|
|
328
|
+
var binaryPath = opts._binaryPath || null;
|
|
329
|
+
var cachedModels = ["auto"];
|
|
330
|
+
var activeHandles = [];
|
|
331
|
+
|
|
332
|
+
function capabilities() {
|
|
333
|
+
return {
|
|
334
|
+
effort: true,
|
|
335
|
+
midSessionModelSwitch: false,
|
|
336
|
+
fork: false,
|
|
337
|
+
rollback: false,
|
|
338
|
+
sessionListing: false,
|
|
339
|
+
sessionRename: false,
|
|
340
|
+
thinking: false,
|
|
341
|
+
betas: false,
|
|
342
|
+
rewind: false,
|
|
343
|
+
sessionResume: true,
|
|
344
|
+
promptSuggestions: false,
|
|
345
|
+
elicitation: false,
|
|
346
|
+
fileCheckpointing: false,
|
|
347
|
+
contextCompacting: false,
|
|
348
|
+
skillSharing: true,
|
|
349
|
+
toolPolicy: ["ask", "allow-all"],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
var adapter = {
|
|
354
|
+
vendor: "antigravity",
|
|
355
|
+
init: async function() {
|
|
356
|
+
if (!binaryPath) binaryPath = findBinary();
|
|
357
|
+
if (!binaryPath) throw new Error("Antigravity CLI binary not found: agy");
|
|
358
|
+
var fetched = opts._fetchModels ? await opts._fetchModels(binaryPath, cwd) : await fetchModels(binaryPath, cwd);
|
|
359
|
+
if (fetched && fetched.length) cachedModels = fetched;
|
|
360
|
+
var skills = skillDiscovery.discoverSkills(cwd).map(function(skill) { return skill.name; });
|
|
361
|
+
return {
|
|
362
|
+
models: cachedModels.slice(),
|
|
363
|
+
defaultModel: cachedModels[0] || "auto",
|
|
364
|
+
skills: skills,
|
|
365
|
+
slashCommands: skills,
|
|
366
|
+
fastModeState: null,
|
|
367
|
+
capabilities: capabilities(),
|
|
368
|
+
};
|
|
369
|
+
},
|
|
370
|
+
supportedModels: function() { return Promise.resolve(cachedModels.slice()); },
|
|
371
|
+
createToolServer: function() { return null; },
|
|
372
|
+
createQuery: async function(queryOpts) {
|
|
373
|
+
if (!binaryPath) await adapter.init();
|
|
374
|
+
queryOpts = queryOpts || {};
|
|
375
|
+
var antigravityOpts = (queryOpts.adapterOptions && queryOpts.adapterOptions.ANTIGRAVITY) || {};
|
|
376
|
+
var handle;
|
|
377
|
+
handle = createAntigravityQueryHandle(binaryPath, Object.assign({}, queryOpts, {
|
|
378
|
+
dangerouslySkipPermissions: !!antigravityOpts.dangerouslySkipPermissions,
|
|
379
|
+
env: antigravityOpts.env || null,
|
|
380
|
+
_spawn: opts._spawn || null,
|
|
381
|
+
}), function() {
|
|
382
|
+
var index = activeHandles.indexOf(handle);
|
|
383
|
+
if (index !== -1) activeHandles.splice(index, 1);
|
|
384
|
+
});
|
|
385
|
+
activeHandles.push(handle);
|
|
386
|
+
return handle;
|
|
387
|
+
},
|
|
388
|
+
generateTitle: async function(messages, titleOpts) {
|
|
389
|
+
var handle = await adapter.createQuery({
|
|
390
|
+
cwd: (titleOpts && titleOpts.cwd) || cwd,
|
|
391
|
+
systemPrompt: "Generate a concise conversation title of 3 to 8 words. Output only the title.",
|
|
392
|
+
});
|
|
393
|
+
var prompt = messages.join("\n");
|
|
394
|
+
var title = "";
|
|
395
|
+
handle.pushMessage(prompt);
|
|
396
|
+
handle.endInput();
|
|
397
|
+
for await (var event of handle) {
|
|
398
|
+
if (event.yokeType === "text_delta") title += event.text;
|
|
399
|
+
}
|
|
400
|
+
return title.trim().replace(/^['\"]|['\"]$/g, "").slice(0, 80) || "New conversation";
|
|
401
|
+
},
|
|
402
|
+
shutdown: function() {
|
|
403
|
+
var handles = activeHandles.slice();
|
|
404
|
+
for (var i = 0; i < handles.length; i++) handles[i].abort();
|
|
405
|
+
activeHandles = [];
|
|
406
|
+
return Promise.resolve(true);
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
return adapter;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
module.exports = {
|
|
413
|
+
createAntigravityAdapter: createAntigravityAdapter,
|
|
414
|
+
createAntigravityQueryHandle: createAntigravityQueryHandle,
|
|
415
|
+
fetchModels: fetchModels,
|
|
416
|
+
parseModels: parseModels,
|
|
417
|
+
};
|
|
@@ -197,6 +197,26 @@ function flattenEvent(raw) {
|
|
|
197
197
|
base.summary = raw.summary || null;
|
|
198
198
|
return base;
|
|
199
199
|
}
|
|
200
|
+
if (raw.subtype === "background_tasks_changed") {
|
|
201
|
+
base.yokeType = "background_tasks_changed";
|
|
202
|
+
base.tasks = normalizeBackgroundTasks(raw.tasks);
|
|
203
|
+
return base;
|
|
204
|
+
}
|
|
205
|
+
if (raw.subtype === "task_updated") {
|
|
206
|
+
base.yokeType = "task_updated";
|
|
207
|
+
base.task_id = raw.task_id;
|
|
208
|
+
base.patch = raw.patch || {};
|
|
209
|
+
return base;
|
|
210
|
+
}
|
|
211
|
+
if (raw.subtype === "task_notification") {
|
|
212
|
+
base.yokeType = "task_notification";
|
|
213
|
+
base.parentToolId = raw.parent_tool_use_id;
|
|
214
|
+
base.taskId = raw.task_id;
|
|
215
|
+
base.status = raw.status || "completed";
|
|
216
|
+
base.summary = raw.summary || "";
|
|
217
|
+
base.usage = raw.usage || null;
|
|
218
|
+
return base;
|
|
219
|
+
}
|
|
200
220
|
// Model refusal: the model declined and the CLI either fell back to
|
|
201
221
|
// another model or ended the turn. Translate to a vendor-neutral
|
|
202
222
|
// yokeType so the relay never sees SDK subtype strings.
|
|
@@ -343,6 +363,20 @@ function flattenEvent(raw) {
|
|
|
343
363
|
return base;
|
|
344
364
|
}
|
|
345
365
|
|
|
366
|
+
function normalizeBackgroundTasks(tasks) {
|
|
367
|
+
if (!Array.isArray(tasks)) return [];
|
|
368
|
+
return tasks.map(function(task) {
|
|
369
|
+
var nativeType = task && task.task_type;
|
|
370
|
+
var taskType = nativeType === "local_bash" ? "shell"
|
|
371
|
+
: nativeType === "local_agent" ? "agent" : "other";
|
|
372
|
+
return {
|
|
373
|
+
task_id: task && task.task_id,
|
|
374
|
+
task_type: taskType,
|
|
375
|
+
description: (task && task.description) || "",
|
|
376
|
+
};
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
346
380
|
// --- QueryHandle ---
|
|
347
381
|
// Wraps a raw SDK query object with the YOKE QueryHandle interface.
|
|
348
382
|
// Events are flattened via flattenEvent before yielding.
|
|
@@ -7,6 +7,7 @@ var path = require("path");
|
|
|
7
7
|
var fs = require("fs");
|
|
8
8
|
var { CodexAppServer } = require("../codex-app-server");
|
|
9
9
|
var skillDiscovery = require("../skill-discovery");
|
|
10
|
+
var { resolveOsUserInfo } = require("../../os-users");
|
|
10
11
|
|
|
11
12
|
// --- Event flattening ---
|
|
12
13
|
// Converts app-server JSON-RPC notifications into flat objects with a yokeType field.
|
|
@@ -1071,6 +1072,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
1071
1072
|
await appServer.send("turn/start", {
|
|
1072
1073
|
threadId: state.threadId,
|
|
1073
1074
|
input: input,
|
|
1075
|
+
model: state.model,
|
|
1074
1076
|
}, 60000);
|
|
1075
1077
|
|
|
1076
1078
|
// Wait for turn to complete
|
|
@@ -1143,7 +1145,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
1143
1145
|
},
|
|
1144
1146
|
|
|
1145
1147
|
setModel: function(model) {
|
|
1146
|
-
|
|
1148
|
+
state.model = model || "gpt-5.6-terra";
|
|
1147
1149
|
return Promise.resolve();
|
|
1148
1150
|
},
|
|
1149
1151
|
|
|
@@ -1220,6 +1222,13 @@ function createCodexAdapter(opts) {
|
|
|
1220
1222
|
var _cwd = (opts && opts.cwd) || process.cwd();
|
|
1221
1223
|
var _slug = (opts && opts.slug) || "";
|
|
1222
1224
|
var _defaultInitOpts = Object.assign({}, opts || {});
|
|
1225
|
+
var _runtimeLinuxUser = (opts && opts.runtimeLinuxUser) || null;
|
|
1226
|
+
var _requiresLinuxUser = !!(opts && opts.osUsers) && !_runtimeLinuxUser;
|
|
1227
|
+
var _userRuntimes = Object.create(null);
|
|
1228
|
+
var _resolveOsUserInfo = (opts && opts.resolveOsUserInfo) || resolveOsUserInfo;
|
|
1229
|
+
var _createAppServer = (opts && opts.createAppServer) || function(serverOpts) {
|
|
1230
|
+
return new CodexAppServer(null, serverOpts);
|
|
1231
|
+
};
|
|
1223
1232
|
// Codex models are a fixed list (the app-server doesn't enumerate them), so
|
|
1224
1233
|
// model listing must not depend on a successful app-server init — otherwise a
|
|
1225
1234
|
// slow/failed `initialize` leaves the picker empty and the chip shows the
|
|
@@ -1244,6 +1253,45 @@ function createCodexAdapter(opts) {
|
|
|
1244
1253
|
var _shuttingDown = false;
|
|
1245
1254
|
var _activeQueries = [];
|
|
1246
1255
|
|
|
1256
|
+
function withoutLinuxUser(callOpts) {
|
|
1257
|
+
var cleaned = Object.assign({}, callOpts || {});
|
|
1258
|
+
delete cleaned.linuxUser;
|
|
1259
|
+
return cleaned;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function getUserRuntime(linuxUser) {
|
|
1263
|
+
if (!linuxUser) return null;
|
|
1264
|
+
if (_runtimeLinuxUser) {
|
|
1265
|
+
if (_runtimeLinuxUser !== linuxUser) {
|
|
1266
|
+
throw new Error("Codex runtime user mismatch: expected " + _runtimeLinuxUser + ", received " + linuxUser);
|
|
1267
|
+
}
|
|
1268
|
+
return adapter;
|
|
1269
|
+
}
|
|
1270
|
+
if (!_userRuntimes[linuxUser]) {
|
|
1271
|
+
var runtimeOpts = Object.assign({}, _defaultInitOpts, {
|
|
1272
|
+
runtimeLinuxUser: linuxUser,
|
|
1273
|
+
});
|
|
1274
|
+
delete runtimeOpts.linuxUser;
|
|
1275
|
+
_userRuntimes[linuxUser] = createCodexAdapter(runtimeOpts);
|
|
1276
|
+
}
|
|
1277
|
+
return _userRuntimes[linuxUser];
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function shutdownUserRuntimes(method, idleMs) {
|
|
1281
|
+
var users = Object.keys(_userRuntimes);
|
|
1282
|
+
if (!users.length) return Promise.resolve([]);
|
|
1283
|
+
return Promise.all(users.map(function(linuxUser) {
|
|
1284
|
+
var runtime = _userRuntimes[linuxUser];
|
|
1285
|
+
var result = method === "shutdownIfIdle"
|
|
1286
|
+
? runtime.shutdownIfIdle(idleMs)
|
|
1287
|
+
: runtime.shutdown();
|
|
1288
|
+
return Promise.resolve(result).then(function(stopped) {
|
|
1289
|
+
if (method === "shutdown" || stopped) delete _userRuntimes[linuxUser];
|
|
1290
|
+
return stopped;
|
|
1291
|
+
});
|
|
1292
|
+
}));
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1247
1295
|
function updateLastActiveAt() {
|
|
1248
1296
|
_lastActiveAt = Date.now();
|
|
1249
1297
|
}
|
|
@@ -1279,7 +1327,7 @@ function createCodexAdapter(opts) {
|
|
|
1279
1327
|
fastModeState: null,
|
|
1280
1328
|
capabilities: {
|
|
1281
1329
|
effort: true,
|
|
1282
|
-
midSessionModelSwitch:
|
|
1330
|
+
midSessionModelSwitch: true,
|
|
1283
1331
|
fork: true,
|
|
1284
1332
|
rollback: true,
|
|
1285
1333
|
sessionListing: false,
|
|
@@ -1396,6 +1444,16 @@ function createCodexAdapter(opts) {
|
|
|
1396
1444
|
vendor: "codex",
|
|
1397
1445
|
|
|
1398
1446
|
init: function(initOpts) {
|
|
1447
|
+
var requestedLinuxUser = initOpts && initOpts.linuxUser;
|
|
1448
|
+
if (!_runtimeLinuxUser && requestedLinuxUser) {
|
|
1449
|
+
return getUserRuntime(requestedLinuxUser).init(withoutLinuxUser(initOpts));
|
|
1450
|
+
}
|
|
1451
|
+
if (_requiresLinuxUser) {
|
|
1452
|
+
return Promise.reject(new Error("Codex requires a mapped Linux user while OS-user isolation is enabled"));
|
|
1453
|
+
}
|
|
1454
|
+
if (_runtimeLinuxUser && requestedLinuxUser && requestedLinuxUser !== _runtimeLinuxUser) {
|
|
1455
|
+
return Promise.reject(new Error("Codex runtime user mismatch"));
|
|
1456
|
+
}
|
|
1399
1457
|
if (_shuttingDown) {
|
|
1400
1458
|
return Promise.reject(createShutdownError());
|
|
1401
1459
|
}
|
|
@@ -1412,6 +1470,9 @@ function createCodexAdapter(opts) {
|
|
|
1412
1470
|
|
|
1413
1471
|
_initPromise = (async function() {
|
|
1414
1472
|
var serverOpts = { cwd: _cwd };
|
|
1473
|
+
if (_runtimeLinuxUser) {
|
|
1474
|
+
serverOpts.osUserInfo = _resolveOsUserInfo(_runtimeLinuxUser);
|
|
1475
|
+
}
|
|
1415
1476
|
|
|
1416
1477
|
// Extract adapter options
|
|
1417
1478
|
if (effectiveInitOpts && effectiveInitOpts.adapterOptions && effectiveInitOpts.adapterOptions.CODEX) {
|
|
@@ -1478,7 +1539,7 @@ function createCodexAdapter(opts) {
|
|
|
1478
1539
|
}
|
|
1479
1540
|
|
|
1480
1541
|
// Spawn and initialize app-server
|
|
1481
|
-
_appServer =
|
|
1542
|
+
_appServer = _createAppServer(serverOpts);
|
|
1482
1543
|
await _appServer.start();
|
|
1483
1544
|
|
|
1484
1545
|
await _appServer.send("initialize", {
|
|
@@ -1565,6 +1626,16 @@ function createCodexAdapter(opts) {
|
|
|
1565
1626
|
},
|
|
1566
1627
|
|
|
1567
1628
|
createQuery: async function(queryOpts) {
|
|
1629
|
+
var requestedLinuxUser = queryOpts && queryOpts.linuxUser;
|
|
1630
|
+
if (!_runtimeLinuxUser && requestedLinuxUser) {
|
|
1631
|
+
return getUserRuntime(requestedLinuxUser).createQuery(withoutLinuxUser(queryOpts));
|
|
1632
|
+
}
|
|
1633
|
+
if (_requiresLinuxUser) {
|
|
1634
|
+
throw new Error("Codex requires a mapped Linux user while OS-user isolation is enabled");
|
|
1635
|
+
}
|
|
1636
|
+
if (_runtimeLinuxUser && requestedLinuxUser && requestedLinuxUser !== _runtimeLinuxUser) {
|
|
1637
|
+
throw new Error("Codex runtime user mismatch");
|
|
1638
|
+
}
|
|
1568
1639
|
if (_shuttingDown) {
|
|
1569
1640
|
throw createShutdownError();
|
|
1570
1641
|
}
|
|
@@ -1661,6 +1732,13 @@ function createCodexAdapter(opts) {
|
|
|
1661
1732
|
|
|
1662
1733
|
// --- Title generation ---
|
|
1663
1734
|
generateTitle: async function(messages, opts) {
|
|
1735
|
+
var requestedLinuxUser = opts && opts.linuxUser;
|
|
1736
|
+
if (!_runtimeLinuxUser && requestedLinuxUser) {
|
|
1737
|
+
return getUserRuntime(requestedLinuxUser).generateTitle(messages, withoutLinuxUser(opts));
|
|
1738
|
+
}
|
|
1739
|
+
if (_requiresLinuxUser) {
|
|
1740
|
+
throw new Error("Codex requires a mapped Linux user while OS-user isolation is enabled");
|
|
1741
|
+
}
|
|
1664
1742
|
var systemPrompt = "You are a title generator. Output only a short title (3-8 words). No quotes, no punctuation at the end, no explanation.";
|
|
1665
1743
|
var prompt = "Below is a conversation between a user and an AI assistant. Generate a short, descriptive title (3-8 words) that captures the main topic. Reply with ONLY the title, nothing else.\n\n";
|
|
1666
1744
|
for (var i = 0; i < messages.length; i++) {
|
|
@@ -1708,6 +1786,11 @@ function createCodexAdapter(opts) {
|
|
|
1708
1786
|
listSessions: function() { return Promise.resolve([]); },
|
|
1709
1787
|
renameSession: function() { return Promise.resolve(); },
|
|
1710
1788
|
forkSession: function(threadId, opts) {
|
|
1789
|
+
var requestedLinuxUser = opts && opts.linuxUser;
|
|
1790
|
+
if (!_runtimeLinuxUser && requestedLinuxUser) {
|
|
1791
|
+
return getUserRuntime(requestedLinuxUser).forkSession(threadId, withoutLinuxUser(opts));
|
|
1792
|
+
}
|
|
1793
|
+
if (_requiresLinuxUser) return Promise.reject(new Error("Codex requires a mapped Linux user while OS-user isolation is enabled"));
|
|
1711
1794
|
if (!_appServer || !_appServer.started) return Promise.resolve(null);
|
|
1712
1795
|
return _appServer.send("thread/fork", { threadId: threadId }, 30000).then(function(result) {
|
|
1713
1796
|
var newThreadId = (result && result.thread) ? result.thread.id : null;
|
|
@@ -1715,17 +1798,30 @@ function createCodexAdapter(opts) {
|
|
|
1715
1798
|
return { sessionId: newThreadId };
|
|
1716
1799
|
});
|
|
1717
1800
|
},
|
|
1718
|
-
rollbackThread: function(threadId, numTurns) {
|
|
1801
|
+
rollbackThread: function(threadId, numTurns, opts) {
|
|
1802
|
+
var requestedLinuxUser = opts && opts.linuxUser;
|
|
1803
|
+
if (!_runtimeLinuxUser && requestedLinuxUser) {
|
|
1804
|
+
return getUserRuntime(requestedLinuxUser).rollbackThread(threadId, numTurns, withoutLinuxUser(opts));
|
|
1805
|
+
}
|
|
1806
|
+
if (_requiresLinuxUser) return Promise.reject(new Error("Codex requires a mapped Linux user while OS-user isolation is enabled"));
|
|
1719
1807
|
if (!_appServer || !_appServer.started) return Promise.resolve(null);
|
|
1720
1808
|
return _appServer.send("thread/rollback", { threadId: threadId, numTurns: numTurns }, 30000);
|
|
1721
1809
|
},
|
|
1722
1810
|
|
|
1723
1811
|
// Shutdown the app-server process
|
|
1724
1812
|
shutdown: function() {
|
|
1725
|
-
return
|
|
1813
|
+
return Promise.all([
|
|
1814
|
+
beginShutdown(true),
|
|
1815
|
+
shutdownUserRuntimes("shutdown"),
|
|
1816
|
+
]).then(function() { return true; });
|
|
1726
1817
|
},
|
|
1727
1818
|
|
|
1728
1819
|
shutdownIfIdle: function(idleMs) {
|
|
1820
|
+
if (!_runtimeLinuxUser && Object.keys(_userRuntimes).length > 0) {
|
|
1821
|
+
return shutdownUserRuntimes("shutdownIfIdle", idleMs).then(function(results) {
|
|
1822
|
+
return results.some(function(stopped) { return !!stopped; });
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1729
1825
|
if (_shuttingDown || _shutdownPromise) return Promise.resolve(false);
|
|
1730
1826
|
if (_initPromise) return Promise.resolve(false);
|
|
1731
1827
|
if (!_appServer) return Promise.resolve(false);
|