@linkshell/host 0.4.29 → 0.4.31
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/dist/host/src/drivers/acp/antigravity.d.ts +88 -0
- package/dist/host/src/drivers/acp/antigravity.js +902 -0
- package/dist/host/src/drivers/acp/antigravity.js.map +1 -0
- package/dist/host/src/drivers/acp/connection.d.ts +1 -0
- package/dist/host/src/drivers/acp/connection.js +53 -4
- package/dist/host/src/drivers/acp/connection.js.map +1 -1
- package/dist/host/src/drivers/acp/driver.d.ts +1 -0
- package/dist/host/src/drivers/acp/driver.js +19 -2
- package/dist/host/src/drivers/acp/driver.js.map +1 -1
- package/dist/host/src/drivers/acp/mapper.js +83 -8
- package/dist/host/src/drivers/acp/mapper.js.map +1 -1
- package/dist/host/src/drivers/acp/questions.d.ts +1 -1
- package/dist/host/src/drivers/acp/questions.js +52 -2
- package/dist/host/src/drivers/acp/questions.js.map +1 -1
- package/dist/host/src/drivers/codex/desktop-ipc.d.ts +3 -0
- package/dist/host/src/drivers/codex/desktop-ipc.js +10 -1
- package/dist/host/src/drivers/codex/desktop-ipc.js.map +1 -1
- package/dist/host/src/drivers/codex/driver.js +19 -7
- package/dist/host/src/drivers/codex/driver.js.map +1 -1
- package/dist/host/src/drivers/registry.js +5 -1
- package/dist/host/src/drivers/registry.js.map +1 -1
- package/dist/host/src/worktrees.js +28 -2
- package/dist/host/src/worktrees.js.map +1 -1
- package/dist/host/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,902 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { accessSync, constants, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
4
|
+
import { basename, delimiter, dirname, isAbsolute, join } from "node:path";
|
|
5
|
+
import { DatabaseSync } from "node:sqlite";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { RpcError, } from "@linkshell/wire";
|
|
9
|
+
import { AcpDriver } from "./driver.js";
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
/**
|
|
12
|
+
* Official Google Antigravity ACP distribution metadata (from the ACP Registry:
|
|
13
|
+
* `antigravity-acp/agent.json`).
|
|
14
|
+
*/
|
|
15
|
+
export const ANTIGRAVITY_ACP_VERSION = "1.4.0";
|
|
16
|
+
export const ANTIGRAVITY_ACP_TARGETS = {
|
|
17
|
+
"darwin-arm64": {
|
|
18
|
+
archiveUrl: "https://dl.google.com/agy-extensions/releases/macos/agy-acp-server-1.4.0-macos-aarch64.zip",
|
|
19
|
+
binaryName: "agy_acp_server.par",
|
|
20
|
+
args: [],
|
|
21
|
+
sha256: "e02a46db4de4fbcc601a666b385fb1956cc67247221303f8a3e1345721dc2ac2",
|
|
22
|
+
},
|
|
23
|
+
"darwin-x64": {
|
|
24
|
+
archiveUrl: "https://dl.google.com/agy-extensions/releases/macos/agy-acp-server-1.4.0-macos-x86_64.zip",
|
|
25
|
+
binaryName: "agy_acp_server.par",
|
|
26
|
+
args: [],
|
|
27
|
+
sha256: "3d841c240014380c0d45184145020ed86c17213e5d7019801d2777a22731969a",
|
|
28
|
+
},
|
|
29
|
+
"linux-x64": {
|
|
30
|
+
archiveUrl: "https://dl.google.com/agy-extensions/releases/linux/agy-acp-server-1.4.0-linux-x86_64.zip",
|
|
31
|
+
binaryName: "agy_acp_server.par",
|
|
32
|
+
args: ["--uid="],
|
|
33
|
+
sha256: "b64e34ab83b848b7cf75552135a4b606196b45bc0a3f97409964f4b18040e523",
|
|
34
|
+
},
|
|
35
|
+
"linux-arm64": {
|
|
36
|
+
archiveUrl: "https://dl.google.com/agy-extensions/releases/linux/agy-acp-server-1.4.0-linux-aarch64.zip",
|
|
37
|
+
binaryName: "agy_acp_server.par",
|
|
38
|
+
args: ["--uid="],
|
|
39
|
+
sha256: "004bd8f9c88e061923479a50203a1545be398eb4b9a9c8ed181f29a8f1ca45b0",
|
|
40
|
+
},
|
|
41
|
+
"win32-x64": {
|
|
42
|
+
archiveUrl: "https://dl.google.com/agy-extensions/releases/windows/agy-acp-server-1.4.0-windows-x86_64.zip",
|
|
43
|
+
binaryName: "agy_acp_server.exe",
|
|
44
|
+
args: [],
|
|
45
|
+
sha256: "d62bc2a1ca587849ecce838574523886729be3fb12867d22a666f149401325de",
|
|
46
|
+
},
|
|
47
|
+
"win32-arm64": {
|
|
48
|
+
archiveUrl: "https://dl.google.com/agy-extensions/releases/windows/agy-acp-server-1.4.0-windows-aarch64.zip",
|
|
49
|
+
binaryName: "agy_acp_server.exe",
|
|
50
|
+
args: [],
|
|
51
|
+
sha256: "d62bc2a1ca587849ecce838574523886729be3fb12867d22a666f149401325de",
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
function isExecutable(filePath) {
|
|
55
|
+
try {
|
|
56
|
+
accessSync(filePath, process.platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function findOnPath(names, env) {
|
|
64
|
+
const pathVar = env.PATH ?? process.env.PATH ?? "";
|
|
65
|
+
for (const name of names) {
|
|
66
|
+
if (isAbsolute(name) && isExecutable(name))
|
|
67
|
+
return name;
|
|
68
|
+
for (const dir of pathVar.split(delimiter)) {
|
|
69
|
+
if (!dir)
|
|
70
|
+
continue;
|
|
71
|
+
const candidate = join(dir, name);
|
|
72
|
+
if (isExecutable(candidate))
|
|
73
|
+
return candidate;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
function findInManagedToolRoots(roots, binaryName) {
|
|
79
|
+
const targetKey = `${process.platform}-${process.arch}`;
|
|
80
|
+
for (const root of roots) {
|
|
81
|
+
if (!existsSync(root))
|
|
82
|
+
continue;
|
|
83
|
+
const direct = join(root, binaryName);
|
|
84
|
+
if (isExecutable(direct))
|
|
85
|
+
return { path: direct, version: ANTIGRAVITY_ACP_VERSION };
|
|
86
|
+
try {
|
|
87
|
+
const versions = readdirSync(root).sort().reverse();
|
|
88
|
+
for (const version of versions) {
|
|
89
|
+
const archCandidate = join(root, version, targetKey, binaryName);
|
|
90
|
+
if (isExecutable(archCandidate))
|
|
91
|
+
return { path: archCandidate, version };
|
|
92
|
+
const flatCandidate = join(root, version, binaryName);
|
|
93
|
+
if (isExecutable(flatCandidate))
|
|
94
|
+
return { path: flatCandidate, version };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// ignore unreadable directories
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Resolves the Antigravity ACP server binary, sibling localharness binary,
|
|
105
|
+
* required environment variables (`AGY_ACP_FORCE_FILE_STORAGE=1`, `PYTHONUNBUFFERED=1`),
|
|
106
|
+
* and platform-specific arguments (`--uid=` on Linux for `.par`).
|
|
107
|
+
*/
|
|
108
|
+
export function resolveAntigravityLaunch(env = process.env) {
|
|
109
|
+
const binaryName = process.platform === "win32" ? "agy_acp_server.exe" : "agy_acp_server.par";
|
|
110
|
+
const harnessName = process.platform === "win32" ? "localharness_external.exe" : "localharness_external";
|
|
111
|
+
const explicitPath = env.LINKSHELL_ANTIGRAVITY_ACP_PATH?.trim() || env.T3CODE_ANTIGRAVITY_ACP_PATH?.trim();
|
|
112
|
+
let binaryPath;
|
|
113
|
+
let version = ANTIGRAVITY_ACP_VERSION;
|
|
114
|
+
if (explicitPath && isExecutable(explicitPath)) {
|
|
115
|
+
binaryPath = explicitPath;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const home = env.HOME || homedir();
|
|
119
|
+
const linkshellHome = env.LINKSHELL_HOME || join(home, ".linkshell");
|
|
120
|
+
const toolRoots = [
|
|
121
|
+
join(linkshellHome, "tools", "antigravity-acp"),
|
|
122
|
+
join(home, ".t3", "tools", "antigravity-acp"),
|
|
123
|
+
...(process.platform === "darwin"
|
|
124
|
+
? [
|
|
125
|
+
join(home, "Library", "Application Support", "t3code (Alpha)", "tools", "antigravity-acp"),
|
|
126
|
+
join(home, "Library", "Application Support", "t3code", "tools", "antigravity-acp"),
|
|
127
|
+
]
|
|
128
|
+
: []),
|
|
129
|
+
];
|
|
130
|
+
const managed = findInManagedToolRoots(toolRoots, binaryName);
|
|
131
|
+
if (managed) {
|
|
132
|
+
binaryPath = managed.path;
|
|
133
|
+
version = managed.version;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
binaryPath = findOnPath(process.platform === "win32"
|
|
137
|
+
? ["agy-acp.exe", "agy_acp_server.exe", "agy-acp", "agy_acp_server.par"]
|
|
138
|
+
: ["agy-acp", "agy_acp_server", "agy_acp_server.par"], env);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!binaryPath)
|
|
142
|
+
return undefined;
|
|
143
|
+
const dir = dirname(binaryPath);
|
|
144
|
+
const siblingHarness = join(dir, harnessName);
|
|
145
|
+
const harnessPath = env.ANTIGRAVITY_HARNESS_PATH?.trim() || (isExecutable(siblingHarness) ? siblingHarness : undefined);
|
|
146
|
+
const dedicatedTmp = join(tmpdir(), "linkshell-antigravity-tmp");
|
|
147
|
+
try {
|
|
148
|
+
mkdirSync(dedicatedTmp, { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// fallback to system tmpdir if creation fails
|
|
152
|
+
}
|
|
153
|
+
const isPar = basename(binaryPath).endsWith(".par");
|
|
154
|
+
const args = process.platform === "linux" && isPar ? ["--uid="] : [];
|
|
155
|
+
const launchEnv = {
|
|
156
|
+
AGY_ACP_FORCE_FILE_STORAGE: env.AGY_ACP_FORCE_FILE_STORAGE ?? "1",
|
|
157
|
+
PYTHONUNBUFFERED: env.PYTHONUNBUFFERED ?? "1",
|
|
158
|
+
TMPDIR: dedicatedTmp,
|
|
159
|
+
TMP: dedicatedTmp,
|
|
160
|
+
TEMP: dedicatedTmp,
|
|
161
|
+
...(harnessPath ? { ANTIGRAVITY_HARNESS_PATH: harnessPath } : {}),
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
command: binaryPath,
|
|
165
|
+
args,
|
|
166
|
+
env: launchEnv,
|
|
167
|
+
version,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Resolves an installed Antigravity Desktop app (`/Applications/Antigravity.app`)
|
|
172
|
+
* and its conversation store (`~/.gemini/antigravity`).
|
|
173
|
+
*/
|
|
174
|
+
export function resolveAntigravityDesktop(env = process.env) {
|
|
175
|
+
if (env.PATH === "" && !env.ANTIGRAVITY_APP_DATA_DIR)
|
|
176
|
+
return undefined;
|
|
177
|
+
const home = env.HOME || homedir();
|
|
178
|
+
const dataCandidates = [
|
|
179
|
+
env.ANTIGRAVITY_APP_DATA_DIR?.trim(),
|
|
180
|
+
join(home, ".gemini", "antigravity"),
|
|
181
|
+
join(home, ".gemini", "antigravity-ide"),
|
|
182
|
+
].filter((entry) => Boolean(entry));
|
|
183
|
+
const dataDir = dataCandidates.find((dir) => existsSync(join(dir, "conversation_summaries.db")) || existsSync(join(dir, "brain")));
|
|
184
|
+
const lsCandidates = process.platform === "darwin"
|
|
185
|
+
? [
|
|
186
|
+
"/Applications/Antigravity.app/Contents/Resources/bin/language_server",
|
|
187
|
+
join(home, "Applications", "Antigravity.app", "Contents", "Resources", "bin", "language_server"),
|
|
188
|
+
]
|
|
189
|
+
: [];
|
|
190
|
+
const languageServerPath = lsCandidates.find((candidate) => isExecutable(candidate));
|
|
191
|
+
if (!dataDir && !languageServerPath)
|
|
192
|
+
return undefined;
|
|
193
|
+
const logCandidates = process.platform === "darwin"
|
|
194
|
+
? [join(home, "Library", "Logs", "Antigravity", "language_server.log")]
|
|
195
|
+
: [join(home, ".config", "Antigravity", "logs", "language_server.log")];
|
|
196
|
+
const logPath = logCandidates.find((candidate) => existsSync(candidate));
|
|
197
|
+
let version = "desktop";
|
|
198
|
+
if (logPath) {
|
|
199
|
+
try {
|
|
200
|
+
const head = readFileSync(logPath, "utf8").slice(0, 2048);
|
|
201
|
+
const match = /Language server version:\s*([^\s\r\n]+)/i.exec(head);
|
|
202
|
+
if (match?.[1])
|
|
203
|
+
version = match[1];
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// ignore unreadable log
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (version === "desktop" && process.platform === "darwin") {
|
|
210
|
+
try {
|
|
211
|
+
const plist = readFileSync("/Applications/Antigravity.app/Contents/Info.plist", "utf8");
|
|
212
|
+
const match = /<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/.exec(plist);
|
|
213
|
+
if (match?.[1])
|
|
214
|
+
version = match[1].trim();
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// ignore unreadable plist
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
dataDir: dataDir ?? join(home, ".gemini", "antigravity"),
|
|
222
|
+
languageServerPath,
|
|
223
|
+
logPath,
|
|
224
|
+
version,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const AUTH_URL_PATTERN = /Open the following link to authenticate the ACP server:\s*(https?:\/\/\S+)/i;
|
|
228
|
+
export function extractAntigravityAuthUrl(line) {
|
|
229
|
+
const match = AUTH_URL_PATTERN.exec(line.trim());
|
|
230
|
+
return match?.[1];
|
|
231
|
+
}
|
|
232
|
+
export async function antigravityAuthStatus(env) {
|
|
233
|
+
if (env.GEMINI_API_KEY?.trim()) {
|
|
234
|
+
return { state: "ok", method: "gemini-api-key" };
|
|
235
|
+
}
|
|
236
|
+
if (env.GOOGLE_API_KEY?.trim() || (env.GOOGLE_CLOUD_PROJECT?.trim() && env.GOOGLE_CLOUD_LOCATION?.trim())) {
|
|
237
|
+
return { state: "ok", method: "agent-platform" };
|
|
238
|
+
}
|
|
239
|
+
return { state: "unknown" };
|
|
240
|
+
}
|
|
241
|
+
export function antigravitySpec() {
|
|
242
|
+
return {
|
|
243
|
+
id: "antigravity",
|
|
244
|
+
label: "Antigravity",
|
|
245
|
+
tier: "remote",
|
|
246
|
+
command: "agy-acp",
|
|
247
|
+
args: [],
|
|
248
|
+
discover: true,
|
|
249
|
+
authStatus: antigravityAuthStatus,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function unquoteArg(value) {
|
|
253
|
+
if (typeof value !== "string")
|
|
254
|
+
return value;
|
|
255
|
+
const trimmed = value.trim();
|
|
256
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
257
|
+
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
258
|
+
(trimmed.startsWith("[") && trimmed.endsWith("]")) ||
|
|
259
|
+
trimmed === "true" ||
|
|
260
|
+
trimmed === "false" ||
|
|
261
|
+
/^-?\d+(?:\.\d+)?$/.test(trimmed)) {
|
|
262
|
+
try {
|
|
263
|
+
return JSON.parse(trimmed);
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return value;
|
|
270
|
+
}
|
|
271
|
+
function parseToolArgs(raw) {
|
|
272
|
+
if (!raw || typeof raw !== "object")
|
|
273
|
+
return {};
|
|
274
|
+
const result = {};
|
|
275
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
276
|
+
result[k] = unquoteArg(v);
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
export function cleanAntigravityUserInput(content) {
|
|
281
|
+
const requestMatch = /<USER_REQUEST>\s*([\s\S]*?)\s*<\/USER_REQUEST>/i.exec(content);
|
|
282
|
+
if (requestMatch)
|
|
283
|
+
return requestMatch[1].trim();
|
|
284
|
+
return content
|
|
285
|
+
.replace(/<(?:ADDITIONAL_METADATA|USER_SETTINGS_CHANGE|SYSTEM_MESSAGE|CONTEXT_SUMMARY)>[\s\S]*?<\/(?:ADDITIONAL_METADATA|USER_SETTINGS_CHANGE|SYSTEM_MESSAGE|CONTEXT_SUMMARY)>/gi, "")
|
|
286
|
+
.trim();
|
|
287
|
+
}
|
|
288
|
+
function mapAntigravityToolCall(stepIndex, callIndex, call, outputText) {
|
|
289
|
+
const name = call.name ?? "tool";
|
|
290
|
+
const args = parseToolArgs(call.args);
|
|
291
|
+
const summary = (typeof args.toolSummary === "string" && args.toolSummary.trim()) ||
|
|
292
|
+
(typeof args.toolAction === "string" && args.toolAction.trim()) ||
|
|
293
|
+
name;
|
|
294
|
+
const toolCallId = call.id || `agy:tc:${stepIndex}:${callIndex}`;
|
|
295
|
+
let kind = "other";
|
|
296
|
+
let rawInput = args;
|
|
297
|
+
let locations;
|
|
298
|
+
if (name === "run_command") {
|
|
299
|
+
kind = "execute";
|
|
300
|
+
rawInput = {
|
|
301
|
+
command: typeof args.CommandLine === "string" ? args.CommandLine : summary,
|
|
302
|
+
cwd: typeof args.Cwd === "string" ? args.Cwd : undefined,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
else if (name === "view_file") {
|
|
306
|
+
kind = "read";
|
|
307
|
+
const path = typeof args.AbsolutePath === "string" ? args.AbsolutePath : undefined;
|
|
308
|
+
const line = typeof args.StartLine === "number" ? args.StartLine : undefined;
|
|
309
|
+
if (path) {
|
|
310
|
+
rawInput = { file_path: path };
|
|
311
|
+
locations = [{ path, ...(line ? { line } : {}) }];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else if (name === "replace_file_content" || name === "multi_replace_file_content" || name === "write_to_file") {
|
|
315
|
+
kind = "edit";
|
|
316
|
+
const path = typeof args.TargetFile === "string" ? args.TargetFile : undefined;
|
|
317
|
+
const line = typeof args.StartLine === "number" ? args.StartLine : undefined;
|
|
318
|
+
if (path) {
|
|
319
|
+
rawInput = { file_path: path };
|
|
320
|
+
locations = [{ path, ...(line ? { line } : {}) }];
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else if (name === "search_web") {
|
|
324
|
+
kind = "search";
|
|
325
|
+
rawInput = { query: typeof args.query === "string" ? args.query : summary };
|
|
326
|
+
}
|
|
327
|
+
else if (name === "read_url_content") {
|
|
328
|
+
kind = "fetch";
|
|
329
|
+
rawInput = { url: typeof args.Url === "string" ? args.Url : summary };
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
sessionUpdate: "tool_call",
|
|
333
|
+
toolCallId,
|
|
334
|
+
title: summary,
|
|
335
|
+
name,
|
|
336
|
+
kind,
|
|
337
|
+
status: "completed",
|
|
338
|
+
rawInput,
|
|
339
|
+
...(outputText ? { rawOutput: { output: outputText } } : {}),
|
|
340
|
+
...(locations ? { locations } : {}),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Converts an Antigravity Desktop `transcript.jsonl` file into LinkShell's
|
|
345
|
+
* `HistoryItem[]` list.
|
|
346
|
+
*/
|
|
347
|
+
export function parseAntigravityTranscript(transcriptPath, fromLine = 0) {
|
|
348
|
+
if (!existsSync(transcriptPath))
|
|
349
|
+
return { items: [], nextLine: fromLine };
|
|
350
|
+
let lines;
|
|
351
|
+
try {
|
|
352
|
+
lines = readFileSync(transcriptPath, "utf8").split("\n").filter((line) => line.trim().length > 0);
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return { items: [], nextLine: fromLine };
|
|
356
|
+
}
|
|
357
|
+
if (fromLine >= lines.length)
|
|
358
|
+
return { items: [], nextLine: lines.length };
|
|
359
|
+
const parsed = [];
|
|
360
|
+
for (let i = fromLine; i < lines.length; i++) {
|
|
361
|
+
try {
|
|
362
|
+
const step = JSON.parse(lines[i]);
|
|
363
|
+
parsed.push({ index: step.step_index ?? i, step });
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// skip partial trailing JSON line
|
|
367
|
+
return { items: [], nextLine: i };
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const items = [];
|
|
371
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
372
|
+
const { index: stepIndex, step } = parsed[i];
|
|
373
|
+
if (step.type === "USER_INPUT") {
|
|
374
|
+
const text = typeof step.content === "string" ? cleanAntigravityUserInput(step.content) : "";
|
|
375
|
+
if (!text)
|
|
376
|
+
continue;
|
|
377
|
+
const messageId = `agy:user:${stepIndex}`;
|
|
378
|
+
items.push({
|
|
379
|
+
itemId: messageId,
|
|
380
|
+
updates: [
|
|
381
|
+
{
|
|
382
|
+
sessionUpdate: "user_message_chunk",
|
|
383
|
+
messageId,
|
|
384
|
+
content: { type: "text", text },
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
else if (step.type === "PLANNER_RESPONSE") {
|
|
390
|
+
const updates = [];
|
|
391
|
+
if (typeof step.thinking === "string" && step.thinking.trim()) {
|
|
392
|
+
const thoughtId = `agy:thought:${stepIndex}`;
|
|
393
|
+
updates.push({
|
|
394
|
+
sessionUpdate: "agent_thought_chunk",
|
|
395
|
+
messageId: thoughtId,
|
|
396
|
+
content: { type: "text", text: step.thinking.trim() },
|
|
397
|
+
}, {
|
|
398
|
+
sessionUpdate: "ls_message_done",
|
|
399
|
+
messageId: thoughtId,
|
|
400
|
+
role: "thought",
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
const calls = Array.isArray(step.tool_calls) ? step.tool_calls : [];
|
|
404
|
+
if (calls.length > 0) {
|
|
405
|
+
// Subsequent GENERIC steps hold the outputs of these tool calls in order.
|
|
406
|
+
const outputs = [];
|
|
407
|
+
let lookahead = i + 1;
|
|
408
|
+
while (lookahead < parsed.length && parsed[lookahead].step.type === "GENERIC" && outputs.length < calls.length) {
|
|
409
|
+
outputs.push(typeof parsed[lookahead].step.content === "string" ? parsed[lookahead].step.content : "");
|
|
410
|
+
lookahead++;
|
|
411
|
+
}
|
|
412
|
+
calls.forEach((call, callIdx) => {
|
|
413
|
+
updates.push(mapAntigravityToolCall(stepIndex, callIdx, call, outputs[callIdx]));
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
if (typeof step.content === "string" && step.content.trim()) {
|
|
417
|
+
const messageId = `agy:msg:${stepIndex}`;
|
|
418
|
+
updates.push({
|
|
419
|
+
sessionUpdate: "agent_message_chunk",
|
|
420
|
+
messageId,
|
|
421
|
+
content: { type: "text", text: step.content.trim() },
|
|
422
|
+
}, {
|
|
423
|
+
sessionUpdate: "ls_message_done",
|
|
424
|
+
messageId,
|
|
425
|
+
role: "agent",
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
if (updates.length > 0) {
|
|
429
|
+
items.push({
|
|
430
|
+
itemId: `agy:step:${stepIndex}`,
|
|
431
|
+
updates,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return { items, nextLine: lines.length };
|
|
437
|
+
}
|
|
438
|
+
async function findRunningLanguageServer(desktop) {
|
|
439
|
+
if (!desktop?.logPath || !existsSync(desktop.logPath))
|
|
440
|
+
return undefined;
|
|
441
|
+
let httpPort;
|
|
442
|
+
try {
|
|
443
|
+
const head = readFileSync(desktop.logPath, "utf8").slice(0, 4096);
|
|
444
|
+
const match = /listening on \w+ port at (\d+) for HTTP\b/i.exec(head);
|
|
445
|
+
if (match?.[1])
|
|
446
|
+
httpPort = Number(match[1]);
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
if (!httpPort)
|
|
452
|
+
return undefined;
|
|
453
|
+
try {
|
|
454
|
+
const { stdout } = await execFileAsync("ps", ["-ax", "-o", "args="], { timeout: 3000 });
|
|
455
|
+
for (const line of stdout.split("\n")) {
|
|
456
|
+
if (!line.includes("language_server") || !line.includes("--csrf_token"))
|
|
457
|
+
continue;
|
|
458
|
+
const tokenMatch = /--csrf_token[=\s]+([^\s]+)/.exec(line);
|
|
459
|
+
if (tokenMatch?.[1]) {
|
|
460
|
+
return { httpPort, csrfToken: tokenMatch[1] };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
// ignore ps errors
|
|
466
|
+
}
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
async function callLanguageServer(conn, method, body) {
|
|
470
|
+
const response = await fetch(`http://127.0.0.1:${conn.httpPort}/exa.language_server_pb.LanguageServerService/${method}`, {
|
|
471
|
+
method: "POST",
|
|
472
|
+
headers: {
|
|
473
|
+
"Content-Type": "application/json",
|
|
474
|
+
"x-codeium-csrf-token": conn.csrfToken,
|
|
475
|
+
},
|
|
476
|
+
body: JSON.stringify(body),
|
|
477
|
+
signal: AbortSignal.timeout(10_000),
|
|
478
|
+
});
|
|
479
|
+
if (!response.ok) {
|
|
480
|
+
const text = await response.text().catch(() => "");
|
|
481
|
+
throw new Error(`Antigravity Desktop RPC ${method} failed (${response.status}): ${text.slice(0, 200)}`);
|
|
482
|
+
}
|
|
483
|
+
return (await response.json());
|
|
484
|
+
}
|
|
485
|
+
function parseWorkspaceCwd(workspaceUrisJson, fallbackCwd) {
|
|
486
|
+
if (!workspaceUrisJson)
|
|
487
|
+
return fallbackCwd;
|
|
488
|
+
try {
|
|
489
|
+
const uris = JSON.parse(workspaceUrisJson);
|
|
490
|
+
if (Array.isArray(uris) && typeof uris[0] === "string") {
|
|
491
|
+
const first = uris[0];
|
|
492
|
+
if (first.startsWith("file://"))
|
|
493
|
+
return fileURLToPath(first);
|
|
494
|
+
if (isAbsolute(first))
|
|
495
|
+
return first;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
// ignore invalid JSON
|
|
500
|
+
}
|
|
501
|
+
return fallbackCwd;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Specialized driver for Google Antigravity:
|
|
505
|
+
* - Drives `agy_acp_server.par` / `agy-acp` over ACP when installed.
|
|
506
|
+
* - Also discovers and interacts with Antigravity Desktop (`/Applications/Antigravity.app`,
|
|
507
|
+
* `~/.gemini/antigravity/conversation_summaries.db`, `brain/<id>/.system_generated/logs/transcript.jsonl`,
|
|
508
|
+
* and its local `language_server` Connect-RPC service) so users with only the Desktop app
|
|
509
|
+
* get full session visibility, history, model selection, and messaging.
|
|
510
|
+
*/
|
|
511
|
+
export class AntigravityDriver extends AcpDriver {
|
|
512
|
+
desktopMode = false;
|
|
513
|
+
desktopWatchers = new Map();
|
|
514
|
+
desktopModelsCache;
|
|
515
|
+
constructor(options) {
|
|
516
|
+
super(antigravitySpec(), options);
|
|
517
|
+
}
|
|
518
|
+
get env() {
|
|
519
|
+
const resolved = resolveAntigravityLaunch(this.options.env ?? process.env);
|
|
520
|
+
return {
|
|
521
|
+
...(this.options.env ?? process.env),
|
|
522
|
+
...this.spec.env,
|
|
523
|
+
...resolved?.env,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
async authStatus() {
|
|
527
|
+
const base = await super.authStatus();
|
|
528
|
+
if (base.state !== "unknown")
|
|
529
|
+
return base;
|
|
530
|
+
const desktop = resolveAntigravityDesktop(this.options.env ?? process.env);
|
|
531
|
+
if (desktop && (existsSync(join(desktop.dataDir, "antigravity_state.pbtxt")) || existsSync(join(desktop.dataDir, "conversation_summaries.db")))) {
|
|
532
|
+
return { state: "ok", method: "antigravity-desktop" };
|
|
533
|
+
}
|
|
534
|
+
return base;
|
|
535
|
+
}
|
|
536
|
+
async detectVersion() {
|
|
537
|
+
const env = this.options.env ?? process.env;
|
|
538
|
+
const resolved = resolveAntigravityLaunch(env);
|
|
539
|
+
if (resolved) {
|
|
540
|
+
this.desktopMode = false;
|
|
541
|
+
this.spec.command = resolved.command;
|
|
542
|
+
this.spec.args = resolved.args;
|
|
543
|
+
return resolved.version;
|
|
544
|
+
}
|
|
545
|
+
const desktop = resolveAntigravityDesktop(env);
|
|
546
|
+
if (desktop) {
|
|
547
|
+
this.desktopMode = true;
|
|
548
|
+
return desktop.version;
|
|
549
|
+
}
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
async boot() {
|
|
553
|
+
const env = this.options.env ?? process.env;
|
|
554
|
+
const resolved = resolveAntigravityLaunch(env);
|
|
555
|
+
if (resolved) {
|
|
556
|
+
this.desktopMode = false;
|
|
557
|
+
this.spec.command = resolved.command;
|
|
558
|
+
this.spec.args = resolved.args;
|
|
559
|
+
await super.boot();
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
const desktop = resolveAntigravityDesktop(env);
|
|
563
|
+
if (desktop) {
|
|
564
|
+
this.desktopMode = true;
|
|
565
|
+
this.capabilityState = {
|
|
566
|
+
...this.capabilityState,
|
|
567
|
+
images: true,
|
|
568
|
+
models: true,
|
|
569
|
+
};
|
|
570
|
+
this.current = { installed: true, version: desktop.version };
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
await super.boot();
|
|
574
|
+
}
|
|
575
|
+
async stop() {
|
|
576
|
+
for (const watcher of this.desktopWatchers.values())
|
|
577
|
+
clearInterval(watcher.timer);
|
|
578
|
+
this.desktopWatchers.clear();
|
|
579
|
+
await super.stop();
|
|
580
|
+
}
|
|
581
|
+
transcriptPathFor(nativeId) {
|
|
582
|
+
const desktop = resolveAntigravityDesktop(this.options.env ?? process.env);
|
|
583
|
+
if (!desktop)
|
|
584
|
+
return undefined;
|
|
585
|
+
return join(desktop.dataDir, "brain", nativeId, ".system_generated", "logs", "transcript.jsonl");
|
|
586
|
+
}
|
|
587
|
+
isConversationRunningInDesktop(nativeId, statusCol) {
|
|
588
|
+
const desktop = resolveAntigravityDesktop(this.options.env ?? process.env);
|
|
589
|
+
if (!desktop)
|
|
590
|
+
return false;
|
|
591
|
+
const lockPath = join(desktop.dataDir, "presence", `${nativeId}.lock`);
|
|
592
|
+
if (statusCol && statusCol !== "CASCADE_RUN_STATUS_RUNNING")
|
|
593
|
+
return false;
|
|
594
|
+
return existsSync(lockPath) && statusCol === "CASCADE_RUN_STATUS_RUNNING";
|
|
595
|
+
}
|
|
596
|
+
listDesktopSessions(limit) {
|
|
597
|
+
const desktop = resolveAntigravityDesktop(this.options.env ?? process.env);
|
|
598
|
+
if (!desktop)
|
|
599
|
+
return [];
|
|
600
|
+
const dbPath = join(desktop.dataDir, "conversation_summaries.db");
|
|
601
|
+
const home = (this.options.env ?? process.env).HOME || homedir();
|
|
602
|
+
const results = [];
|
|
603
|
+
if (existsSync(dbPath)) {
|
|
604
|
+
let db;
|
|
605
|
+
try {
|
|
606
|
+
db = new DatabaseSync(dbPath, { readOnly: true });
|
|
607
|
+
const rows = db
|
|
608
|
+
.prepare(`SELECT conversation_id, title, last_modified_time, workspace_uris, status, parent_conversation_id, killed
|
|
609
|
+
FROM conversation_summaries
|
|
610
|
+
WHERE (parent_conversation_id = '' OR parent_conversation_id IS NULL) AND killed = 0
|
|
611
|
+
ORDER BY last_modified_time DESC
|
|
612
|
+
LIMIT ?`)
|
|
613
|
+
.all(limit);
|
|
614
|
+
for (const row of rows) {
|
|
615
|
+
if (!row.conversation_id)
|
|
616
|
+
continue;
|
|
617
|
+
const cwd = parseWorkspaceCwd(row.workspace_uris, home);
|
|
618
|
+
const updatedAt = Date.parse(row.last_modified_time) || Date.now();
|
|
619
|
+
let title = row.title?.trim() || undefined;
|
|
620
|
+
if (!title) {
|
|
621
|
+
const transcriptPath = join(desktop.dataDir, "brain", row.conversation_id, ".system_generated", "logs", "transcript.jsonl");
|
|
622
|
+
const { items } = parseAntigravityTranscript(transcriptPath, 0);
|
|
623
|
+
const firstUser = items.find((entry) => entry.updates.some((u) => u.sessionUpdate === "user_message_chunk"));
|
|
624
|
+
const chunk = firstUser?.updates.find((u) => u.sessionUpdate === "user_message_chunk");
|
|
625
|
+
if (chunk?.sessionUpdate === "user_message_chunk" && chunk.content.type === "text") {
|
|
626
|
+
title = chunk.content.text.split("\n")[0]?.slice(0, 100).trim() || undefined;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
const running = this.isConversationRunningInDesktop(row.conversation_id, row.status);
|
|
630
|
+
results.push({
|
|
631
|
+
nativeId: row.conversation_id,
|
|
632
|
+
cwd,
|
|
633
|
+
title,
|
|
634
|
+
createdAt: updatedAt,
|
|
635
|
+
updatedAt,
|
|
636
|
+
state: running ? "running" : "idle",
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
catch {
|
|
641
|
+
// fallback to brain directory scan below if sqlite table is locked or unreadable
|
|
642
|
+
}
|
|
643
|
+
finally {
|
|
644
|
+
try {
|
|
645
|
+
db?.close();
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
// ignore close errors
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (results.length === 0 && existsSync(join(desktop.dataDir, "brain"))) {
|
|
653
|
+
try {
|
|
654
|
+
const dirs = readdirSync(join(desktop.dataDir, "brain"));
|
|
655
|
+
for (const id of dirs) {
|
|
656
|
+
const transcriptPath = join(desktop.dataDir, "brain", id, ".system_generated", "logs", "transcript.jsonl");
|
|
657
|
+
if (!existsSync(transcriptPath))
|
|
658
|
+
continue;
|
|
659
|
+
const stat = statSync(transcriptPath);
|
|
660
|
+
const { items } = parseAntigravityTranscript(transcriptPath, 0);
|
|
661
|
+
const firstUser = items.find((entry) => entry.updates.some((u) => u.sessionUpdate === "user_message_chunk"));
|
|
662
|
+
const chunk = firstUser?.updates.find((u) => u.sessionUpdate === "user_message_chunk");
|
|
663
|
+
const title = chunk?.sessionUpdate === "user_message_chunk" && chunk.content.type === "text"
|
|
664
|
+
? chunk.content.text.split("\n")[0]?.slice(0, 100).trim()
|
|
665
|
+
: undefined;
|
|
666
|
+
results.push({
|
|
667
|
+
nativeId: id,
|
|
668
|
+
cwd: home,
|
|
669
|
+
title,
|
|
670
|
+
createdAt: stat.mtimeMs,
|
|
671
|
+
updatedAt: stat.mtimeMs,
|
|
672
|
+
state: "idle",
|
|
673
|
+
});
|
|
674
|
+
if (results.length >= limit)
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
// ignore unreadable brain dir
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return results;
|
|
683
|
+
}
|
|
684
|
+
async listSessions(limit) {
|
|
685
|
+
const acpSessions = !this.desktopMode && this.connection?.alive ? await super.listSessions(limit).catch(() => []) : [];
|
|
686
|
+
const desktopSessions = this.listDesktopSessions(limit);
|
|
687
|
+
const merged = new Map();
|
|
688
|
+
for (const session of [...acpSessions, ...desktopSessions]) {
|
|
689
|
+
if (!merged.has(session.nativeId))
|
|
690
|
+
merged.set(session.nativeId, session);
|
|
691
|
+
}
|
|
692
|
+
return [...merged.values()].sort((a, b) => b.updatedAt - a.updatedAt).slice(0, limit);
|
|
693
|
+
}
|
|
694
|
+
async loadDesktopModels() {
|
|
695
|
+
if (this.desktopModelsCache?.length)
|
|
696
|
+
return this.desktopModelsCache;
|
|
697
|
+
const desktop = resolveAntigravityDesktop(this.options.env ?? process.env);
|
|
698
|
+
const conn = await findRunningLanguageServer(desktop);
|
|
699
|
+
if (!conn)
|
|
700
|
+
return [];
|
|
701
|
+
try {
|
|
702
|
+
const data = await callLanguageServer(conn, "GetCascadeModelConfigData", {});
|
|
703
|
+
const models = (data.clientModelConfigs ?? [])
|
|
704
|
+
.map((entry) => {
|
|
705
|
+
const value = entry.modelOrAlias?.model ?? entry.modelOrAlias?.alias;
|
|
706
|
+
const name = entry.label?.trim();
|
|
707
|
+
if (!value || !name)
|
|
708
|
+
return undefined;
|
|
709
|
+
return {
|
|
710
|
+
value,
|
|
711
|
+
name,
|
|
712
|
+
...(entry.tagTitle ? { description: entry.tagTitle } : {}),
|
|
713
|
+
};
|
|
714
|
+
})
|
|
715
|
+
.filter((entry) => Boolean(entry));
|
|
716
|
+
if (models.length > 0)
|
|
717
|
+
this.desktopModelsCache = models;
|
|
718
|
+
return models;
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
return [];
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
async emitDesktopConfig(nativeId, selectedModel) {
|
|
725
|
+
const models = await this.loadDesktopModels();
|
|
726
|
+
if (models.length === 0)
|
|
727
|
+
return;
|
|
728
|
+
let defaultModel = selectedModel;
|
|
729
|
+
if (!defaultModel) {
|
|
730
|
+
const desktop = resolveAntigravityDesktop(this.options.env ?? process.env);
|
|
731
|
+
if (desktop && existsSync(join(desktop.dataDir, "antigravity_state.pbtxt"))) {
|
|
732
|
+
try {
|
|
733
|
+
const stateText = readFileSync(join(desktop.dataDir, "antigravity_state.pbtxt"), "utf8");
|
|
734
|
+
const match = /last_selected_agent_model:\s*(\S+)/.exec(stateText);
|
|
735
|
+
if (match?.[1] && models.some((m) => m.value === match[1]))
|
|
736
|
+
defaultModel = match[1];
|
|
737
|
+
}
|
|
738
|
+
catch {
|
|
739
|
+
// ignore
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
const current = defaultModel && models.some((m) => m.value === defaultModel) ? defaultModel : models[0].value;
|
|
744
|
+
const options = [
|
|
745
|
+
{
|
|
746
|
+
id: "model",
|
|
747
|
+
name: "Model",
|
|
748
|
+
category: "model",
|
|
749
|
+
current,
|
|
750
|
+
values: models,
|
|
751
|
+
},
|
|
752
|
+
];
|
|
753
|
+
this.host?.update(this.id, nativeId, { sessionUpdate: "ls_config", options });
|
|
754
|
+
}
|
|
755
|
+
startWatchingDesktopTranscript(nativeId, transcriptPath, initialLine) {
|
|
756
|
+
const existing = this.desktopWatchers.get(nativeId);
|
|
757
|
+
if (existing)
|
|
758
|
+
clearInterval(existing.timer);
|
|
759
|
+
const lastSize = existsSync(transcriptPath) ? statSync(transcriptPath).size : 0;
|
|
760
|
+
const watcher = {
|
|
761
|
+
nextLine: initialLine,
|
|
762
|
+
lastSize,
|
|
763
|
+
running: false,
|
|
764
|
+
selectedModel: existing?.selectedModel,
|
|
765
|
+
timer: setInterval(() => {
|
|
766
|
+
if (!existsSync(transcriptPath))
|
|
767
|
+
return;
|
|
768
|
+
let stat;
|
|
769
|
+
try {
|
|
770
|
+
stat = statSync(transcriptPath);
|
|
771
|
+
}
|
|
772
|
+
catch {
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (stat.size <= watcher.lastSize)
|
|
776
|
+
return;
|
|
777
|
+
watcher.lastSize = stat.size;
|
|
778
|
+
const { items, nextLine } = parseAntigravityTranscript(transcriptPath, watcher.nextLine);
|
|
779
|
+
watcher.nextLine = nextLine;
|
|
780
|
+
for (const item of items) {
|
|
781
|
+
item.updates.forEach((update, idx) => {
|
|
782
|
+
this.host?.update(this.id, nativeId, update, idx === item.updates.length - 1 ? item.itemId : undefined);
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
}, 1500),
|
|
786
|
+
};
|
|
787
|
+
watcher.timer.unref?.();
|
|
788
|
+
this.desktopWatchers.set(nativeId, watcher);
|
|
789
|
+
}
|
|
790
|
+
async attach(nativeId, context) {
|
|
791
|
+
const transcriptPath = this.transcriptPathFor(nativeId);
|
|
792
|
+
if (this.desktopMode || (transcriptPath && existsSync(transcriptPath))) {
|
|
793
|
+
await this.ensureStarted();
|
|
794
|
+
const { items, nextLine } = transcriptPath ? parseAntigravityTranscript(transcriptPath, 0) : { items: [], nextLine: 0 };
|
|
795
|
+
if (transcriptPath) {
|
|
796
|
+
this.startWatchingDesktopTranscript(nativeId, transcriptPath, nextLine);
|
|
797
|
+
}
|
|
798
|
+
const running = this.isConversationRunningInDesktop(nativeId, "CASCADE_RUN_STATUS_RUNNING");
|
|
799
|
+
this.host?.update(this.id, nativeId, { sessionUpdate: "ls_status", state: running ? "running" : "idle", turnActive: running });
|
|
800
|
+
void this.emitDesktopConfig(nativeId, this.desktopWatchers.get(nativeId)?.selectedModel);
|
|
801
|
+
return items;
|
|
802
|
+
}
|
|
803
|
+
return super.attach(nativeId, context);
|
|
804
|
+
}
|
|
805
|
+
async detach(nativeId) {
|
|
806
|
+
const watcher = this.desktopWatchers.get(nativeId);
|
|
807
|
+
if (watcher) {
|
|
808
|
+
clearInterval(watcher.timer);
|
|
809
|
+
this.desktopWatchers.delete(nativeId);
|
|
810
|
+
}
|
|
811
|
+
await super.detach(nativeId);
|
|
812
|
+
}
|
|
813
|
+
async createSession(options) {
|
|
814
|
+
if (!this.desktopMode)
|
|
815
|
+
return super.createSession(options);
|
|
816
|
+
await this.ensureStarted();
|
|
817
|
+
const desktop = resolveAntigravityDesktop(this.options.env ?? process.env);
|
|
818
|
+
const conn = await findRunningLanguageServer(desktop);
|
|
819
|
+
if (!conn) {
|
|
820
|
+
throw RpcError.app("agent_unavailable", "请先在电脑上打开 Antigravity 桌面端应用");
|
|
821
|
+
}
|
|
822
|
+
const response = await callLanguageServer(conn, "StartCascade", {
|
|
823
|
+
workspaceUris: [pathToFileURL(options.cwd).href],
|
|
824
|
+
overrideWorkspaceUris: true,
|
|
825
|
+
...(options.model ? { requestedModel: options.model } : {}),
|
|
826
|
+
});
|
|
827
|
+
const nativeId = response.cascadeId;
|
|
828
|
+
if (!nativeId)
|
|
829
|
+
throw RpcError.app("agent_unavailable", "Antigravity 未返回新会话 ID");
|
|
830
|
+
const transcriptPath = this.transcriptPathFor(nativeId);
|
|
831
|
+
if (transcriptPath)
|
|
832
|
+
this.startWatchingDesktopTranscript(nativeId, transcriptPath, 0);
|
|
833
|
+
if (options.model) {
|
|
834
|
+
const watcher = this.desktopWatchers.get(nativeId);
|
|
835
|
+
if (watcher)
|
|
836
|
+
watcher.selectedModel = options.model;
|
|
837
|
+
}
|
|
838
|
+
void this.emitDesktopConfig(nativeId, options.model);
|
|
839
|
+
const now = Date.now();
|
|
840
|
+
return { nativeId, cwd: options.cwd, createdAt: now, updatedAt: now, state: "idle" };
|
|
841
|
+
}
|
|
842
|
+
async prompt(nativeId, content, clientMessageId, context) {
|
|
843
|
+
const transcriptPath = this.transcriptPathFor(nativeId);
|
|
844
|
+
if (!this.desktopMode && !(transcriptPath && existsSync(transcriptPath))) {
|
|
845
|
+
return super.prompt(nativeId, content, clientMessageId, context);
|
|
846
|
+
}
|
|
847
|
+
const desktop = resolveAntigravityDesktop(this.options.env ?? process.env);
|
|
848
|
+
const conn = await findRunningLanguageServer(desktop);
|
|
849
|
+
if (!conn) {
|
|
850
|
+
throw RpcError.app("agent_unavailable", "请先在电脑上打开 Antigravity 桌面端应用,再发送消息");
|
|
851
|
+
}
|
|
852
|
+
const text = content
|
|
853
|
+
.map((block) => (block.type === "text" ? block.text : ""))
|
|
854
|
+
.join("\n")
|
|
855
|
+
.trim();
|
|
856
|
+
if (!text)
|
|
857
|
+
throw RpcError.app("invalid_params", "nothing to send");
|
|
858
|
+
const fullText = context ? `${context}\n\n${text}` : text;
|
|
859
|
+
const selectedModel = this.desktopWatchers.get(nativeId)?.selectedModel;
|
|
860
|
+
this.host?.update(this.id, nativeId, { sessionUpdate: "ls_turn", state: "started" });
|
|
861
|
+
this.host?.update(this.id, nativeId, { sessionUpdate: "ls_status", state: "running", turnActive: true });
|
|
862
|
+
await callLanguageServer(conn, "SendUserCascadeMessage", {
|
|
863
|
+
cascadeId: nativeId,
|
|
864
|
+
items: [{ text: fullText }],
|
|
865
|
+
...(selectedModel ? { cascadeConfig: { plannerConfig: { requestedModel: { model: selectedModel } } } } : {}),
|
|
866
|
+
});
|
|
867
|
+
if (transcriptPath && !this.desktopWatchers.has(nativeId)) {
|
|
868
|
+
const { nextLine } = parseAntigravityTranscript(transcriptPath, 0);
|
|
869
|
+
this.startWatchingDesktopTranscript(nativeId, transcriptPath, nextLine);
|
|
870
|
+
}
|
|
871
|
+
return "started";
|
|
872
|
+
}
|
|
873
|
+
async cancel(nativeId) {
|
|
874
|
+
if (this.desktopMode || this.desktopWatchers.has(nativeId)) {
|
|
875
|
+
const desktop = resolveAntigravityDesktop(this.options.env ?? process.env);
|
|
876
|
+
const conn = await findRunningLanguageServer(desktop);
|
|
877
|
+
if (conn) {
|
|
878
|
+
await callLanguageServer(conn, "CancelCascadeInvocation", {
|
|
879
|
+
cascadeId: nativeId,
|
|
880
|
+
killBackgroundTasks: false,
|
|
881
|
+
}).catch(() => { });
|
|
882
|
+
}
|
|
883
|
+
this.host?.update(this.id, nativeId, { sessionUpdate: "ls_turn", state: "ended", stopReason: "cancelled" });
|
|
884
|
+
this.host?.update(this.id, nativeId, { sessionUpdate: "ls_status", state: "idle", turnActive: false });
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
await super.cancel(nativeId);
|
|
888
|
+
}
|
|
889
|
+
async setConfig(nativeId, optionId, value) {
|
|
890
|
+
if (this.desktopMode || this.desktopWatchers.has(nativeId)) {
|
|
891
|
+
if (optionId !== "model")
|
|
892
|
+
throw RpcError.app("not_supported", `Antigravity 暂不支持设置 ${optionId}`);
|
|
893
|
+
const watcher = this.desktopWatchers.get(nativeId);
|
|
894
|
+
if (watcher)
|
|
895
|
+
watcher.selectedModel = value;
|
|
896
|
+
await this.emitDesktopConfig(nativeId, value);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
await super.setConfig(nativeId, optionId, value);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
//# sourceMappingURL=antigravity.js.map
|