clay-server 3.4.0-beta.1 → 3.4.0-beta.3

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.
@@ -0,0 +1,148 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { iconHtml, refreshIcons } from './icons.js';
4
+ import { showToast } from './utils.js';
5
+
6
+ var defaultPlaceholder = "";
7
+
8
+ function getInput() {
9
+ return document.getElementById("input");
10
+ }
11
+
12
+ function appendToMessages(element) {
13
+ var messages = document.getElementById("messages");
14
+ if (!messages) return;
15
+ messages.appendChild(element);
16
+ requestAnimationFrame(function () { messages.scrollTop = messages.scrollHeight; });
17
+ }
18
+
19
+ function setMode(active) {
20
+ var input = getInput();
21
+ var button = document.getElementById("shell-command-btn");
22
+ var row = document.getElementById("input-row");
23
+ store.set({ shellCommandMode: active });
24
+ if (button) {
25
+ button.classList.toggle("active", active);
26
+ button.setAttribute("aria-pressed", active ? "true" : "false");
27
+ }
28
+ if (row) row.classList.toggle("shell-command-mode", active);
29
+ if (input) {
30
+ if (!defaultPlaceholder) defaultPlaceholder = input.placeholder;
31
+ input.placeholder = active ? "Run a shell command in this project…" : defaultPlaceholder;
32
+ input.focus();
33
+ }
34
+ }
35
+
36
+ export function isShellCommandMode() {
37
+ return !!store.get("shellCommandMode");
38
+ }
39
+
40
+ export function toggleShellCommandMode() {
41
+ if (store.get("shellCommandRunning")) return;
42
+ var target = store.get("dmTargetUser");
43
+ if (store.get("dmMode") && target && !target.isMate) {
44
+ showToast("Shell commands are available in agent sessions, not user DMs.", "error");
45
+ return;
46
+ }
47
+ setMode(!isShellCommandMode());
48
+ }
49
+
50
+ function renderPendingCommand(requestId, command) {
51
+ var card = document.createElement("div");
52
+ card.className = "shell-command-card running";
53
+ card.dataset.requestId = requestId;
54
+ card.innerHTML =
55
+ '<div class="shell-command-header">' +
56
+ '<span class="shell-command-icon">' + iconHtml("square-terminal") + '</span>' +
57
+ '<code></code><span class="shell-command-status">Running…</span>' +
58
+ '</div>' +
59
+ '<pre class="shell-command-output">Waiting for output…</pre>';
60
+ card.querySelector("code").textContent = "$ " + command;
61
+ appendToMessages(card);
62
+ refreshIcons();
63
+ }
64
+
65
+ export function submitShellCommand(command) {
66
+ command = String(command || "").trim();
67
+ if (!command || store.get("shellCommandRunning")) return false;
68
+ var ws = getWs();
69
+ if (!ws || ws.readyState !== 1) {
70
+ showToast("Not connected — command not run.", "error");
71
+ return false;
72
+ }
73
+
74
+ var requestId = "shell_" + Date.now() + "_" + Math.random().toString(36).slice(2, 9);
75
+ store.set({ shellCommandRunning: true, pendingShellCommandId: requestId });
76
+ var input = getInput();
77
+ if (input) {
78
+ input.disabled = true;
79
+ input.placeholder = "Running command…";
80
+ }
81
+ renderPendingCommand(requestId, command);
82
+ ws.send(JSON.stringify({ type: "shell_command", requestId: requestId, command: command }));
83
+ return true;
84
+ }
85
+
86
+ export function handleShellCommandResult(msg) {
87
+ var cards = document.querySelectorAll(".shell-command-card[data-request-id]");
88
+ var card = null;
89
+ for (var i = 0; i < cards.length; i++) {
90
+ if (cards[i].dataset.requestId === (msg.requestId || "")) {
91
+ card = cards[i];
92
+ break;
93
+ }
94
+ }
95
+ if (card) {
96
+ var status = card.querySelector(".shell-command-status");
97
+ var output = card.querySelector(".shell-command-output");
98
+ card.classList.remove("running");
99
+ if (msg.error) {
100
+ card.classList.add("error");
101
+ if (status) status.textContent = "Failed";
102
+ if (output) output.textContent = msg.error;
103
+ } else {
104
+ card.classList.toggle("error", msg.exitCode !== 0);
105
+ if (status) status.textContent = msg.timedOut ? "Timed out" : "Exit " + (msg.exitCode == null ? "—" : msg.exitCode);
106
+ if (output) output.textContent = msg.output || "(no output)";
107
+ }
108
+ }
109
+
110
+ store.set({ shellCommandRunning: false, pendingShellCommandId: null });
111
+ var input = getInput();
112
+ if (input) input.disabled = false;
113
+ if (msg.error) {
114
+ setMode(true);
115
+ } else {
116
+ setMode(false);
117
+ }
118
+ var messages = document.getElementById("messages");
119
+ if (messages) requestAnimationFrame(function () { messages.scrollTop = messages.scrollHeight; });
120
+ }
121
+
122
+ export function initShellCommand() {
123
+ var button = document.getElementById("shell-command-btn");
124
+ var mobileButton = document.getElementById("input-more-shell");
125
+ if (button) button.addEventListener("click", toggleShellCommandMode);
126
+ if (mobileButton) {
127
+ mobileButton.addEventListener("click", function () {
128
+ var sheet = document.getElementById("input-more-sheet");
129
+ if (sheet) {
130
+ sheet.classList.remove("open");
131
+ setTimeout(function () { sheet.classList.add("hidden"); }, 250);
132
+ }
133
+ toggleShellCommandMode();
134
+ });
135
+ }
136
+ store.subscribe(function (state, previous) {
137
+ if (previous.connected && !state.connected && state.shellCommandRunning) {
138
+ resetShellCommand();
139
+ }
140
+ });
141
+ }
142
+
143
+ export function resetShellCommand() {
144
+ store.set({ shellCommandMode: false, shellCommandRunning: false, pendingShellCommandId: null });
145
+ var input = getInput();
146
+ if (input) input.disabled = false;
147
+ setMode(false);
148
+ }
@@ -460,7 +460,7 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
460
460
  var vendorLabels = {
461
461
  claude: "Claude Code",
462
462
  codex: "OpenAI Codex",
463
- gemini: "Gemini CLI",
463
+ antigravity: "Antigravity CLI",
464
464
  opencode: "OpenCode",
465
465
  kiro: "Kiro CLI",
466
466
  };
