@anhlp/alp-runtime 0.0.0-beta.1

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 (94) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +7 -0
  3. package/README.md +7 -0
  4. package/dist/acp-transport.d.ts +114 -0
  5. package/dist/acp-transport.js +478 -0
  6. package/dist/antigravity-transport.d.ts +80 -0
  7. package/dist/antigravity-transport.js +609 -0
  8. package/dist/board-domain.d.ts +56 -0
  9. package/dist/board-domain.js +157 -0
  10. package/dist/board.d.ts +21 -0
  11. package/dist/board.js +69 -0
  12. package/dist/catalog.d.ts +31 -0
  13. package/dist/catalog.js +38 -0
  14. package/dist/claude-transport.d.ts +157 -0
  15. package/dist/claude-transport.js +671 -0
  16. package/dist/contracts.d.ts +163 -0
  17. package/dist/contracts.js +5 -0
  18. package/dist/events.d.ts +1 -0
  19. package/dist/events.js +0 -0
  20. package/dist/github.d.ts +10 -0
  21. package/dist/github.js +39 -0
  22. package/dist/handoff.d.ts +36 -0
  23. package/dist/handoff.js +73 -0
  24. package/dist/index.d.ts +24 -0
  25. package/dist/index.js +64 -0
  26. package/dist/language.d.ts +41 -0
  27. package/dist/language.js +68 -0
  28. package/dist/live.d.ts +48 -0
  29. package/dist/live.js +45 -0
  30. package/dist/mailbox.d.ts +24 -0
  31. package/dist/mailbox.js +36 -0
  32. package/dist/mcp-tools.d.ts +115 -0
  33. package/dist/mcp-tools.js +71 -0
  34. package/dist/permission-path.d.ts +2 -0
  35. package/dist/permission-path.js +44 -0
  36. package/dist/process-info.d.ts +14 -0
  37. package/dist/process-info.js +32 -0
  38. package/dist/providers.d.ts +28 -0
  39. package/dist/providers.js +71 -0
  40. package/dist/recall.d.ts +42 -0
  41. package/dist/recall.js +60 -0
  42. package/dist/resolve.d.ts +121 -0
  43. package/dist/resolve.js +238 -0
  44. package/dist/runtime-context.d.ts +43 -0
  45. package/dist/runtime-context.js +61 -0
  46. package/dist/runtime.d.ts +4 -0
  47. package/dist/runtime.js +2979 -0
  48. package/dist/session-config.d.ts +1973 -0
  49. package/dist/session-config.js +205 -0
  50. package/dist/session-state.d.ts +140 -0
  51. package/dist/session-state.js +0 -0
  52. package/dist/tool-results.d.ts +12 -0
  53. package/dist/tool-results.js +25 -0
  54. package/dist/tools.d.ts +674 -0
  55. package/dist/tools.js +375 -0
  56. package/dist/transport.d.ts +47 -0
  57. package/dist/transport.js +169 -0
  58. package/dist/usage.d.ts +15 -0
  59. package/dist/usage.js +35 -0
  60. package/dist/workspace.d.ts +101 -0
  61. package/dist/workspace.js +259 -0
  62. package/dist/worktree-domain.d.ts +88 -0
  63. package/dist/worktree-domain.js +196 -0
  64. package/package.json +256 -0
  65. package/src/acp-transport.ts +473 -0
  66. package/src/antigravity-transport.ts +658 -0
  67. package/src/board-domain.ts +185 -0
  68. package/src/board.ts +80 -0
  69. package/src/catalog.ts +32 -0
  70. package/src/claude-transport.ts +771 -0
  71. package/src/contracts.ts +166 -0
  72. package/src/events.ts +1 -0
  73. package/src/github.ts +37 -0
  74. package/src/handoff.ts +86 -0
  75. package/src/index.ts +26 -0
  76. package/src/language.ts +102 -0
  77. package/src/live.ts +86 -0
  78. package/src/mailbox.ts +46 -0
  79. package/src/mcp-tools.ts +73 -0
  80. package/src/permission-path.ts +37 -0
  81. package/src/process-info.ts +40 -0
  82. package/src/providers.ts +84 -0
  83. package/src/recall.ts +85 -0
  84. package/src/resolve.ts +230 -0
  85. package/src/runtime-context.ts +49 -0
  86. package/src/runtime.ts +3579 -0
  87. package/src/session-config.ts +301 -0
  88. package/src/session-state.ts +141 -0
  89. package/src/tool-results.ts +26 -0
  90. package/src/tools.ts +379 -0
  91. package/src/transport.ts +116 -0
  92. package/src/usage.ts +42 -0
  93. package/src/workspace.ts +318 -0
  94. package/src/worktree-domain.ts +216 -0
