clay-server 3.3.2-beta.3 → 3.4.0-beta.10

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 (77) hide show
  1. package/lib/daemon-projects.js +3 -3
  2. package/lib/daemon.js +1 -1
  3. package/lib/project-connection.js +20 -5
  4. package/lib/project-debate.js +4 -0
  5. package/lib/project-file-watch.js +77 -16
  6. package/lib/project-mate-interaction.js +21 -1
  7. package/lib/project-memory.js +3 -0
  8. package/lib/project-shell-command.js +160 -0
  9. package/lib/project-user-message.js +10 -0
  10. package/lib/project.js +19 -4
  11. package/lib/public/antigravity-avatar.png +0 -0
  12. package/lib/public/app.js +28 -0
  13. package/lib/public/copilot-avatar.svg +5 -0
  14. package/lib/public/css/command-palette.css +22 -2
  15. package/lib/public/css/icon-strip.css +29 -13
  16. package/lib/public/css/input.css +79 -0
  17. package/lib/public/css/mates.css +7 -0
  18. package/lib/public/css/menus.css +48 -14
  19. package/lib/public/css/messages.css +9 -0
  20. package/lib/public/css/pane.css +4 -0
  21. package/lib/public/css/pwa-mobile.css +17 -0
  22. package/lib/public/css/title-bar.css +19 -0
  23. package/lib/public/grok-avatar.svg +4 -0
  24. package/lib/public/index.html +43 -4
  25. package/lib/public/junie-avatar.svg +11 -0
  26. package/lib/public/kimi-avatar.svg +4 -0
  27. package/lib/public/modules/app-header.js +4 -0
  28. package/lib/public/modules/app-messages.js +28 -5
  29. package/lib/public/modules/app-panels.js +37 -5
  30. package/lib/public/modules/app-projects.js +3 -1
  31. package/lib/public/modules/app-rendering.js +22 -1
  32. package/lib/public/modules/input.js +58 -28
  33. package/lib/public/modules/mate-sidebar.js +17 -3
  34. package/lib/public/modules/notifications.js +7 -1
  35. package/lib/public/modules/pane-bridge.js +11 -0
  36. package/lib/public/modules/pane-links.js +23 -0
  37. package/lib/public/modules/project-switcher.js +7 -6
  38. package/lib/public/modules/shell-command.js +148 -0
  39. package/lib/public/modules/sidebar-mates.js +7 -1
  40. package/lib/public/modules/sidebar-mobile.js +2 -1
  41. package/lib/public/modules/sidebar-projects.js +34 -43
  42. package/lib/public/modules/sidebar-sessions.js +6 -6
  43. package/lib/public/modules/split-pair-ui.js +3 -2
  44. package/lib/public/modules/tools.js +12 -1
  45. package/lib/public/modules/vendor-priority.js +14 -0
  46. package/lib/public/modules/vendor-selection.js +20 -0
  47. package/lib/public/modules/worktree-location.js +17 -0
  48. package/lib/public/opencode-avatar.svg +4 -0
  49. package/lib/public/qwen-avatar.svg +4 -0
  50. package/lib/public/style.css +3 -2
  51. package/lib/sdk-bridge.js +59 -19
  52. package/lib/sdk-message-processor.js +10 -1
  53. package/lib/session-notes-mcp-server.js +7 -1
  54. package/lib/worktree.js +9 -4
  55. package/lib/ws-schema.js +3 -0
  56. package/lib/yoke/acp-agent-profiles.js +238 -0
  57. package/lib/yoke/acp-driver-runtime.js +50 -0
  58. package/lib/yoke/acp-event-normalizer.js +179 -0
  59. package/lib/yoke/acp-process-manager.js +264 -0
  60. package/lib/yoke/acp-query-handle.js +487 -0
  61. package/lib/yoke/adapters/acp.js +317 -0
  62. package/lib/yoke/adapters/antigravity.js +417 -0
  63. package/lib/yoke/adapters/codex.js +98 -3
  64. package/lib/yoke/adapters/copilot.js +7 -0
  65. package/lib/yoke/adapters/grok.js +7 -0
  66. package/lib/yoke/adapters/junie.js +7 -0
  67. package/lib/yoke/adapters/kimi.js +7 -0
  68. package/lib/yoke/adapters/kiro.js +4 -4
  69. package/lib/yoke/adapters/opencode.js +7 -0
  70. package/lib/yoke/adapters/qwen.js +7 -0
  71. package/lib/yoke/codex-app-server.js +25 -8
  72. package/lib/yoke/index.js +81 -13
  73. package/lib/yoke/instructions.js +3 -0
  74. package/lib/yoke/interface.js +2 -0
  75. package/lib/yoke/kiro-acp-server.js +30 -276
  76. package/lib/yoke/vendor-registry.js +77 -0
  77. package/package.json +1 -1