@@ -817,7 +817,7 @@ function resolvePermissionIdentity(mateId, vendor) {
817
817
  var vendorAvatars = {
818
818
  claude: "/claude-code-avatar.png",
819
819
  codex: "/codex-avatar.png",
820
- gemini: "/gemini-avatar.svg",
820
+ antigravity: "/antigravity-avatar.png",
821
821
  opencode: "/opencode-avatar.svg",
822
822
  kiro: "/kiro-avatar.svg",
823
823
  };
package/lib/sdk-bridge.js CHANGED
@@ -1528,6 +1528,10 @@ function createSDKBridge(opts) {
1528
1528
  ACP: {
1529
1529
  mcpServers: getAcpMcpServers(session),
1530
1530
  },
1531
+ ANTIGRAVITY: {
1532
+ dangerouslySkipPermissions: dangerouslySkipPermissions
1533
+ || session.permissionMode === "bypassPermissions",
1534
+ },
1531
1535
  KIRO: {
1532
1536
  engine: kiroConfig.engine,
1533
1537
  mode: kiroConfig.mode,
@@ -1790,16 +1794,20 @@ function createSDKBridge(opts) {
1790
1794
  } catch (e) {}
1791
1795
  if ((codexBin && fs.existsSync(codexBin)) || tryLookup(yoke.getVendorInfo("codex").binaryName)) result.push("codex");
1792
1796
 
1793
- var acpVendorKeys = ["gemini", "opencode"];
1797
+ var subprocessVendorKeys = ["antigravity", "opencode"];
1794
1798
  var acpProfiles = require("./yoke/acp-agent-profiles");
1795
- for (var acpVendorIndex = 0; acpVendorIndex < acpVendorKeys.length; acpVendorIndex++) {
1796
- var acpVendor = acpVendorKeys[acpVendorIndex];
1797
- var acpInfo = yoke.getVendorInfo(acpVendor);
1798
- if (!linuxUser || acpInfo.osUserIsolation) {
1799
- var acpProfile = acpProfiles.getAcpAgentProfile(acpVendor);
1800
- if (acpProfiles.findAcpAgentPath(acpProfile)) result.push(acpVendor);
1799
+ for (var subprocessVendorIndex = 0; subprocessVendorIndex < subprocessVendorKeys.length; subprocessVendorIndex++) {
1800
+ var subprocessVendor = subprocessVendorKeys[subprocessVendorIndex];
1801
+ var subprocessInfo = yoke.getVendorInfo(subprocessVendor);
1802
+ if (!linuxUser || subprocessInfo.osUserIsolation) {
1803
+ if (subprocessVendor === "antigravity") {
1804
+ if (tryLookup(subprocessInfo.binaryName)) result.push(subprocessVendor);
1805
+ } else {
1806
+ var acpProfile = acpProfiles.getAcpAgentProfile(subprocessVendor);
1807
+ if (acpProfiles.findAcpAgentPath(acpProfile)) result.push(subprocessVendor);
1808
+ }
1801
1809
  } else {
1802
- console.log("[sdk-bridge] " + acpInfo.displayName + " hidden for OS-isolated user " + linuxUser + ": per-user spawning is not implemented");
1810
+ console.log("[sdk-bridge] " + subprocessInfo.displayName + " hidden for OS-isolated user " + linuxUser + ": per-user spawning is not implemented");
1803
1811
  }
1804
1812
  }
1805
1813
 
@@ -792,7 +792,16 @@ function attachMessageProcessor(ctx) {
792
792
  // processQueryStream can finish the turn when the iterator closes.
793
793
  var adapterErrorText = parsed.text || parsed.message || parsed.error || "Agent runtime error";
794
794
  session._lastAdapterError = adapterErrorText;
795
- sendAndRecord(session, { type: "error", text: adapterErrorText });
795
+ var isSessionWriterConflict = /thread-store conflict|already has an active writer/i.test(adapterErrorText);
796
+ if (isSessionWriterConflict) {
797
+ sendAndRecord(session, {
798
+ type: "session_writer_conflict",
799
+ vendor: session.vendor || "codex",
800
+ text: "This Codex session is already open in another Clay or Codex process. Stop the other server or close the other session, then try again.",
801
+ });
802
+ } else {
803
+ sendAndRecord(session, { type: "error", text: adapterErrorText });
804
+ }
796
805
 
797
806
  } else if (parsed.yokeType === "model_refusal") {
798
807
  // Model declined the request. "fallback" => the CLI retried on another
package/lib/ws-schema.js CHANGED
@@ -145,6 +145,7 @@ var schema = {
145
145
  "kill_process": { direction: "c2s", handler: "lib/project-sessions.js", description: "Kill a system process by PID" },
146
146
  "process_killed": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Process was successfully killed" },
147
147
  "process_conflict": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Conflict: another process is using the session" },
148
+ "session_writer_conflict": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Codex session is already open in another process" },
148
149
 
149
150
  // -----------------------------------------------------------------------
150
151
  // Context / usage
@@ -353,6 +354,8 @@ var schema = {
353
354
  "term_closed": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Terminal session was closed" },
354
355
  "term_list": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full list of open terminals" },
355
356
  "term_error": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Terminal error (e.g. access denied)" },
357
+ "shell_command": { direction: "c2s", handler: "lib/project-shell-command.js", description: "Run a one-shot shell command for agent context" },
358
+ "shell_command_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "One-shot shell command output and exit status" },
356
359
 
357
360
  // -----------------------------------------------------------------------
358
361
  // Sticky notes
@@ -97,20 +97,6 @@ function validateOpenCodeConfig(config) {
97
97
  }
98
98
 
99
99
  var ACP_AGENT_PROFILES = {
100
- gemini: {
101
- vendor: "gemini",
102
- displayName: "Gemini CLI",
103
- binaryName: "gemini",
104
- overrideName: "GEMINI_CLI_PATH",
105
- args: ["--acp", "--approval-mode=default"],
106
- defaultModels: ["auto"],
107
- defaultModel: "auto",
108
- // Gemini advertises loadSession, but releases through 0.46.0 could load
109
- // the transcript without restoring the model's conversation memory.
110
- // Keep resume gated until Clay's live contract test proves it reliable.
111
- sessionResume: false,
112
- permissionModeGuaranteed: true,
113
- },
114
100
  opencode: {
115
101
  vendor: "opencode",
116
102
  displayName: "OpenCode",
@@ -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
+ };