@@ -0,0 +1,609 @@
1
+ // packages/runtime/src/antigravity-transport.ts
2
+ import { spawn, execFile } from "node:child_process";
3
+ import { randomBytes, randomUUID } from "node:crypto";
4
+ import { rmSync } from "node:fs";
5
+ import { mkdir, open, readFile, rename, writeFile, unlink } from "node:fs/promises";
6
+ import net from "node:net";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+ import { createInterface } from "node:readline";
10
+ import { openMcpTools } from "./mcp-tools.js";
11
+ var BRIDGE = `// ALP Antigravity bridge v1
12
+
13
+ const net = require('node:net');
14
+ const readline = require('node:readline');
15
+ const socket = process.env.ALP_BRIDGE_SOCKET ? net.connect(process.env.ALP_BRIDGE_SOCKET) : null;
16
+ const waiting = new Map();
17
+ let sequence = 0;
18
+ let buffer = '';
19
+ socket?.setEncoding('utf8');
20
+ socket?.write(JSON.stringify({ token: process.env.ALP_BRIDGE_TOKEN }) + '\\n');
21
+ socket?.on('data', data => {
22
+ buffer += data;
23
+ for (let index; (index = buffer.indexOf('\\n')) >= 0;) {
24
+ const line = buffer.slice(0, index); buffer = buffer.slice(index + 1);
25
+ try { const message = JSON.parse(line); const waiter = waiting.get(message.id); if (waiter) { waiting.delete(message.id); waiter(message); } } catch {}
26
+ }
27
+ });
28
+ socket?.on('close', () => process.exit(0));
29
+ socket?.on('error', () => process.exit(1));
30
+ const ask = message => !socket ? Promise.resolve({ tools: [], result: { content: [{ type: 'text', text: 'No ALP session' }], isError: true } }) : new Promise(resolve => { const id = ++sequence; waiting.set(id, resolve); socket.write(JSON.stringify({ ...message, id }) + '\\n'); });
31
+ const send = value => process.stdout.write(JSON.stringify({ jsonrpc: '2.0', ...value }) + '\\n');
32
+ readline.createInterface({ input: process.stdin }).on('line', async line => {
33
+ let message; try { message = JSON.parse(line); } catch { return; }
34
+ if (message.id === undefined || message.id === null) return;
35
+ try {
36
+ if (message.method === 'initialize') return send({ id: message.id, result: { protocolVersion: (message.params && message.params.protocolVersion) || '2025-06-18', capabilities: { tools: {} }, serverInfo: { name: 'alp', version: '1' } } });
37
+ if (message.method === 'ping') return send({ id: message.id, result: {} });
38
+ if (message.method === 'tools/list') { const reply = await ask({ op: 'list' }); return send({ id: message.id, result: { tools: reply.tools } }); }
39
+ if (message.method === 'tools/call') { const reply = await ask({ op: 'call', name: message.params.name, arguments: message.params.arguments || {} }); return send({ id: message.id, result: reply.result }); }
40
+ send({ id: message.id, error: { code: -32601, message: 'Method not found' } });
41
+ } catch (error) { send({ id: message.id, error: { code: -32603, message: String((error && error.message) || error) } }); }
42
+ });
43
+ process.stdin.on('end', () => process.exit(0));
44
+ `;
45
+ function encodePrompt(text) {
46
+ return `${JSON.stringify({ event: "user", message: { content: text } })}
47
+ `;
48
+ }
49
+ var AntigravityTransport = class {
50
+ constructor(command = "agy", cwd = process.cwd(), env = process.env, provider) {
51
+ this.command = command;
52
+ this.cwd = cwd;
53
+ this.env = env;
54
+ this.provider = provider;
55
+ }
56
+ child;
57
+ lines;
58
+ exit = Promise.resolve();
59
+ listeners = /* @__PURE__ */ new Set();
60
+ failures = /* @__PURE__ */ new Set();
61
+ requestHandler;
62
+ closed = false;
63
+ conversationId = "";
64
+ config;
65
+ activeTurn;
66
+ messageText = /* @__PURE__ */ new Map();
67
+ messages = 0;
68
+ instructionsSent = false;
69
+ tools = [];
70
+ bridge;
71
+ readyPromise;
72
+ resolveReady;
73
+ rejectReady;
74
+ external;
75
+ externalStarting;
76
+ startupAbort = new AbortController();
77
+ async initialize() {
78
+ if (/\.(cmd|bat|ps1)$/i.test(this.command)) {
79
+ throw new Error("Antigravity command must be a native executable, not a shell launcher");
80
+ }
81
+ }
82
+ onNotification(listener) {
83
+ this.listeners.add(listener);
84
+ return () => this.listeners.delete(listener);
85
+ }
86
+ onFailure(listener) {
87
+ this.failures.add(listener);
88
+ return () => this.failures.delete(listener);
89
+ }
90
+ onRequest(handler) {
91
+ this.requestHandler = handler;
92
+ }
93
+ async request(method, params) {
94
+ if (this.closed) throw new Error("Runtime is closed");
95
+ if (method === "thread/start" || method === "thread/resume") {
96
+ if (params.sandbox !== "full-access" && params.sandbox !== "danger-full-access") throw new Error("Antigravity currently supports only full-access");
97
+ this.config = params;
98
+ this.tools = params.dynamicTools ?? [];
99
+ try {
100
+ if (Object.keys(params.mcpServers ?? {}).length) {
101
+ this.externalStarting = openMcpTools(params.mcpServers, params.cwd || this.cwd, { ...this.env, ...this.provider?.env, ...params.env }, this.startupAbort.signal);
102
+ const external = await this.externalStarting;
103
+ if (this.closed) {
104
+ await external.close();
105
+ throw new Error("Runtime is closed");
106
+ }
107
+ this.external = external;
108
+ }
109
+ if (this.tools.length > 0 || this.external?.tools.length) await this.openBridge();
110
+ if (this.closed) throw new Error("Runtime is closed");
111
+ await this.startProcess(params);
112
+ } catch (error) {
113
+ await this.close();
114
+ throw error;
115
+ }
116
+ return {
117
+ thread: { id: this.conversationId, turns: [] },
118
+ cwd: params.cwd,
119
+ model: params.model,
120
+ reasoningEffort: params.thinking
121
+ };
122
+ }
123
+ if (method === "turn/start") {
124
+ if (!this.conversationId) throw new Error("Antigravity session is not open");
125
+ if (!this.child) await this.startProcess({ ...this.config, threadId: this.conversationId });
126
+ if (this.activeTurn) throw new Error("Antigravity already has an active turn");
127
+ const turnId = randomUUID();
128
+ this.activeTurn = turnId;
129
+ this.messageText.clear();
130
+ const parts = (params.input ?? []).filter((part) => typeof part?.text === "string").map((part) => part.text);
131
+ let promptText = parts.join("\n\n");
132
+ if (!this.instructionsSent && this.config?.developerInstructions) {
133
+ promptText = `Instructions from ALP, which runs you. Follow them for this whole session.
134
+
135
+ ${this.config.developerInstructions}
136
+
137
+ ${promptText}`;
138
+ this.instructionsSent = true;
139
+ }
140
+ setImmediate(() => {
141
+ try {
142
+ this.child?.stdin.write(encodePrompt(promptText));
143
+ } catch (error) {
144
+ this.endTurn(turnId, "failed", error instanceof Error ? error.message : String(error));
145
+ }
146
+ });
147
+ return { turn: { id: turnId } };
148
+ }
149
+ if (method === "turn/steer") {
150
+ throw new Error("Antigravity takes no messages during a turn; ALP delivers them when it ends");
151
+ }
152
+ if (method === "turn/interrupt") {
153
+ if (this.child && this.activeTurn) {
154
+ const turnId = this.activeTurn;
155
+ const child = this.child;
156
+ this.child = void 0;
157
+ this.lines?.close();
158
+ const kill = (signal) => {
159
+ if (process.platform !== "win32" && child.pid) {
160
+ try {
161
+ process.kill(-child.pid, signal);
162
+ } catch {
163
+ child.kill(signal);
164
+ }
165
+ } else child.kill(signal);
166
+ };
167
+ kill("SIGINT");
168
+ const timer = setTimeout(() => kill("SIGKILL"), 1500);
169
+ try {
170
+ await this.exit;
171
+ } finally {
172
+ clearTimeout(timer);
173
+ }
174
+ this.endTurn(turnId, "interrupted");
175
+ }
176
+ return {};
177
+ }
178
+ if (method === "session/configure" || method === "thread/delete") {
179
+ if (method === "session/configure" && params.sandbox && params.sandbox !== "full-access") throw new Error("Antigravity currently supports only full-access");
180
+ return {};
181
+ }
182
+ throw new Error(`Antigravity does not support '${method}'`);
183
+ }
184
+ startProcess(params) {
185
+ if (this.closed) return Promise.reject(new Error("Runtime is closed"));
186
+ this.readyPromise = new Promise((resolve, reject) => {
187
+ this.resolveReady = resolve;
188
+ this.rejectReady = reject;
189
+ });
190
+ const args = [
191
+ ...this.provider?.args ?? [],
192
+ "--input-format",
193
+ "stream-json",
194
+ "--output-format",
195
+ "stream-json",
196
+ "--add-dir",
197
+ params.cwd || this.cwd,
198
+ "--disable-slash-commands",
199
+ "--dangerously-skip-permissions"
200
+ ];
201
+ if (params.model) {
202
+ args.push("--model", params.model);
203
+ }
204
+ if (params.threadId) {
205
+ args.push("--conversation", params.threadId);
206
+ }
207
+ const env = {
208
+ ...this.env,
209
+ ...this.provider?.env ?? {},
210
+ ...params.env ?? {},
211
+ // Session credentials are inherited by this agy's MCP child only, never persisted.
212
+ ALP_BRIDGE_SOCKET: this.bridge?.socket ?? "",
213
+ ALP_BRIDGE_TOKEN: this.bridge?.token ?? ""
214
+ };
215
+ const child = spawn(this.command, args, {
216
+ cwd: params.cwd || this.cwd,
217
+ env,
218
+ windowsHide: true,
219
+ detached: process.platform !== "win32",
220
+ stdio: ["pipe", "pipe", "pipe"]
221
+ });
222
+ this.child = child;
223
+ this.exit = new Promise((resolve) => {
224
+ child.once("close", () => resolve());
225
+ child.once("error", () => resolve());
226
+ });
227
+ let stderr = "";
228
+ child.stderr.on("data", (chunk) => {
229
+ stderr = (stderr + chunk.toString()).slice(-8192);
230
+ });
231
+ child.stdin.on("error", (error) => this.fail(error));
232
+ child.on("error", (error) => {
233
+ const err = new Error(`Antigravity did not start: ${error.message}`);
234
+ if (this.rejectReady) this.rejectReady(err);
235
+ this.fail(err);
236
+ });
237
+ child.on("close", (code) => {
238
+ const msg = stderr.trim() || `Antigravity exited (${code})`;
239
+ if (!this.closed && this.child === child) {
240
+ const error = new Error(msg);
241
+ if (this.rejectReady) this.rejectReady(error);
242
+ if (this.activeTurn) {
243
+ this.endTurn(this.activeTurn, "failed", msg);
244
+ }
245
+ this.fail(error);
246
+ }
247
+ });
248
+ const lines = createInterface({ input: child.stdout, terminal: false });
249
+ this.lines = lines;
250
+ lines.on("line", (line) => this.receiveLine(line));
251
+ const startupTimeout = setTimeout(() => {
252
+ if (this.rejectReady) {
253
+ const error = new Error("Antigravity did not initialize within 30 seconds");
254
+ this.rejectReady(error);
255
+ this.fail(error);
256
+ }
257
+ }, 3e4);
258
+ return this.readyPromise.finally(() => clearTimeout(startupTimeout));
259
+ }
260
+ receiveLine(line) {
261
+ if (!line.trim() || this.closed) return;
262
+ if (line.length > 10 * 1024 * 1024) {
263
+ this.fail(new Error("Antigravity frame exceeds 10MB limit"));
264
+ return;
265
+ }
266
+ let frame;
267
+ try {
268
+ frame = JSON.parse(line);
269
+ } catch {
270
+ return;
271
+ }
272
+ if (frame.event === "init") {
273
+ this.conversationId = frame.conversation_id || frame.init?.conversation_id || randomUUID();
274
+ if (this.resolveReady) {
275
+ this.resolveReady();
276
+ this.resolveReady = void 0;
277
+ this.rejectReady = void 0;
278
+ }
279
+ return;
280
+ }
281
+ if (frame.event === "step_update") {
282
+ this.handleStepUpdate(frame.step_update);
283
+ return;
284
+ }
285
+ if (frame.event === "result") {
286
+ this.handleResult(frame.result);
287
+ return;
288
+ }
289
+ }
290
+ handleStepUpdate(step) {
291
+ if (!step || !this.activeTurn) return;
292
+ if (step.step_type === "agent_response") {
293
+ const itemId = `agy-msg-${this.activeTurn}-${step.step_index ?? this.messages}`;
294
+ const delta = typeof step.text_delta === "string" ? step.text_delta : "";
295
+ if (delta) {
296
+ this.messageText.set(itemId, (this.messageText.get(itemId) ?? "") + delta);
297
+ this.emit("item/agentMessage/delta", {
298
+ threadId: this.conversationId,
299
+ itemId,
300
+ delta
301
+ });
302
+ }
303
+ if (step.state === "DONE" && this.messageText.has(itemId)) {
304
+ this.emit("item/completed", {
305
+ threadId: this.conversationId,
306
+ item: { type: "agentMessage", id: itemId, text: this.messageText.get(itemId) }
307
+ });
308
+ }
309
+ if (step.state === "DONE") {
310
+ this.messageText.delete(itemId);
311
+ this.messages++;
312
+ }
313
+ if (step.usage && typeof step.usage === "object") {
314
+ const inTokens = step.usage.input_tokens || 0;
315
+ const outTokens = step.usage.output_tokens || 0;
316
+ const cachedTokens = step.usage.cache_read_tokens || 0;
317
+ const total = inTokens + outTokens;
318
+ const last = { totalTokens: total, inputTokens: inTokens, outputTokens: outTokens };
319
+ this.emit("thread/tokenUsage/updated", {
320
+ threadId: this.conversationId,
321
+ turnId: this.activeTurn,
322
+ tokenUsage: {
323
+ last,
324
+ total: last,
325
+ ...cachedTokens ? { cachedInputTokens: cachedTokens } : {}
326
+ }
327
+ });
328
+ }
329
+ return;
330
+ }
331
+ if (step.step_type === "tool") {
332
+ const toolName = step.tool_name || "tool";
333
+ const callId = `agy-tool-${this.activeTurn}-${step.step_index ?? ++this.messages}`;
334
+ if (step.state === "ACTIVE") {
335
+ this.emit("item/started", {
336
+ threadId: this.conversationId,
337
+ item: {
338
+ type: "commandExecution",
339
+ id: callId,
340
+ command: toolName,
341
+ cwd: this.cwd,
342
+ status: "inProgress",
343
+ aggregatedOutput: ""
344
+ }
345
+ });
346
+ } else if (step.state === "DONE" || step.state === "ERROR") {
347
+ const output = typeof step.tool_info?.output === "string" ? step.tool_info.output : step.tool_info?.error?.message || "";
348
+ this.emit("item/completed", {
349
+ threadId: this.conversationId,
350
+ item: {
351
+ type: "commandExecution",
352
+ id: callId,
353
+ command: toolName,
354
+ cwd: this.cwd,
355
+ status: step.state === "DONE" ? "completed" : "failed",
356
+ exitCode: step.state === "DONE" ? 0 : 1,
357
+ aggregatedOutput: output
358
+ }
359
+ });
360
+ }
361
+ }
362
+ }
363
+ handleResult(result) {
364
+ if (!this.activeTurn) return;
365
+ const turnId = this.activeTurn;
366
+ if (!result || typeof result !== "object") {
367
+ this.endTurn(turnId, "failed", "Invalid Antigravity result");
368
+ return;
369
+ }
370
+ if (result.status === "ERROR") {
371
+ const isInterrupted = result.error === "interrupted";
372
+ this.endTurn(turnId, isInterrupted ? "interrupted" : "failed", result.error);
373
+ } else {
374
+ this.endTurn(turnId, "completed");
375
+ }
376
+ }
377
+ endTurn(turnId, status, error) {
378
+ if (this.activeTurn !== turnId) return;
379
+ this.activeTurn = void 0;
380
+ setImmediate(() => {
381
+ this.emit("turn/completed", {
382
+ threadId: this.conversationId,
383
+ turn: {
384
+ id: turnId,
385
+ status,
386
+ ...status === "failed" ? { error: { message: error || "Antigravity turn failed" } } : {}
387
+ }
388
+ });
389
+ });
390
+ }
391
+ async openBridge() {
392
+ if (this.bridge) return;
393
+ const token = randomBytes(16).toString("hex");
394
+ const name = `alp-agy-${randomBytes(6).toString("hex")}`;
395
+ const socket = process.platform === "win32" ? `\\\\.\\pipe\\${name}` : path.join(os.tmpdir(), `${name}.sock`);
396
+ const connections = /* @__PURE__ */ new Set();
397
+ const server = net.createServer((connection) => {
398
+ connections.add(connection);
399
+ connection.once("close", () => connections.delete(connection));
400
+ this.serveBridge(connection, token);
401
+ });
402
+ await new Promise((resolve, reject) => {
403
+ server.once("error", reject);
404
+ server.listen(socket, () => resolve());
405
+ });
406
+ this.bridge = { server, socket, token, connections };
407
+ await this.registerMcpServer();
408
+ }
409
+ /** One stable launcher; each child inherits its own socket/token from agy.
410
+ * No session endpoint or credential is written to the global configuration.
411
+ * Leave the launcher installed: removing it would break concurrent sessions.
412
+ */
413
+ async registerMcpServer() {
414
+ const configFile = this.env.ALP_AGY_MCP_CONFIG ?? path.join(os.homedir(), ".gemini", "config", "mcp_config.json");
415
+ await mkdir(path.dirname(configFile), { recursive: true });
416
+ const lockFile = `${configFile}.alp-lock`;
417
+ let lock;
418
+ for (let attempt = 0; attempt < 100; attempt++) {
419
+ try {
420
+ lock = await open(lockFile, "wx", 384);
421
+ break;
422
+ } catch (error) {
423
+ if (error.code !== "EEXIST") throw error;
424
+ await new Promise((resolve) => setTimeout(resolve, 25));
425
+ }
426
+ }
427
+ if (!lock) throw new Error(`Antigravity MCP configuration is locked: ${lockFile}`);
428
+ const temporary = `${configFile}.${randomUUID()}.tmp`;
429
+ try {
430
+ let text = "{}";
431
+ try {
432
+ text = await readFile(configFile, "utf8");
433
+ } catch (error) {
434
+ if (error.code !== "ENOENT") throw error;
435
+ }
436
+ const config = JSON.parse(text);
437
+ if (!config || typeof config !== "object" || Array.isArray(config) || config.mcpServers !== void 0 && (!config.mcpServers || typeof config.mcpServers !== "object" || Array.isArray(config.mcpServers))) {
438
+ throw new Error("Invalid Antigravity MCP configuration; existing file was preserved");
439
+ }
440
+ config.mcpServers ??= {};
441
+ const existing = config.mcpServers.alp;
442
+ if (existing && !existing.args?.some((arg) => typeof arg === "string" && arg.startsWith("// ALP Antigravity bridge v1"))) {
443
+ throw new Error("Antigravity already has an MCP server named alp; rename it before using ALP");
444
+ }
445
+ const launcher = { command: process.execPath, args: ["-e", BRIDGE], ...process.versions.electron ? { env: { ELECTRON_RUN_AS_NODE: "1" } } : {} };
446
+ if (JSON.stringify(existing) === JSON.stringify(launcher)) return;
447
+ config.mcpServers.alp = launcher;
448
+ await writeFile(temporary, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
449
+ await rename(temporary, configFile);
450
+ } finally {
451
+ await unlink(temporary).catch(() => {
452
+ });
453
+ await lock.close();
454
+ await unlink(lockFile);
455
+ }
456
+ }
457
+ serveBridge(connection, token) {
458
+ let trusted = false;
459
+ connection.setEncoding("utf8");
460
+ connection.on("error", () => {
461
+ });
462
+ const reply = (value) => {
463
+ if (!connection.destroyed) connection.write(`${JSON.stringify(value)}
464
+ `);
465
+ };
466
+ createInterface({ input: connection }).on("line", async (line) => {
467
+ let message;
468
+ try {
469
+ message = JSON.parse(line);
470
+ } catch {
471
+ connection.destroy();
472
+ return;
473
+ }
474
+ if (!trusted) {
475
+ if (message.token !== token) {
476
+ connection.destroy();
477
+ return;
478
+ }
479
+ trusted = true;
480
+ return;
481
+ }
482
+ if (message.op === "list") {
483
+ reply({
484
+ id: message.id,
485
+ tools: [...this.tools.map((tool) => ({
486
+ name: tool.name,
487
+ description: tool.description,
488
+ inputSchema: tool.inputSchema ?? { type: "object" }
489
+ })), ...this.external?.tools ?? []]
490
+ });
491
+ return;
492
+ }
493
+ if (message.op === "call") {
494
+ reply({
495
+ id: message.id,
496
+ result: await this.toolCall(String(message.name), message.arguments ?? {})
497
+ });
498
+ }
499
+ });
500
+ }
501
+ async toolCall(tool, args) {
502
+ if (this.activeTurn && this.external?.has(tool)) {
503
+ const callId2 = `agy-mcp-tool-${randomUUID()}`;
504
+ const item2 = { type: "dynamicToolCall", id: callId2, callId: callId2, tool, arguments: args };
505
+ this.emit("item/started", { threadId: this.conversationId, item: { ...item2, status: "inProgress" } });
506
+ const result2 = await this.external.call(tool, args).catch(() => ({ content: [{ type: "text", text: `External MCP tool '${tool}' failed` }], isError: true }));
507
+ const ok2 = result2.isError !== true;
508
+ this.emit("item/completed", {
509
+ threadId: this.conversationId,
510
+ item: { ...item2, status: ok2 ? "completed" : "failed", success: ok2, contentItems: (Array.isArray(result2.content) ? result2.content : []).map((part) => ({ type: "inputText", text: part.type === "text" ? part.text : JSON.stringify(part) })) }
511
+ });
512
+ return result2;
513
+ }
514
+ if (!this.requestHandler || !this.activeTurn || !this.tools.some((t) => t.name === tool)) {
515
+ return {
516
+ content: [{ type: "text", text: this.activeTurn ? `Unknown ALP tool '${tool}'` : "ALP tools are unavailable between turns" }],
517
+ isError: true
518
+ };
519
+ }
520
+ const callId = `agy-alp-tool-${randomUUID()}`;
521
+ const item = { type: "dynamicToolCall", id: callId, callId, tool, arguments: args };
522
+ this.emit("item/started", { threadId: this.conversationId, item: { ...item, status: "inProgress" } });
523
+ const result = await this.requestHandler("item/tool/call", {
524
+ threadId: this.conversationId,
525
+ turnId: this.activeTurn,
526
+ callId,
527
+ namespace: null,
528
+ tool,
529
+ arguments: args
530
+ }).catch((error) => ({
531
+ success: false,
532
+ contentItems: [{ type: "inputText", text: error instanceof Error ? error.message : String(error) }]
533
+ }));
534
+ const ok = result?.success !== false;
535
+ this.emit("item/completed", {
536
+ threadId: this.conversationId,
537
+ item: { ...item, status: ok ? "completed" : "failed", success: ok, contentItems: result?.contentItems ?? [] }
538
+ });
539
+ return {
540
+ content: (result?.contentItems ?? []).map((part) => ({ type: "text", text: part.text ?? JSON.stringify(part) })),
541
+ isError: !ok
542
+ };
543
+ }
544
+ emit(method, params) {
545
+ for (const listener of this.listeners) listener(method, params);
546
+ }
547
+ fail(error) {
548
+ if (this.closed) return;
549
+ for (const listener of this.failures) listener(error);
550
+ void this.close();
551
+ }
552
+ async close() {
553
+ if (this.closed) return this.exit;
554
+ this.closed = true;
555
+ this.listeners.clear();
556
+ this.failures.clear();
557
+ this.startupAbort.abort();
558
+ await this.externalStarting?.then((external) => external.close(), () => {
559
+ });
560
+ await this.external?.close();
561
+ if (this.bridge) {
562
+ this.bridge.server.close();
563
+ for (const connection of this.bridge.connections) connection.destroy();
564
+ if (process.platform !== "win32") {
565
+ try {
566
+ rmSync(this.bridge.socket, { force: true });
567
+ } catch {
568
+ }
569
+ }
570
+ }
571
+ if (this.lines) {
572
+ this.lines.close();
573
+ }
574
+ const child = this.child;
575
+ if (!child) return;
576
+ try {
577
+ child.stdin.end();
578
+ } catch {
579
+ }
580
+ const timer = setTimeout(() => {
581
+ if (process.platform === "win32" && child.pid) {
582
+ execFile("taskkill.exe", ["/PID", String(child.pid), "/T", "/F"], { windowsHide: true }, () => {
583
+ });
584
+ } else if (child.pid) {
585
+ try {
586
+ process.kill(-child.pid, "SIGKILL");
587
+ } catch {
588
+ child.kill("SIGKILL");
589
+ }
590
+ }
591
+ }, 1500);
592
+ let deadline;
593
+ try {
594
+ await Promise.race([
595
+ this.exit,
596
+ new Promise((resolve) => {
597
+ deadline = setTimeout(resolve, 4e3);
598
+ })
599
+ ]);
600
+ } finally {
601
+ clearTimeout(timer);
602
+ clearTimeout(deadline);
603
+ }
604
+ }
605
+ };
606
+ export {
607
+ AntigravityTransport,
608
+ encodePrompt
609
+ };
@@ -0,0 +1,56 @@
1
+ import { type Pin } from './board.js';
2
+ import type { RuntimeOptions } from './contracts.js';
3
+ import type { Session } from './session-state.js';
4
+ import type { AlpEvent } from './events.js';
5
+ import type { MailEvent } from './mailbox.js';
6
+ type BoardContext = {
7
+ options: RuntimeOptions;
8
+ sessions: Map<string, Session>;
9
+ boards: Map<string, Promise<Pin[]>>;
10
+ loadedBoards: Map<string, Pin[]>;
11
+ rootOf(sessionId: string): string;
12
+ lineage(sessionId: string): string[];
13
+ runLog(rootId: string, entry: Record<string, unknown>): void;
14
+ emit(sessionId: string, event: AlpEvent): void;
15
+ post(sessionId: string, event: Omit<MailEvent, 'id'>): MailEvent;
16
+ steerMail(sessionId: string, session: Session): Promise<void>;
17
+ };
18
+ /** Shared project boards retain their original write ordering and session ownership. */
19
+ export declare function createBoardDomain({ options, sessions, boards, loadedBoards, rootOf, lineage, runLog, emit, post, steerMail }: BoardContext): {
20
+ boardOf: (project: string) => Promise<Pin[]>;
21
+ claimConflicts: (sessionId: string, pins: Pin[], paths: string[]) => {
22
+ task?: string | undefined;
23
+ pinId: string;
24
+ agent: string;
25
+ sessionId: string;
26
+ paths: string[];
27
+ body: string;
28
+ at: string;
29
+ }[];
30
+ taskOf: (sessionId: string) => string | undefined;
31
+ addPin: (sessionId: string, session: Session, pins: Pin[], fields: Pick<Pin, "kind" | "body" | "paths" | "task">) => Pin;
32
+ pinTool: (sessionId: string, session: Session, args: unknown) => Promise<{
33
+ success: boolean;
34
+ contentItems: {
35
+ type: string;
36
+ text: string;
37
+ }[];
38
+ }>;
39
+ boardTool: (session: Session, args: unknown) => Promise<{
40
+ success: boolean;
41
+ contentItems: {
42
+ type: string;
43
+ text: string;
44
+ }[];
45
+ }>;
46
+ unpinTool: (sessionId: string, session: Session, args: unknown) => Promise<{
47
+ success: boolean;
48
+ contentItems: {
49
+ type: string;
50
+ text: string;
51
+ }[];
52
+ }>;
53
+ releaseClaims: (sessionId: string, project: string) => void;
54
+ flush: () => Promise<void>;
55
+ };
56
+ export {};