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.
Files changed (80) hide show
  1. package/lib/daemon-projects.js +3 -3
  2. package/lib/daemon.js +59 -59
  3. package/lib/project-connection.js +35 -7
  4. package/lib/project-debate.js +4 -0
  5. package/lib/project-mate-interaction.js +21 -1
  6. package/lib/project-memory.js +3 -0
  7. package/lib/project-models.js +220 -0
  8. package/lib/project-session-handoff.js +162 -0
  9. package/lib/project-sessions.js +13 -45
  10. package/lib/project-worker-proposal.js +49 -14
  11. package/lib/project.js +38 -80
  12. package/lib/public/antigravity-avatar.png +0 -0
  13. package/lib/public/app.js +29 -0
  14. package/lib/public/copilot-avatar.svg +5 -0
  15. package/lib/public/css/command-palette.css +22 -2
  16. package/lib/public/css/icon-strip.css +29 -13
  17. package/lib/public/css/input.css +56 -0
  18. package/lib/public/css/mates.css +6 -0
  19. package/lib/public/css/menus.css +38 -25
  20. package/lib/public/css/messages.css +9 -0
  21. package/lib/public/css/pane.css +16 -3
  22. package/lib/public/css/pwa-mobile.css +17 -0
  23. package/lib/public/css/session-actions.css +98 -0
  24. package/lib/public/css/title-bar.css +19 -0
  25. package/lib/public/grok-avatar.svg +4 -0
  26. package/lib/public/index.html +29 -7
  27. package/lib/public/junie-avatar.svg +11 -0
  28. package/lib/public/kimi-avatar.svg +4 -0
  29. package/lib/public/modules/agent-config-selects.js +69 -0
  30. package/lib/public/modules/app-header.js +4 -22
  31. package/lib/public/modules/app-messages.js +54 -11
  32. package/lib/public/modules/app-panels.js +36 -83
  33. package/lib/public/modules/app-projects.js +3 -1
  34. package/lib/public/modules/app-rendering.js +19 -4
  35. package/lib/public/modules/background-tasks-ui.js +55 -0
  36. package/lib/public/modules/mate-sidebar.js +11 -3
  37. package/lib/public/modules/model-picker.js +263 -0
  38. package/lib/public/modules/notifications.js +7 -1
  39. package/lib/public/modules/pane-bridge.js +11 -0
  40. package/lib/public/modules/pane-links.js +23 -0
  41. package/lib/public/modules/project-switcher.js +7 -6
  42. package/lib/public/modules/session-actions.js +294 -0
  43. package/lib/public/modules/sidebar-mates.js +1 -1
  44. package/lib/public/modules/sidebar-mobile.js +2 -1
  45. package/lib/public/modules/sidebar-projects.js +34 -43
  46. package/lib/public/modules/sidebar-sessions.js +20 -6
  47. package/lib/public/modules/split-pair-ui.js +16 -77
  48. package/lib/public/modules/split-view.js +3 -0
  49. package/lib/public/modules/tools.js +6 -1
  50. package/lib/public/modules/vendor-priority.js +14 -0
  51. package/lib/public/modules/vendor-selection.js +20 -0
  52. package/lib/public/modules/worker-proposal.js +5 -1
  53. package/lib/public/modules/worktree-location.js +17 -0
  54. package/lib/public/qwen-avatar.svg +4 -0
  55. package/lib/public/style.css +4 -2
  56. package/lib/sdk-bridge.js +99 -56
  57. package/lib/sdk-message-processor.js +26 -4
  58. package/lib/session-handoff-context.js +110 -0
  59. package/lib/session-notes-mcp-server.js +8 -1
  60. package/lib/sessions.js +4 -0
  61. package/lib/worktree.js +9 -4
  62. package/lib/ws-schema.js +9 -1
  63. package/lib/yoke/acp-agent-profiles.js +64 -14
  64. package/lib/yoke/adapters/antigravity.js +417 -0
  65. package/lib/yoke/adapters/claude.js +34 -0
  66. package/lib/yoke/adapters/codex.js +101 -5
  67. package/lib/yoke/adapters/copilot.js +7 -0
  68. package/lib/yoke/adapters/grok.js +7 -0
  69. package/lib/yoke/adapters/junie.js +7 -0
  70. package/lib/yoke/adapters/kimi.js +7 -0
  71. package/lib/yoke/adapters/kiro.js +2 -2
  72. package/lib/yoke/adapters/qwen.js +7 -0
  73. package/lib/yoke/codex-app-server.js +25 -8
  74. package/lib/yoke/index.js +67 -28
  75. package/lib/yoke/instructions.js +3 -0
  76. package/lib/yoke/interface.js +12 -0
  77. package/lib/yoke/vendor-registry.js +61 -6
  78. package/package.json +1 -1
  79. package/lib/public/gemini-avatar.svg +0 -11
  80. 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
- // Model is set at thread creation. Cannot change mid-thread.
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: false,
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 = new CodexAppServer(null, serverOpts);
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 beginShutdown(true);
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);
@@ -0,0 +1,7 @@
1
+ var createAcpAdapter = require("./acp").createAcpAdapter;
2
+
3
+ function createCopilotAdapter(opts) {
4
+ return createAcpAdapter("copilot", opts);
5
+ }
6
+
7
+ module.exports = { createCopilotAdapter: createCopilotAdapter };
@@ -0,0 +1,7 @@
1
+ var createAcpAdapter = require("./acp").createAcpAdapter;
2
+
3
+ function createGrokAdapter(opts) {
4
+ return createAcpAdapter("grok", opts);
5
+ }
6
+
7
+ module.exports = { createGrokAdapter: createGrokAdapter };
@@ -0,0 +1,7 @@
1
+ var createAcpAdapter = require("./acp").createAcpAdapter;
2
+
3
+ function createJunieAdapter(opts) {
4
+ return createAcpAdapter("junie", opts);
5
+ }
6
+
7
+ module.exports = { createJunieAdapter: createJunieAdapter };