@@ -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
+ };
@@ -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.
@@ -1220,6 +1221,13 @@ function createCodexAdapter(opts) {
1220
1221
  var _cwd = (opts && opts.cwd) || process.cwd();
1221
1222
  var _slug = (opts && opts.slug) || "";
1222
1223
  var _defaultInitOpts = Object.assign({}, opts || {});
1224
+ var _runtimeLinuxUser = (opts && opts.runtimeLinuxUser) || null;
1225
+ var _requiresLinuxUser = !!(opts && opts.osUsers) && !_runtimeLinuxUser;
1226
+ var _userRuntimes = Object.create(null);
1227
+ var _resolveOsUserInfo = (opts && opts.resolveOsUserInfo) || resolveOsUserInfo;
1228
+ var _createAppServer = (opts && opts.createAppServer) || function(serverOpts) {
1229
+ return new CodexAppServer(null, serverOpts);
1230
+ };
1223
1231
  // Codex models are a fixed list (the app-server doesn't enumerate them), so
1224
1232
  // model listing must not depend on a successful app-server init — otherwise a
1225
1233
  // slow/failed `initialize` leaves the picker empty and the chip shows the
@@ -1244,6 +1252,45 @@ function createCodexAdapter(opts) {
1244
1252
  var _shuttingDown = false;
1245
1253
  var _activeQueries = [];
1246
1254
 
1255
+ function withoutLinuxUser(callOpts) {
1256
+ var cleaned = Object.assign({}, callOpts || {});
1257
+ delete cleaned.linuxUser;
1258
+ return cleaned;
1259
+ }
1260
+
1261
+ function getUserRuntime(linuxUser) {
1262
+ if (!linuxUser) return null;
1263
+ if (_runtimeLinuxUser) {
1264
+ if (_runtimeLinuxUser !== linuxUser) {
1265
+ throw new Error("Codex runtime user mismatch: expected " + _runtimeLinuxUser + ", received " + linuxUser);
1266
+ }
1267
+ return adapter;
1268
+ }
1269
+ if (!_userRuntimes[linuxUser]) {
1270
+ var runtimeOpts = Object.assign({}, _defaultInitOpts, {
1271
+ runtimeLinuxUser: linuxUser,
1272
+ });
1273
+ delete runtimeOpts.linuxUser;
1274
+ _userRuntimes[linuxUser] = createCodexAdapter(runtimeOpts);
1275
+ }
1276
+ return _userRuntimes[linuxUser];
1277
+ }
1278
+
1279
+ function shutdownUserRuntimes(method, idleMs) {
1280
+ var users = Object.keys(_userRuntimes);
1281
+ if (!users.length) return Promise.resolve([]);
1282
+ return Promise.all(users.map(function(linuxUser) {
1283
+ var runtime = _userRuntimes[linuxUser];
1284
+ var result = method === "shutdownIfIdle"
1285
+ ? runtime.shutdownIfIdle(idleMs)
1286
+ : runtime.shutdown();
1287
+ return Promise.resolve(result).then(function(stopped) {
1288
+ if (method === "shutdown" || stopped) delete _userRuntimes[linuxUser];
1289
+ return stopped;
1290
+ });
1291
+ }));
1292
+ }
1293
+
1247
1294
  function updateLastActiveAt() {
1248
1295
  _lastActiveAt = Date.now();
1249
1296
  }
@@ -1396,6 +1443,16 @@ function createCodexAdapter(opts) {
1396
1443
  vendor: "codex",
1397
1444
 
1398
1445
  init: function(initOpts) {
1446
+ var requestedLinuxUser = initOpts && initOpts.linuxUser;
1447
+ if (!_runtimeLinuxUser && requestedLinuxUser) {
1448
+ return getUserRuntime(requestedLinuxUser).init(withoutLinuxUser(initOpts));
1449
+ }
1450
+ if (_requiresLinuxUser) {
1451
+ return Promise.reject(new Error("Codex requires a mapped Linux user while OS-user isolation is enabled"));
1452
+ }
1453
+ if (_runtimeLinuxUser && requestedLinuxUser && requestedLinuxUser !== _runtimeLinuxUser) {
1454
+ return Promise.reject(new Error("Codex runtime user mismatch"));
1455
+ }
1399
1456
  if (_shuttingDown) {
1400
1457
  return Promise.reject(createShutdownError());
1401
1458
  }
@@ -1412,6 +1469,9 @@ function createCodexAdapter(opts) {
1412
1469
 
1413
1470
  _initPromise = (async function() {
1414
1471
  var serverOpts = { cwd: _cwd };
1472
+ if (_runtimeLinuxUser) {
1473
+ serverOpts.osUserInfo = _resolveOsUserInfo(_runtimeLinuxUser);
1474
+ }
1415
1475
 
1416
1476
  // Extract adapter options
1417
1477
  if (effectiveInitOpts && effectiveInitOpts.adapterOptions && effectiveInitOpts.adapterOptions.CODEX) {
@@ -1478,7 +1538,7 @@ function createCodexAdapter(opts) {
1478
1538
  }
1479
1539
 
1480
1540
  // Spawn and initialize app-server
1481
- _appServer = new CodexAppServer(null, serverOpts);
1541
+ _appServer = _createAppServer(serverOpts);
1482
1542
  await _appServer.start();
1483
1543
 
1484
1544
  await _appServer.send("initialize", {
@@ -1565,6 +1625,16 @@ function createCodexAdapter(opts) {
1565
1625
  },
1566
1626
 
1567
1627
  createQuery: async function(queryOpts) {
1628
+ var requestedLinuxUser = queryOpts && queryOpts.linuxUser;
1629
+ if (!_runtimeLinuxUser && requestedLinuxUser) {
1630
+ return getUserRuntime(requestedLinuxUser).createQuery(withoutLinuxUser(queryOpts));
1631
+ }
1632
+ if (_requiresLinuxUser) {
1633
+ throw new Error("Codex requires a mapped Linux user while OS-user isolation is enabled");
1634
+ }
1635
+ if (_runtimeLinuxUser && requestedLinuxUser && requestedLinuxUser !== _runtimeLinuxUser) {
1636
+ throw new Error("Codex runtime user mismatch");
1637
+ }
1568
1638
  if (_shuttingDown) {
1569
1639
  throw createShutdownError();
1570
1640
  }
@@ -1661,6 +1731,13 @@ function createCodexAdapter(opts) {
1661
1731
 
1662
1732
  // --- Title generation ---
1663
1733
  generateTitle: async function(messages, opts) {
1734
+ var requestedLinuxUser = opts && opts.linuxUser;
1735
+ if (!_runtimeLinuxUser && requestedLinuxUser) {
1736
+ return getUserRuntime(requestedLinuxUser).generateTitle(messages, withoutLinuxUser(opts));
1737
+ }
1738
+ if (_requiresLinuxUser) {
1739
+ throw new Error("Codex requires a mapped Linux user while OS-user isolation is enabled");
1740
+ }
1664
1741
  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
1742
  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
1743
  for (var i = 0; i < messages.length; i++) {
@@ -1708,6 +1785,11 @@ function createCodexAdapter(opts) {
1708
1785
  listSessions: function() { return Promise.resolve([]); },
1709
1786
  renameSession: function() { return Promise.resolve(); },
1710
1787
  forkSession: function(threadId, opts) {
1788
+ var requestedLinuxUser = opts && opts.linuxUser;
1789
+ if (!_runtimeLinuxUser && requestedLinuxUser) {
1790
+ return getUserRuntime(requestedLinuxUser).forkSession(threadId, withoutLinuxUser(opts));
1791
+ }
1792
+ if (_requiresLinuxUser) return Promise.reject(new Error("Codex requires a mapped Linux user while OS-user isolation is enabled"));
1711
1793
  if (!_appServer || !_appServer.started) return Promise.resolve(null);
1712
1794
  return _appServer.send("thread/fork", { threadId: threadId }, 30000).then(function(result) {
1713
1795
  var newThreadId = (result && result.thread) ? result.thread.id : null;
@@ -1715,17 +1797,30 @@ function createCodexAdapter(opts) {
1715
1797
  return { sessionId: newThreadId };
1716
1798
  });
1717
1799
  },
1718
- rollbackThread: function(threadId, numTurns) {
1800
+ rollbackThread: function(threadId, numTurns, opts) {
1801
+ var requestedLinuxUser = opts && opts.linuxUser;
1802
+ if (!_runtimeLinuxUser && requestedLinuxUser) {
1803
+ return getUserRuntime(requestedLinuxUser).rollbackThread(threadId, numTurns, withoutLinuxUser(opts));
1804
+ }
1805
+ if (_requiresLinuxUser) return Promise.reject(new Error("Codex requires a mapped Linux user while OS-user isolation is enabled"));
1719
1806
  if (!_appServer || !_appServer.started) return Promise.resolve(null);
1720
1807
  return _appServer.send("thread/rollback", { threadId: threadId, numTurns: numTurns }, 30000);
1721
1808
  },
1722
1809
 
1723
1810
  // Shutdown the app-server process
1724
1811
  shutdown: function() {
1725
- return beginShutdown(true);
1812
+ return Promise.all([
1813
+ beginShutdown(true),
1814
+ shutdownUserRuntimes("shutdown"),
1815
+ ]).then(function() { return true; });
1726
1816
  },
1727
1817
 
1728
1818
  shutdownIfIdle: function(idleMs) {
1819
+ if (!_runtimeLinuxUser && Object.keys(_userRuntimes).length > 0) {
1820
+ return shutdownUserRuntimes("shutdownIfIdle", idleMs).then(function(results) {
1821
+ return results.some(function(stopped) { return !!stopped; });
1822
+ });
1823
+ }
1729
1824
  if (_shuttingDown || _shutdownPromise) return Promise.resolve(false);
1730
1825
  if (_initPromise) return Promise.resolve(false);
1731
1826
  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 };
@@ -0,0 +1,7 @@
1
+ var createAcpAdapter = require("./acp").createAcpAdapter;
2
+
3
+ function createKimiAdapter(opts) {
4
+ return createAcpAdapter("kimi", opts);
5
+ }
6
+
7
+ module.exports = { createKimiAdapter: createKimiAdapter };
@@ -477,10 +477,10 @@ function createKiroQueryHandle(acp, queryOpts) {
477
477
  var method = msg.method;
478
478
  var params = msg.params || {};
479
479
 
480
- // Routing by sessionId happens in KiroAcpServer._handleMessage, which also
481
- // guarantees unroutable requests get an error response. Do not add a silent
482
- // sessionId filter here: dropping a request without calling acp.respond()
483
- // blocks kiro-cli until the session/prompt timeout.
480
+ // The shared ACP manager routes by sessionId and guarantees unroutable
481
+ // requests get an error response. Do not add a silent sessionId filter here:
482
+ // dropping a request without calling acp.respond() blocks kiro-cli until the
483
+ // session/prompt timeout.
484
484
 
485
485
  // Tool permission request (server -> client, has an id we must answer)
486
486
  if (method === "session/request_permission") {
@@ -0,0 +1,7 @@
1
+ var createAcpAdapter = require("./acp").createAcpAdapter;
2
+
3
+ function createOpenCodeAdapter(opts) {
4
+ return createAcpAdapter("opencode", opts);
5
+ }
6
+
7
+ module.exports = { createOpenCodeAdapter: createOpenCodeAdapter };
@@ -0,0 +1,7 @@
1
+ var createAcpAdapter = require("./acp").createAcpAdapter;
2
+
3
+ function createQwenAdapter(opts) {
4
+ return createAcpAdapter("qwen", opts);
5
+ }
6
+
7
+ module.exports = { createQwenAdapter: createQwenAdapter };