@estebanforge/pi-antigravity-bridge 1.3.3 → 1.4.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.
@@ -37,9 +37,12 @@ import {
37
37
  import { SessionStore } from "../src/sessions.js";
38
38
  import { ToolRoundTrips, WrapperReplay, createStreamSimple } from "../src/provider.js";
39
39
  import { AgyDriver } from "../src/driver.js";
40
- import { CONFIG_PATH, loadConfig, saveConfig, type AgyMode, type BridgeTools, type ThinkingTier } from "../src/config.js";
40
+ import { AcpDriver } from "../src/acp/driver.js";
41
+ import { ensureAcpReady, inspectAcpSetup } from "../src/acp/setup.js";
42
+ import type { TurnDriver } from "../src/driver-types.js";
43
+ import { CONFIG_PATH, loadConfig, saveConfig, type AgyMode, type BridgeTools, type Engine, type ThinkingTier } from "../src/config.js";
41
44
  import { registerAskAntigravityTool, toolModelsFromRaw } from "../src/ask-tool.js";
42
- import { startMcpServer, type McpServerHandle } from "../src/mcp-server.js";
45
+ import { startMcpServer, TOKEN_HEADER, type McpServerHandle } from "../src/mcp-server.js";
43
46
  import {
44
47
  ACTIVATE_SKILL_TOOL_NAME,
45
48
  activateSkillSchema,
@@ -79,14 +82,72 @@ export default async function (pi: ExtensionAPI): Promise<void> {
79
82
  const toolModels = toolModelsFromRaw(raw);
80
83
  const usingFallback = discovered.length === 0;
81
84
  const entries: AgyModelEntry[] = usingFallback ? FALLBACK_MODELS : discovered;
82
- const models = entries.map(toPiModel);
85
+ // Engine latched at load: /agy engine takes effect on the next pi start
86
+ // (documented). Everything below resolves from THIS value - per-call
87
+ // config reads would let a mid-session flip leave ToolRoundTrips,
88
+ // kickIdle, and reentry pointing at the other engine (round-7 finding).
89
+ const engine: Engine = loadConfig().engine;
90
+ // Engine switching requires a restart, so the catalog-time engine read is
91
+ // authoritative for input advertising: image attach rides only when turns
92
+ // will run on the ACP engine (the legacy CLI prompt is text-only).
93
+ const modelInput: Array<"text" | "image"> = engine === "acp" ? ["text", "image"] : ["text"];
94
+ const models = entries.map((e) => toPiModel(e, modelInput));
83
95
 
84
96
  const store = new SessionStore();
85
- // Persistent stream-json engine + the no-patch pi-tool round-trip store.
86
- // The MCP bridge parks calls here; the provider emits them as real pi
87
- // toolUse turns and completes them from the next call's toolResult.
88
- const driver = new AgyDriver();
89
- const roundTrips = new ToolRoundTrips(driver);
97
+ // MCP bridge handle, declared early: the ACP engine reads the bridge port
98
+ // at session/new / session/load time.
99
+ let mcpHandle: McpServerHandle | null = null;
100
+ // ACP self-heal runs once per process (session_start re-fires on /reload;
101
+ // a ready setup is two file stats, so re-running is harmless anyway).
102
+ let acpSelfHealRan = false;
103
+ // Two turn engines behind one contract (plan §9): stream-json (tested
104
+ // default) and the official ACP server (opt-in via config.engine, off by
105
+ // default). Neither spawns anything until its first turn.
106
+ // ACP log routing: only genuine failures reach stderr. Routine lifecycle
107
+ // (driver-created, spawn, session-new, ...) stays in the driver's
108
+ // #lifecycle ring buffer, visible via /agy doctor. An unfiltered sink fired
109
+ // console.error at extension load ("driver-created"), before any UI exists,
110
+ // and leaked raw driver lines into the terminal on every startup.
111
+ const acpFailures = new Set([
112
+ "start-failed", "spawn-error", "parse-error", "write-failed",
113
+ "mode-apply-failed", "timeout", "stall", "auth-required",
114
+ "session-load-failed-creating-fresh", "connection-exited", "cancel-failed",
115
+ "unsupported-server-request",
116
+ ]);
117
+ const legacyDriver = new AgyDriver();
118
+ const acpDriver = new AcpDriver({
119
+ // Resolved per connection: the setup flow can install the binary and
120
+ // update acp.bin mid-session; the next turn picks it up (no restart).
121
+ bin: () => loadConfig().acp.bin,
122
+ log: (msg, data) => {
123
+ if (!acpFailures.has(msg)) return;
124
+ console.error(`[antigravity-bridge acp] ${msg}${data !== undefined ? " " + JSON.stringify(data) : ""}`);
125
+ },
126
+ mcpServers: () => {
127
+ const handle = mcpHandle;
128
+ if (!handle) return [];
129
+ // The bridge 403s any request without the shared-secret header; the
130
+ // legacy engine carries it via mcp_config.json, ACP via headers[].
131
+ return [
132
+ {
133
+ name: "pi-bridge",
134
+ type: "http",
135
+ url: `http://127.0.0.1:${handle.port}/mcp`,
136
+ headers: [{ name: TOKEN_HEADER, value: handle.token }],
137
+ },
138
+ ];
139
+ },
140
+ });
141
+ // The active engine is resolved from the latched load-time value.
142
+ const activeDriver = (): TurnDriver => (engine === "acp" ? acpDriver : legacyDriver);
143
+ // The provider's stream-json slot gets the LEGACY driver explicitly - never
144
+ // activeDriver(), or a load-time acp engine would make deps.driver and
145
+ // deps.acpDriver the same object and break the engine identity check.
146
+ const driver = legacyDriver;
147
+ // The no-patch pi-tool round-trip store: the MCP bridge parks calls here;
148
+ // the provider emits them as real pi toolUse turns and completes them from
149
+ // the next call's toolResult.
150
+ const roundTrips = new ToolRoundTrips(activeDriver);
90
151
  const replay = new WrapperReplay();
91
152
  // Native re-exec only emits for builtins actually active in the session;
92
153
  // anything else (or an unknown name) falls back to the wrapper card.
@@ -99,9 +160,20 @@ export default async function (pi: ExtensionAPI): Promise<void> {
99
160
  }
100
161
  };
101
162
  // A settled turn cannot answer its parked calls; the driver never sees
102
- // ToolRoundTrips, so the provider bridges the two here.
103
- driver.onTurnEnd = () => roundTrips.failAll("antigravity turn ended with an unresolved pi tool call");
104
- const streamSimple = createStreamSimple({ entries, store, driver, roundTrips, replay, nativeActive });
163
+ // ToolRoundTrips, so the provider bridges the two here (both engines).
164
+ const onTurnEnd = () => roundTrips.failAll("antigravity turn ended with an unresolved pi tool call");
165
+ legacyDriver.onTurnEnd = onTurnEnd;
166
+ acpDriver.onTurnEnd = onTurnEnd;
167
+ const streamSimple = createStreamSimple({
168
+ entries,
169
+ store,
170
+ driver,
171
+ acpDriver,
172
+ roundTrips,
173
+ replay,
174
+ nativeActive,
175
+ engine,
176
+ });
105
177
 
106
178
  pi.registerProvider("antigravity", {
107
179
  name: "Antigravity (agy)",
@@ -122,7 +194,15 @@ export default async function (pi: ExtensionAPI): Promise<void> {
122
194
  streamSimple,
123
195
  });
124
196
 
125
- registerAgyCommand(pi, { entries, store, usingFallback, driver, getMcpPort: () => mcpHandle?.port ?? null });
197
+ registerAgyCommand(pi, {
198
+ entries,
199
+ store,
200
+ usingFallback,
201
+ driver,
202
+ acpDriver,
203
+ engine,
204
+ getMcpPort: () => mcpHandle?.port ?? null,
205
+ });
126
206
 
127
207
  // AskAntigravity tool: one-shot delegation to agy (ported from
128
208
  // pi-ask-antigravity). When both extensions are installed, the bridge wins
@@ -153,7 +233,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
153
233
  // Calls park in the provider's round-trip store and complete through pi's
154
234
  // normal toolUse loop (native cards, permissions, hooks) - no patch, no
155
235
  // privileged API. Started on session_start, torn down on session_shutdown.
156
- let mcpHandle: McpServerHandle | null = null;
157
236
  pi.on("session_start", async (_event, ctx) => {
158
237
  // Legacy cleanup: users who ran the old consent-gated patcher still
159
238
  // carry pi.invokeTool in their installed pi. Inert, but tell them once
@@ -172,29 +251,52 @@ export default async function (pi: ExtensionAPI): Promise<void> {
172
251
  } catch {
173
252
  /* detection is best-effort */
174
253
  }
175
- // Bridge lifecycle/error logger. Routes through ctx.ui.notify (an
176
- // ephemeral toast that fades) instead of stderr: pi's TUI captures stderr
177
- // and pins it above the input for the whole session, which left the
178
- // startup "bridge-config-written" / "listening" lines stuck on screen all
179
- // session. Headless modes (print/json, hasUI === false) have no toast, so
180
- // fall back to stderr there. Per-turn success events (list-tools /
181
- // call-tool) stay silent either way.
254
+ // ACP self-heal: engine=acp needs a server binary + auth. Silent when
255
+ // everything is ready; installs from the registry and bootstraps auth
256
+ // otherwise; manual instructions only on failure. Fire-and-forget: it
257
+ // must not delay session start (and nothing spawns until the first turn).
258
+ if (engine === "acp" && !acpSelfHealRan) {
259
+ acpSelfHealRan = true;
260
+ void ensureAcpReady({ configBin: loadConfig().acp.bin }).then((status) => {
261
+ if (status.ok) {
262
+ if (status.binarySource === "installed" || status.binarySource === "existing") {
263
+ saveConfig({ acp: { bin: status.bin, permissions: loadConfig().acp.permissions } });
264
+ }
265
+ if (status.needsLogin) {
266
+ const msg =
267
+ "ACP: one-time Google login pending. Your next antigravity message opens the browser; sign in with your Antigravity subscription account (the same login as the agy CLI). Tokens stay on your machine.";
268
+ if (ctx.hasUI) ctx.ui.notify(msg, "info");
269
+ else console.error(`[antigravity-bridge] ${msg}`);
270
+ }
271
+ return;
272
+ }
273
+ const msg = `ACP auto-setup failed (${status.error}).\n${status.manual}`;
274
+ if (ctx.hasUI) ctx.ui.notify(msg, "warning");
275
+ else console.error(`[antigravity-bridge] ${msg}`);
276
+ });
277
+ }
278
+ // Bridge failure logger. Routine lifecycle (listening,
279
+ // bridge-config-written/removed, closed) is normal startup/teardown
280
+ // traffic: toasting it every session, or pinning it via stderr in
281
+ // headless mode, was noise. Only genuine failures surface - as a
282
+ // warning toast (ctx.ui.notify, ephemeral) or stderr when headless.
283
+ // Per-turn success events (list-tools / call-tool) stay silent.
182
284
  const mcpLog = (s: string, d?: unknown) => {
183
- const surfaced = new Set([
184
- "listening", "capability-missing", "http-error", "closed",
185
- "bridge-config-written", "bridge-config-removed", "bridge-config-write-failed",
186
- "call-tool-fail", "transport-error", "handleRequest-error",
187
- "request-error", "request-handler-error", "unauthorized", "self-patch-error",
285
+ // Routine abort traffic: failAll fires on turn end / session shutdown
286
+ // and the bridge answers every parked call with an error. Not a fault.
287
+ if (s === "call-tool-fail") {
288
+ const detail = (d as { msg?: string } | undefined)?.msg ?? "";
289
+ if (detail.includes("unresolved pi tool call") || detail.includes("session shut down")) return;
290
+ }
291
+ const failures = new Set([
292
+ "http-error", "bridge-config-write-failed", "call-tool-fail",
293
+ "transport-error", "handleRequest-error", "request-error",
294
+ "request-handler-error", "unauthorized",
188
295
  ]);
189
- if (!surfaced.has(s)) return;
296
+ if (!failures.has(s)) return;
190
297
  const msg = `[antigravity-bridge mcp] ${s}${d !== undefined ? " " + JSON.stringify(d) : ""}`;
191
- if (ctx.hasUI) {
192
- const ok = s === "listening" || s === "bridge-config-written"
193
- || s === "bridge-config-removed" || s === "closed";
194
- ctx.ui.notify(msg, ok ? "info" : "warning");
195
- } else {
196
- console.error(msg);
197
- }
298
+ if (ctx.hasUI) ctx.ui.notify(msg, "warning");
299
+ else console.error(msg);
198
300
  };
199
301
  // Start the bridge unless the user turned it off. No patch gate, no
200
302
  // consent flow: calls route through pi's normal toolUse loop.
@@ -266,7 +368,8 @@ export default async function (pi: ExtensionAPI): Promise<void> {
266
368
  mcpHandle = null;
267
369
  await h?.close();
268
370
  roundTrips.failAll("antigravity session shut down");
269
- await driver.close("shutdown");
371
+ await legacyDriver.close("shutdown");
372
+ await acpDriver.close("shutdown");
270
373
  });
271
374
  }
272
375
 
@@ -276,7 +379,10 @@ interface AgyCommandCtx {
276
379
  entries: AgyModelEntry[];
277
380
  store: SessionStore;
278
381
  usingFallback: boolean;
279
- driver: AgyDriver;
382
+ driver: TurnDriver;
383
+ acpDriver: AcpDriver;
384
+ /** Engine latched at extension load (see the provider wiring note). */
385
+ engine: Engine;
280
386
  getMcpPort: () => number | null;
281
387
  }
282
388
 
@@ -293,6 +399,7 @@ function statusText(ctx: AgyCommandCtx): string {
293
399
  const perm = config.skipPermissions ? "auto-approved (DANGEROUS)" : "prompt (hangs in -p)";
294
400
  return [
295
401
  "Antigravity bridge",
402
+ ` engine: ${config.engine}${config.engine === "acp" ? " (official server, opt-in)" : ""}`,
296
403
  ` models: ${ctx.entries.length} ${source}`,
297
404
  ` mode: ${config.mode}`,
298
405
  ` permissions: ${perm}`,
@@ -304,7 +411,7 @@ function statusText(ctx: AgyCommandCtx): string {
304
411
  ` digest: ${config.digest ? "on" : "off"}`,
305
412
  ` system prompt: ${config.systemPrompt ? "on" : "off"}`,
306
413
  "",
307
- "Subcommands: /agy mode plan|accept-edits, /agy permissions on|off, /agy model flash|pro|gemini, /agy thinking low|medium|high, /agy digest on|off, /agy system-prompt on|off, /agy clear",
414
+ "Subcommands: /agy engine stream-json|acp, /agy mode plan|accept-edits, /agy permissions on|off, /agy model flash|pro|gemini, /agy thinking low|medium|high, /agy digest on|off, /agy system-prompt on|off, /agy acp-auth, /agy patch-cleanup, /agy clear",
308
415
  ].join("\n");
309
416
  }
310
417
 
@@ -312,7 +419,7 @@ function statusText(ctx: AgyCommandCtx): string {
312
419
  function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
313
420
  pi.registerCommand("agy", {
314
421
  description:
315
- "Antigravity provider: status, doctor, mode picker, clear sessions. Usage: /agy [status|doctor|mode [plan|accept-edits]|digest on|off|system-prompt on|off|patch-cleanup|clear]",
422
+ "Antigravity provider: status, doctor, engine picker, mode picker, clear sessions. Usage: /agy [status|doctor|engine stream-json|acp|mode [plan|accept-edits]|permissions on|off|digest on|off|system-prompt on|off|acp-auth|patch-cleanup|clear]",
316
423
  handler: async (args, cmdCtx: ExtensionCommandContext) => {
317
424
  const ui = cmdCtx.ui;
318
425
  const mode = cmdCtx.mode;
@@ -345,28 +452,112 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
345
452
  );
346
453
  return;
347
454
  }
455
+ if (sub === "engine") {
456
+ if (val === "acp" || val === "stream-json") {
457
+ if (val === "acp" && loadConfig().mode === "plan") {
458
+ ui?.notify("mode is plan; the ACP engine has no plan mode. /agy mode accept-edits first.", "warning");
459
+ return;
460
+ }
461
+ const next = saveConfig({ engine: val });
462
+ if (next.engine !== "acp") {
463
+ ui?.notify("engine set to stream-json. Takes effect on the next pi start (or /reload).", "info");
464
+ return;
465
+ }
466
+ // Self-service setup: install the server from the official
467
+ // registry and bootstrap auth now, so the restart just works.
468
+ // Manual instructions only when a step fails.
469
+ ui?.notify("engine set to acp. Preparing the server (binary + auth)…", "info");
470
+ const status = await ensureAcpReady({
471
+ configBin: loadConfig().acp.bin,
472
+ onProgress: (m) => ui?.notify(m, "info"),
473
+ });
474
+ if (!status.ok) {
475
+ ui?.notify(`ACP auto-setup failed (${status.error}).\n${status.manual}`, "warning");
476
+ return;
477
+ }
478
+ saveConfig({ acp: { bin: status.bin, permissions: loadConfig().acp.permissions } });
479
+ ui?.notify(
480
+ status.needsLogin
481
+ ? "ACP engine ready. Your first ACP message opens the Google login in your browser: sign in with the account of your Antigravity subscription (the same login as the Antigravity CLI, agy). One-time; tokens stay on your machine and this extension never sees them. Takes effect on the next pi start (or /reload)."
482
+ : `ACP engine ready (auth: ${status.auth}). Takes effect on the next pi start (or /reload).`,
483
+ "info",
484
+ );
485
+ } else {
486
+ ui?.notify(`current engine: ${loadConfig().engine}\nusage: /agy engine stream-json|acp`, "info");
487
+ }
488
+ return;
489
+ }
490
+ if (sub === "acp-auth") {
491
+ ui?.notify(
492
+ [
493
+ "ACP engine authentication (one-time; usually automatic -",
494
+ "/agy engine acp and session start set this up for you):",
495
+ "",
496
+ "The server is Google's official Antigravity ACP, installed from Google's",
497
+ "own registry. Logging in uses your Antigravity subscription: the same",
498
+ "Google account and plan as the Antigravity CLI (agy). It is no different",
499
+ "from logging into the CLI; the server just keeps its own token file on",
500
+ "your machine, like any Google tool. This extension never sees your",
501
+ "credentials.",
502
+ "",
503
+ "1. Server binary: auto-setup installs it. Manual: agy_acp_server.par from",
504
+ " the antigravity-acp registry; point acp.bin or AGY_ACP_BIN at it.",
505
+ '2. Default: put {"auth":{"type":"oauth-personal"}} in',
506
+ " ~/.gemini/antigravity-acp/settings.json, run one turn, and complete the",
507
+ " Google login that opens in your browser (headless: tunnel 127.0.0.1:<port>",
508
+ " over ssh, then open the URL on your machine).",
509
+ ' Headless alternative: GEMINI_API_KEY + {"auth":{"type":"gemini-api-key"}}',
510
+ " (metered paid API - not your Antigravity plan). The key is used only",
511
+ " when that type is selected; with the default oauth-personal in place,",
512
+ " an exported key is ignored.",
513
+ "3. Run one turn; /agy doctor shows the server version when auth is OK.",
514
+ ].join("\n"),
515
+ "info",
516
+ );
517
+ return;
518
+ }
348
519
  if (sub === "doctor") {
349
520
  const config = loadConfig();
350
- const snap = ctx.driver.snapshot();
521
+ const engine = ctx.engine;
522
+ const snap = (engine === "acp" ? ctx.acpDriver : ctx.driver).snapshot();
351
523
  const port = ctx.getMcpPort();
352
524
  const lines = [
353
525
  "Antigravity doctor (no tokens spent)",
526
+ ` engine: ${engine}`,
354
527
  ` bridge: ${config.bridgeTools}${port ? ` (port ${port})` : " (not running)"}`,
355
- ` driver: ${snap.state}${snap.pid ? ` pid=${snap.pid}` : ""}${snap.conversationId ? ` conv=${snap.conversationId.slice(0, 8)}` : ""}`,
528
+ ` driver: ${snap.state}${snap.pid ? ` pid=${snap.pid}` : ""}${snap.conversationId ? ` session=${snap.conversationId.slice(0, 8)}` : ""}`,
356
529
  ` driver stats: spawns=${snap.stats.spawns} turns=${snap.stats.turns} reused=${snap.stats.reused} recycles=${snap.stats.recycles}${snap.stats.lastRecycleReason ? ` (last: ${snap.stats.lastRecycleReason})` : ""}`,
357
530
  ` sessions: ${ctx.store.size} bound`,
358
531
  ` models: ${ctx.entries.length} ${ctx.usingFallback ? "FALLBACK (agy models failed)" : "discovered"}`,
359
532
  ` config: ${CONFIG_PATH}`,
360
533
  ];
534
+ if (snap.engine === "acp" && snap.acp) {
535
+ lines.push(
536
+ ` acp session: ${snap.acp.sessionId ?? "(none)"}`,
537
+ ` acp server: ${snap.acp.serverVersion ?? "unknown"}${snap.acp.agentTitle ? ` (${snap.acp.agentTitle})` : ""}`,
538
+ ` acp stats: prompts=${snap.acp.prompts} created=${snap.acp.sessionsCreated} loaded=${snap.acp.sessionsLoaded} kills=${snap.acp.kills} reconnects=${snap.acp.reconnects} cancel=${snap.acp.cancelSupported === null ? "unprobed" : snap.acp.cancelSupported ? "supported" : "unsupported (kill+reload)"}`,
539
+ );
540
+ }
361
541
  if (snap.lifecycle.length > 0) {
362
542
  lines.push(" lifecycle (last 5):");
363
543
  for (const entry of snap.lifecycle.slice(-5)) lines.push(` ${entry}`);
364
544
  }
545
+ if (engine === "acp") {
546
+ const setup = inspectAcpSetup({ configBin: config.acp.bin });
547
+ lines.push(
548
+ ` acp binary: ${setup.bin ?? "not found (auto-setup offers install)"}${setup.source ? ` (${setup.source})` : ""}`,
549
+ ` acp auth: ${setup.auth ?? "not configured (auto-setup bootstraps)"}`,
550
+ );
551
+ }
365
552
  ui?.notify(lines.join("\n"), "info");
366
553
  return;
367
554
  }
368
555
  if (sub === "mode") {
369
556
  if (val === "plan" || val === "accept-edits") {
557
+ if (val === "plan" && ctx.engine === "acp") {
558
+ ui?.notify("the ACP engine has no plan mode (RC01). /agy engine stream-json first, or /agy mode accept-edits.", "warning");
559
+ return;
560
+ }
370
561
  const next = saveConfig({ mode: val as AgyMode });
371
562
  ui?.notify(`mode set to ${next.mode}`, "info");
372
563
  } else {
@@ -531,6 +722,11 @@ async function openAgyPicker(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promis
531
722
  )
532
723
  return;
533
724
 
725
+ if (pending.mode === "plan" && ctx.engine === "acp") {
726
+ ui.notify("the ACP engine has no plan mode (RC01). Switch /agy engine stream-json first.", "warning");
727
+ return;
728
+ }
729
+
534
730
  try {
535
731
  const next = saveConfig(pending);
536
732
  const changed = [
package/package.json CHANGED
@@ -1,14 +1,20 @@
1
1
  {
2
2
  "name": "@estebanforge/pi-antigravity-bridge",
3
- "version": "1.3.3",
4
- "description": "Streaming Gemini provider for pi, built on the agy CLI. Registers antigravity/* models in pi's /model picker; drives agy through its stream-json protocol (persistent process, tool round-trips, live usage).",
3
+ "version": "1.4.1",
4
+ "description": "Gemini provider for Pi on the Antigravity ACP server (official Google ACP) or the stream-json agy CLI. antigravity/* models in Pi's /model picker, no-patch MCP bridge: agy runs Pi's tools. ToS safe to use.",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi-extension",
8
8
  "antigravity",
9
+ "antigravity-acp",
9
10
  "agy",
10
11
  "gemini",
11
12
  "google",
13
+ "google-acp",
14
+ "acp",
15
+ "agent-client-protocol",
16
+ "mcp",
17
+ "mcp-server",
12
18
  "provider",
13
19
  "streaming"
14
20
  ],
@@ -53,9 +59,10 @@
53
59
  }
54
60
  },
55
61
  "devDependencies": {
56
- "@earendil-works/pi-ai": "^0.84.3",
57
- "@earendil-works/pi-coding-agent": "^0.84.3",
58
- "@earendil-works/pi-tui": "^0.84.3",
62
+ "@earendil-works/pi-ai": "^0.85.0",
63
+ "@earendil-works/pi-coding-agent": "^0.85.0",
64
+ "@earendil-works/pi-server": "^0.85.0",
65
+ "@earendil-works/pi-tui": "^0.85.0",
59
66
  "@types/node": "^22.0.0",
60
67
  "tsx": "^4.19.0",
61
68
  "typebox": "^1.1.38",