diff --git a/AGENTS.md b/AGENTS.md
index 10490b76..29fca9f9 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -76,6 +76,9 @@ with no silent fallback.
(native in-process plugin injected via `OPENCODE_CONFIG_CONTENT`).
- **`@cotal-ai/connector-hermes`** (`extensions/connector-hermes`): the Hermes (Nous Research)
adapter; includes a Python sidecar.
+- **`@cotal-ai/pi`** (`extensions/pi`): native-embed adapter — embeds `MeshAgent` in the pi
+ agent's own process and drives its loop straight off the inbox, with true mid-turn `steer()`.
+ See [docs/agent-frameworks.md](docs/agent-frameworks.md).
- **`@cotal-ai/cmux`** (`extensions/cmux`): the cmux integration: a driver over the cmux CLI
plus a self-registering `cmux` Runtime and `TerminalLayout` provider.
- **`@cotal-ai/tmux`** (`extensions/tmux`): the tmux integration: a driver over the tmux CLI
diff --git a/README.md b/README.md
index 9aabe5f7..e954b6fb 100644
--- a/README.md
+++ b/README.md
@@ -135,14 +135,16 @@ Full index: [docs/examples.md](docs/examples.md).
-They attach differently but expose the same `cotal_*` tools, and all three push, so a
-peer message wakes an idle agent the instant it arrives. Any agent that implements the
+They attach differently but expose the same `cotal_*` tools, and all four push, so a
+peer message wakes an idle agent the instant it arrives; pi additionally drives a live
+turn, folding a same-scope message into an in-flight one with `steer()`. Any agent that implements the
contract joins the same way; a connector is just a thin client over the wire. Want one
for an agent that isn't here yet?
[Vote for the next connector](https://github.com/Cotal-AI/Cotal/discussions/80).
diff --git a/assets/agents/pi.svg b/assets/agents/pi.svg
new file mode 100644
index 00000000..c403a4ea
--- /dev/null
+++ b/assets/agents/pi.svg
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/docs/agent-frameworks.md b/docs/agent-frameworks.md
new file mode 100644
index 00000000..5cc4d227
--- /dev/null
+++ b/docs/agent-frameworks.md
@@ -0,0 +1,74 @@
+# Agent frameworks
+
+Besides Claude Code (see [claude-code-integration](claude-code-integration.md)), an agent
+built on an embeddable agent library can join a Cotal space as a native lateral peer. The
+first such adapter:
+
+| Extension | Framework | Language |
+|---|---|---|
+| `@cotal-ai/pi` | [pi coding agent](https://github.com/earendil-works/pi) | TypeScript |
+
+It joins the same mesh as the host-bound connectors and interoperates over the same
+subjects, presence, and delivery modes.
+
+## The pattern: a native embedded peer
+
+The adapter embeds a Cotal endpoint in the framework's own process — not a separate
+bridge. The shared piece is `MeshAgent` (in `@cotal-ai/connector-core`, the same runtime
+behind the Claude Code and Codex connectors): it owns the NATS connection, presence, and a
+stream-backed inbox, and emits `"incoming"` for each message. The adapter wires two things
+around it:
+
+1. **Inbound drives the loop.** The peer drives straight off the inbox via an `InboxTurn` —
+ the inbox is the single source of truth, no parallel buffer. On `"incoming"` it surfaces
+ the front message, flips presence to `working`, runs the agent, and delivers the reply on
+ the same delivery mode (DM/anycast → DM the sender by id; channel → multicast back to that
+ channel), then flips to `idle`. Delivery is **ack-on-surface**: the surfaced run is
+ `drainInbox`-acked only once the turn completes, so a crash or restart redelivers and
+ nothing is lost. The loop owns delivery (always routed right, sent once); runs are
+ serialized; the peer answers DMs and anycasts but only replies on a channel when named
+ (never its own echoes — ambient chatter is ack-dropped).
+2. **Mesh awareness as tools.** The model also gets read/presence tools via the framework's
+ tool mechanism (`defineTool()` for pi): `cotal_roster` (who's present) and `cotal_status`
+ (set its own status). Sending is left to the loop, so the model can't mis-route or
+ duplicate a reply.
+
+This makes the agent a real peer that wakes on traffic, like Claude Code — not a pull-only
+tool caller.
+
+**pi** is a clean fit, since it ships as an embeddable library: `@cotal-ai/pi` embeds
+`MeshAgent` alongside a pi `createAgentSession()` in one process, reads presence straight off
+the session's own event stream (`agent_start` → `working`, `tool_execution_start` → the
+running tool, `agent_end` → `idle`), and drives the loop with the session's own verbs —
+`prompt()` to wake an idle session into a turn and `steer()` to fold a *same-scope* message
+into a *live* one (true mid-turn drive, before the next LLM call; a different-scope message
+waits for its own turn, so a private DM is never folded into a channel reply), with `abort()`
+to interrupt. No external
+channel, host process, or keystrokes — inbound `"incoming"` calls a method on the embedded
+session. pi has no built-in permission gate, but in TUI mode (`PI_PEER_MODE=tui`, or the
+agent file's `peerMode: tui` frontmatter hint) any pi extension that calls `ctx.ui.*`
+(approval gates, prompts, selects, editors) is rendered live in the peer's tmux pane and
+becomes operator-answerable per-pane (e.g. an edit-approval gate); headless peers
+(unset/`headless`) run unattended (sandbox/containerize per pi's own guidance). It needs
+Node ≥22.19 and a provider key in the env.
+
+A `Connector` extension (`buildLaunch`) lets the manager spawn it: it launches the peer via
+`tsx` (dev) or `node` (built `dist/`) and forwards the launcher's identity (`COTAL_ID`), minted
+creds (`COTAL_CREDS`), and the agent file (`COTAL_AGENT_FILE`). At launch it parses the agent
+file and forwards the resolved model (`COTAL_MODEL` — the `cotal start --model` flag takes
+precedence over the file's `model:`); each runtime path reads the file's persona body and
+injects it as the agent's system prompt, so a spawned peer runs as its declared persona.
+Under auth the peer authenticates as the id the manager provisioned. The connector
+self-registers on import (`pi`); a composition root just imports it.
+
+## Running
+
+The pi connector has an example composition root under `examples/03-pi` (a manager that
+imports `@cotal-ai/pi`). See its README to run it; in short:
+
+```bash
+export ANTHROPIC_API_KEY=sk-...
+pnpm cotal up
+pnpm --filter @cotal-ai/example-03-pi manager
+pnpm cotal start --name pi1 --role research --agent pi
+```
diff --git a/docs/architecture.md b/docs/architecture.md
index 240031ac..1cb0b532 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -170,6 +170,10 @@ Implementations stay self-contained and never import each other: the `cli` drive
purely over the mesh (`start`/`stop`/`ps` control requests), so neither imports the other. Only
the example wires them together.
+Beyond the host-bound connectors, `@cotal-ai/pi` is a **native-embed** adapter: it embeds a
+Cotal endpoint directly in the pi agent's own process and drives its run loop off the inbox —
+see [agent-frameworks](agent-frameworks.md).
+
## Integration surfaces (Claude Code + OpenCode)
Each target agent exposes the same four surfaces. The adapters share one runtime
diff --git a/examples/03-pi/README.md b/examples/03-pi/README.md
new file mode 100644
index 00000000..e3e9a692
--- /dev/null
+++ b/examples/03-pi/README.md
@@ -0,0 +1,27 @@
+# Example 03 — pi coding-agent peers
+
+An agent built with the [pi coding agent](https://github.com/earendil-works/pi) joining a
+Cotal space as a native lateral peer. The adapter (`@cotal-ai/pi`) embeds a Cotal endpoint
+alongside a pi `createAgentSession()` in one process, reads presence off the session's own
+event stream, exposes the mesh to the model as `cotal_*` tools, and drives the session on
+inbound messages — `prompt()` wakes an idle session, `steer()` folds a same-scope message
+into a live turn (true mid-turn drive) — so it answers DMs and anycasts, and replies on a
+channel when mentioned by name.
+
+## Run
+
+```bash
+pnpm cotal up # local NATS/JetStream (auth on; --open for a dev mesh)
+export ANTHROPIC_API_KEY=sk-... # the peer's pi session calls the model (any pi-supported provider key works)
+pnpm --filter @cotal-ai/example-03-pi manager # start the manager
+
+# spawn a peer (either form works)
+pnpm cotal start --name pi1 --role research --agent pi
+pnpm cotal watch # watch it join and reply
+pnpm cotal join --name me --role human # then DM it: /dm pi1 hello
+```
+
+pi resolves its model and credentials via its own `AuthStorage` (falling back to provider
+keys in the environment, e.g. `ANTHROPIC_API_KEY`/`OPENAI_API_KEY`); pi has no permission
+gate, so sandbox/containerize a spawned peer per pi's own guidance. Requires Node ≥22.19. See
+[docs/agent-frameworks.md](../../docs/agent-frameworks.md) for how the adapter works.
diff --git a/examples/03-pi/package.json b/examples/03-pi/package.json
new file mode 100644
index 00000000..0f86825d
--- /dev/null
+++ b/examples/03-pi/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@cotal-ai/example-03-pi",
+ "version": "0.0.0",
+ "private": true,
+ "license": "Apache-2.0",
+ "type": "module",
+ "scripts": {
+ "manager": "tsx src/manager.ts",
+ "typecheck": "tsc -p tsconfig.json --noEmit",
+ "build": "tsc -p tsconfig.json"
+ },
+ "dependencies": {
+ "@cotal-ai/core": "workspace:*",
+ "@cotal-ai/manager": "workspace:*",
+ "@cotal-ai/pi": "workspace:*"
+ },
+ "devDependencies": {
+ "tsx": "^4.22.4"
+ }
+}
diff --git a/examples/03-pi/src/manager.ts b/examples/03-pi/src/manager.ts
new file mode 100644
index 00000000..e1bce367
--- /dev/null
+++ b/examples/03-pi/src/manager.ts
@@ -0,0 +1,36 @@
+/**
+ * Composition root for example 03 (pi coding agent). Runs a manager that spawns
+ * pi peers into the space. Each spawn is a real pi agent session
+ * (extensions/pi) that embeds a Cotal endpoint and answers DMs, anycasts, and
+ * @-mentions on channels — waking an idle session with prompt() and folding
+ * same-scope traffic into a live turn with steer(). Importing the connector
+ * self-registers it as "pi"; we also alias it under "cotal" so a bare
+ * `cotal start --name x` (the default agent type) spawns one too.
+ */
+import { DEFAULT_SERVER, isReachable, registry, type Connector } from "@cotal-ai/core";
+import { Manager } from "@cotal-ai/manager";
+import { piConnector } from "@cotal-ai/pi"; // self-registers "pi"
+
+const cotalAlias: Connector = {
+ kind: "connector",
+ name: "cotal",
+ buildLaunch: piConnector.buildLaunch,
+};
+registry.register(cotalAlias);
+
+const space = process.env.COTAL_SPACE?.trim() || "demo";
+const server = process.env.COTAL_SERVERS?.trim() || DEFAULT_SERVER;
+
+if (!(await isReachable(server))) {
+ console.error(`Can't reach NATS at ${server}. Run: pnpm cotal up`);
+ process.exit(1);
+}
+
+const mgr = new Manager({ space, servers: server });
+await mgr.start();
+console.log(`example-03-pi manager up in space "${space}" — connectors: pi, cotal`);
+console.log(`console: ${mgr.consoleUrl}`);
+
+process.on("SIGINT", () => void mgr.stop().then(() => process.exit(0)));
+process.on("SIGTERM", () => void mgr.stop().then(() => process.exit(0)));
+await new Promise(() => {});
diff --git a/examples/03-pi/tsconfig.json b/examples/03-pi/tsconfig.json
new file mode 100644
index 00000000..051d08e2
--- /dev/null
+++ b/examples/03-pi/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist"
+ },
+ "include": ["src"]
+}
diff --git a/extensions/connector-core/inbox-turn.smoke.ts b/extensions/connector-core/inbox-turn.smoke.ts
new file mode 100644
index 00000000..78bc3510
--- /dev/null
+++ b/extensions/connector-core/inbox-turn.smoke.ts
@@ -0,0 +1,120 @@
+/**
+ * Smoke test for the InboxTurn ack-on-surface helper (no NATS/LLM needed): drives it against
+ * a fake inbox — including the MAX_INBOX front-eviction the real MeshAgent does — and asserts
+ * the surface/ack invariants the embed adapters rely on.
+ *
+ * pnpm smoke:inbox
+ */
+import { InboxTurn, type InboxSource } from "./src/inbox-turn.js";
+import type { InboxItem } from "./src/agent.js";
+
+function item(id: string, fromId: string, kind: InboxItem["kind"] = "dm"): InboxItem {
+ return { id, ts: 0, fromId, fromName: fromId, kind, mentionsMe: false, text: id };
+}
+
+/** A fake inbox that mirrors MeshAgent: ingest force-acks + evicts from the front past `cap`,
+ * drainInbox acks by position, ackInbox acks by id (no-op for an absent id). */
+class FakeInbox implements InboxSource {
+ items: InboxItem[] = [];
+ acked: InboxItem[] = [];
+ constructor(private cap = Infinity) {}
+ ingest(it: InboxItem): void {
+ this.items.push(it);
+ if (this.items.length > this.cap) {
+ for (const ev of this.items.splice(0, this.items.length - this.cap)) this.acked.push(ev);
+ }
+ }
+ peekInbox(): InboxItem[] {
+ return [...this.items];
+ }
+ drainInbox(limit?: number): InboxItem[] {
+ const n = limit && limit > 0 ? Math.min(limit, this.items.length) : this.items.length;
+ const taken = this.items.splice(0, n);
+ this.acked.push(...taken);
+ return taken;
+ }
+ ackInbox(ids: string[]): InboxItem[] {
+ const wanted = new Set(ids);
+ const taken: InboxItem[] = [];
+ this.items = this.items.filter((p) => {
+ if (!wanted.has(p.id)) return true;
+ this.acked.push(p);
+ taken.push(p);
+ return false;
+ });
+ return taken;
+ }
+}
+
+function assert(cond: boolean, msg: string): void {
+ if (!cond) throw new Error(`FAIL: ${msg}`);
+}
+
+const ids = (xs: InboxItem[]): string => xs.map((x) => x.id).join(",");
+const sameScope = (a: InboxItem, b: InboxItem): boolean =>
+ a.fromId === b.fromId && a.kind === b.kind;
+
+// 1) drop leading non-actionable, start on the front, commit acks exactly the origin
+{
+ const fake = new FakeInbox();
+ fake.items = [item("echo", "self"), item("b", "alice")];
+ const turn = new InboxTurn(fake);
+ turn.drop((i) => i.fromId === "self");
+ assert(ids(fake.acked) === "echo", "drop ack-drops the self echo");
+ assert(turn.start()?.id === "b", "start surfaces the front actionable");
+ assert(turn.count === 1, "surfaced exactly the origin");
+ turn.commit();
+ assert(ids(fake.acked) === "echo,b", "commit acks the origin");
+ assert(fake.items.length === 0 && !turn.inFlight, "inbox drained, turn idle");
+}
+
+// 2) extend folds the front-contiguous same-scope run, stops at a different-scope gap
+{
+ const fake = new FakeInbox();
+ fake.items = [item("1", "alice"), item("2", "alice"), item("3", "bob"), item("4", "alice")];
+ const turn = new InboxTurn(fake);
+ assert(turn.start()?.id === "1", "origin = 1");
+ assert(ids(turn.extend(sameScope)) === "2", "folds only contiguous same-scope #2, stops at #3");
+ assert(turn.count === 2, "surfaced the 2-message run");
+ turn.commit();
+ assert(ids(fake.acked) === "1,2", "commit acks exactly the surfaced run [1,2]");
+ assert(ids(fake.items) === "3,4", "cross-scope #3 and gapped #4 stay on the stream");
+}
+
+// 3) abandon acks nothing — the surfaced run redelivers
+{
+ const fake = new FakeInbox();
+ fake.items = [item("x", "alice")];
+ const turn = new InboxTurn(fake);
+ turn.start();
+ turn.abandon();
+ assert(fake.acked.length === 0, "abandon acks nothing");
+ assert(ids(fake.items) === "x" && !turn.inFlight, "item stays on the stream; turn idle");
+}
+
+// 4) 200+ ambient burst mid-turn: the overflow evicts the in-flight prefix from the front;
+// ack-by-id no-ops the evicted origin, acks the surviving folded peer, and never touches
+// the newer messages that took the prefix's place
+{
+ const fake = new FakeInbox(200);
+ fake.ingest(item("origin", "alice"));
+ const turn = new InboxTurn(fake);
+ assert(turn.start()?.id === "origin", "origin surfaced");
+ fake.ingest(item("peer", "alice"));
+ assert(ids(turn.extend(sameScope)) === "peer", "folds the same-scope peer");
+ for (let i = 0; i < 199; i++) fake.ingest(item(`amb${i}`, "bob", "channel")); // 201 → evict 1
+ assert(
+ fake.acked.some((x) => x.id === "origin") && fake.items.some((x) => x.id === "peer"),
+ "overflow evicted+acked the origin; the folded peer survived",
+ );
+ const before = fake.acked.length;
+ turn.commit(); // ackInbox(["origin","peer"])
+ assert(fake.acked.length === before + 1, "commit acks only the survivor — evicted origin no-ops");
+ assert(!fake.items.some((x) => x.id === "peer"), "the survivor was acked by id");
+ assert(
+ fake.items.length === 199 && fake.items.every((x) => x.id.startsWith("amb")),
+ "all 199 newer ambient messages left untouched — none mis-acked",
+ );
+}
+
+console.log("INBOX-TURN SMOKE OK ✅");
diff --git a/extensions/connector-core/src/agent.ts b/extensions/connector-core/src/agent.ts
index 1a15c35f..28fbb28a 100644
--- a/extensions/connector-core/src/agent.ts
+++ b/extensions/connector-core/src/agent.ts
@@ -304,6 +304,24 @@ export class MeshAgent extends EventEmitter {
return taken.map((p) => p.item);
}
+ /** Ack + remove the buffered messages with these ids, wherever they sit in the inbox. An id
+ * that's no longer buffered — already acked, e.g. force-evicted by the MAX_INBOX overflow — is
+ * a harmless no-op. This lets a consumer ack exactly the messages it surfaced, immune to the
+ * front-eviction that shifts positions out from under {@link drainInbox}. */
+ ackInbox(ids: string[]): InboxItem[] {
+ if (!ids.length) return [];
+ const wanted = new Set(ids);
+ const taken: InboxItem[] = [];
+ this.inbox = this.inbox.filter((p) => {
+ if (!wanted.has(p.item.id)) return true;
+ p.ack();
+ this.markHandled(p.item.id);
+ taken.push(p.item);
+ return false;
+ });
+ return taken;
+ }
+
/** Record an id as surfaced/handled, for {@link ingest}'s commit-aware cross-path dedup. Bounded via
* two rotating windows: when the live set fills, it becomes the previous window and a fresh one
* starts — so memory stays ~2× the cap while the lookup horizon never shrinks below it. */
diff --git a/extensions/connector-core/src/inbox-turn.ts b/extensions/connector-core/src/inbox-turn.ts
new file mode 100644
index 00000000..771da05e
--- /dev/null
+++ b/extensions/connector-core/src/inbox-turn.ts
@@ -0,0 +1,122 @@
+import type { InboxItem } from "./agent.js";
+
+/** The slice of {@link MeshAgent} an {@link InboxTurn} drives off of. */
+export interface InboxSource {
+ /** Buffered messages, oldest first, without acking. */
+ peekInbox(): InboxItem[];
+ /** Ack + remove the front `limit` messages and return them. */
+ drainInbox(limit?: number): InboxItem[];
+ /** Ack + remove the messages with these ids (any position); an absent id is a no-op. */
+ ackInbox(ids: string[]): InboxItem[];
+}
+
+/**
+ * Ack-on-surface delivery off a {@link MeshAgent}'s stream-backed inbox.
+ *
+ * The inbox is the single source of truth — there is no parallel buffer to drift out of
+ * sync. A turn *surfaces* messages by id and acks them (via `ackInbox`) only once the turn
+ * COMPLETES; a crash or interrupt before {@link commit} leaves them on the stream, so they
+ * redeliver — nothing is acked merely by being read. Acking by id (rather than by front
+ * position) keeps it correct even when the `MAX_INBOX` overflow force-evicts the in-flight
+ * prefix from the front mid-turn: those ids are already gone, so the ack no-ops them and
+ * never touches the newer messages that took their place.
+ *
+ * Fits both shapes the embed adapters use:
+ * - a one-message serialize loop: `start()` → run → `commit()`;
+ * - pi's same-scope steer loop: `start()` → `extend(match)`* (fold contiguous peers, e.g.
+ * same-scope messages, as they stream in) → `commit()` on a clean/failed finish, or
+ * `abandon()` on interrupt.
+ *
+ * `cotal_inbox` (where exposed) must stay on `peekInbox` so a model call can't double-drain
+ * what the loop already surfaced.
+ */
+export class InboxTurn {
+ private surfacedIds: string[] = [];
+ private _origin?: InboxItem;
+
+ constructor(private readonly source: InboxSource) {}
+
+ /** True while a turn holds surfaced-but-unacked messages. */
+ get inFlight(): boolean {
+ return this.surfacedIds.length > 0;
+ }
+
+ /** The message that opened the current turn (its reply scope), or undefined when idle. */
+ get origin(): InboxItem | undefined {
+ return this._origin;
+ }
+
+ /** How many messages this turn has surfaced. */
+ get count(): number {
+ return this.surfacedIds.length;
+ }
+
+ /**
+ * Ack-drop the leading messages matching `skip` (own echoes, ambient chatter) so they
+ * neither block the front nor linger to the inbox cap. Only valid with no turn in flight
+ * (a between-turns, synchronous front trim — no eviction can interleave).
+ */
+ drop(skip: (item: InboxItem) => boolean): void {
+ if (this.surfacedIds.length) return;
+ const pending = this.source.peekInbox();
+ let n = 0;
+ while (n < pending.length && skip(pending[n])) n++;
+ if (n) this.source.drainInbox(n);
+ }
+
+ /**
+ * Open a turn on the front message (its origin) and surface it. Returns the origin, or
+ * undefined if the inbox is empty. Idempotent while a turn is in flight (returns the
+ * current origin). Call {@link drop} first so the front is the message you mean to answer.
+ */
+ start(): InboxItem | undefined {
+ if (this.surfacedIds.length) return this._origin;
+ const front = this.source.peekInbox()[0];
+ if (!front) return undefined;
+ this._origin = front;
+ this.surfacedIds = [front.id];
+ return front;
+ }
+
+ /**
+ * Fold the front-contiguous run of not-yet-surfaced messages that `match(item, origin)`
+ * into this turn, stopping at the first unsurfaced non-match (so a cross-scope message is
+ * left to open its own turn, preserving FIFO + scope isolation). Already-surfaced messages
+ * are skipped, so an overflow that evicts part of the prefix can't desync this. Returns the
+ * newly surfaced messages for the caller to feed in (e.g. via steer). No-op until
+ * {@link start}.
+ */
+ extend(match: (item: InboxItem, origin: InboxItem) => boolean): InboxItem[] {
+ if (!this._origin) return [];
+ const surfaced = new Set(this.surfacedIds);
+ const run: InboxItem[] = [];
+ for (const item of this.source.peekInbox()) {
+ if (surfaced.has(item.id)) continue; // already surfaced (still buffered) — skip
+ if (!match(item, this._origin)) break; // first unsurfaced non-match → stop at the gap
+ run.push(item);
+ this.surfacedIds.push(item.id);
+ }
+ return run;
+ }
+
+ /**
+ * Ack the surfaced messages by id — the sole ack site. Call on a terminal status that
+ * should consume them: a clean finish, or a failed/dropped turn (drop, no retry-loop). Ids
+ * already evicted by the overflow no-op. Do NOT call on interrupt/crash — use
+ * {@link abandon} so the run redelivers.
+ */
+ commit(): void {
+ if (this.surfacedIds.length) this.source.ackInbox(this.surfacedIds);
+ this.reset();
+ }
+
+ /** End the turn without acking — the surfaced run stays on the stream and redelivers. */
+ abandon(): void {
+ this.reset();
+ }
+
+ private reset(): void {
+ this.surfacedIds = [];
+ this._origin = undefined;
+ }
+}
diff --git a/extensions/connector-core/src/index.ts b/extensions/connector-core/src/index.ts
index ee15298e..0db4edf5 100644
--- a/extensions/connector-core/src/index.ts
+++ b/extensions/connector-core/src/index.ts
@@ -1,5 +1,6 @@
export * from "./config.js";
export * from "./agent.js";
+export * from "./inbox-turn.js";
export * from "./runtime.js";
export * from "./launch.js";
export * from "./tool-specs.js";
diff --git a/extensions/pi/README.md b/extensions/pi/README.md
new file mode 100644
index 00000000..a182b1f4
--- /dev/null
+++ b/extensions/pi/README.md
@@ -0,0 +1,13 @@
+# @cotal-ai/pi
+
+The pi adapter: a native mesh peer that embeds a Cotal endpoint inside a [pi coding
+agent](https://github.com/earendil-works/pi) process and answers mesh traffic through the
+agent's own loop. Reuses `MeshAgent` from
+[`@cotal-ai/connector-core`](../connector-core); unlike the host-bound connectors it also
+drives a *live* turn — `steer()` folds a same-scope message into an in-flight one.
+
+**Tier:** `extensions/`. Peer-depends [`@cotal-ai/core`](../../packages/core); self-registers on
+import.
+
+See [docs/agent-frameworks.md](../../docs/agent-frameworks.md) for the native-embed pattern,
+and the [root AGENTS.md](../../AGENTS.md) for the tier rules.
diff --git a/extensions/pi/package.json b/extensions/pi/package.json
new file mode 100644
index 00000000..1ed49dcd
--- /dev/null
+++ b/extensions/pi/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "@cotal-ai/pi",
+ "version": "0.1.0",
+ "license": "Apache-2.0",
+ "type": "module",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ }
+ },
+ "engines": {
+ "node": ">=22.19.0"
+ },
+ "scripts": {
+ "typecheck": "tsc -p tsconfig.json --noEmit",
+ "build": "tsc -p tsconfig.json",
+ "prepublishOnly": "pnpm run build"
+ },
+ "dependencies": {
+ "@earendil-works/pi-ai": "^0.79.0",
+ "@earendil-works/pi-coding-agent": "^0.79.0",
+ "@cotal-ai/connector-core": "workspace:*",
+ "tsx": "^4.22.4"
+ },
+ "peerDependencies": {
+ "@cotal-ai/core": "workspace:*"
+ },
+ "devDependencies": {
+ "@cotal-ai/core": "workspace:*"
+ },
+ "files": [
+ "dist"
+ ],
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/extensions/pi/src/connector.ts b/extensions/pi/src/connector.ts
new file mode 100644
index 00000000..d1172a66
--- /dev/null
+++ b/extensions/pi/src/connector.ts
@@ -0,0 +1,89 @@
+import { fileURLToPath } from "node:url";
+import { loadAgentFile, registry, type Connector, type LaunchOpts, type LaunchSpec } from "@cotal-ai/core";
+
+/** The peer loop runs via tsx when launched from source (.ts, dev) or via node directly
+ * when launched from built dist (.js). Using node for the built artifact matters: the
+ * manager's pty runtime sends SIGTERM to the spawned command on `cotal stop`, and a
+ * tsx wrapper does not forward that signal to its node child (the child is SIGKILLed
+ * after the grace window instead of running its shutdown handler). Running the built
+ * .js with node lets the peer's SIGTERM/SIGINT handlers fire for a clean mesh
+ * disconnect + raw-mode restore. `main` is loaded with the same extension as this
+ * module — `main.ts` in dev, `main.js` from built `dist/` — so the entrypoint
+ * resolves to a file that actually exists in either mode. */
+const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
+const MAIN = fileURLToPath(new URL(`./main${ext}`, import.meta.url));
+const CMD = ext === ".ts"
+ ? fileURLToPath(new URL("../node_modules/.bin/tsx", import.meta.url)) // dev: tsx transpiles .ts
+ : process.execPath; // built: node runs .js + forwards signals
+
+/** Provider API keys pi resolves from the environment (AuthStorage falls back to env).
+ * Forwarded when present so a spawned peer has credentials for its model. */
+export const PROVIDER_KEYS = [
+ "ANTHROPIC_API_KEY",
+ "OPENAI_API_KEY",
+ "GEMINI_API_KEY",
+ "GOOGLE_API_KEY",
+ "GROQ_API_KEY",
+ "XAI_API_KEY",
+ "DEEPSEEK_API_KEY",
+ "MISTRAL_API_KEY",
+ "OPENROUTER_API_KEY",
+];
+
+/**
+ * The pi connector: launches an embedded Cotal peer that runs the pi coding-agent SDK
+ * loop and answers mesh traffic as a lateral peer. Inbound drives the loop directly
+ * (prompt to wake, steer to interject mid-turn). Forwards the launcher's identity +
+ * minted creds so the peer authenticates as `id` under auth. Parses the agent file at
+ * launch (so a malformed persona fails loud at `cotal start`, matching the sibling
+ * connectors) and forwards the resolved model (`COTAL_MODEL`); the runtime path reads
+ * the persona body from the agent file and injects it as the system prompt, so a
+ * spawned peer runs as its declared persona. `PI_PEER_MODE=tui` (or the agent file's `peerMode: tui` frontmatter hint)
+ * selects the interactive per-pane host (operator-answerable `ctx.ui.*` dialogs);
+ * unset/`headless` runs the headless embedded loop. Self-registers on import; the manager resolves it by
+ * agent type "pi".
+ */
+export const piConnector: Connector = {
+ kind: "connector",
+ name: "pi",
+ buildLaunch(opts: LaunchOpts): LaunchSpec {
+ const env: Record = { COTAL_SPACE: opts.space, COTAL_NAME: opts.name };
+ if (opts.role) env.COTAL_ROLE = opts.role;
+ if (opts.id) env.COTAL_ID = opts.id;
+ if (opts.creds) env.COTAL_CREDS = opts.creds;
+ if (opts.servers) env.COTAL_SERVERS = opts.servers;
+ if (opts.configPath) {
+ env.COTAL_AGENT_FILE = opts.configPath;
+ // Parse the persona file here (not in the child) so a malformed persona
+ // fails loud at `cotal start` (matching the sibling connectors) and so the
+ // `cotal start --model` flag precedence over the file's `model:` resolves
+ // once, here. Forward only the short resolved model string; the persona
+ // body is read from disk by whichever runtime path runs (TUI/headless), so
+ // a large persona never rides in env (ARG_MAX ceiling on spawn).
+ const def = loadAgentFile(opts.configPath);
+ const model = opts.model ?? def.model;
+ if (model) env.COTAL_MODEL = model;
+ // Launch mode (headless embedded loop vs. interactive TUI host). The agent
+ // file's `peerMode:` frontmatter key is the persistent per-agent knob — an
+ // unmodelled scalar swept into AgentDef.meta, the same passthrough the
+ // OpenCode connector uses for `face:`, so core stays ignorant of it. An
+ // operator one-off still wins: `PI_PEER_MODE` env overrides the file and is
+ // forwarded verbatim (the peer validates it, see main.ts). Only "tui" is
+ // worth forwarding — "headless"/unset leave the var absent so the peer
+ // defaults to headless; an invalid file value fails loud here, not silently.
+ const peerMode = def.meta?.peerMode;
+ if (peerMode !== undefined && peerMode !== "tui" && peerMode !== "headless")
+ throw new Error(`agent file ${opts.configPath}: peerMode must be "tui" or "headless" (got ${JSON.stringify(peerMode)})`);
+ if (!process.env.PI_PEER_MODE && peerMode === "tui") env.PI_PEER_MODE = "tui";
+ }
+ for (const key of PROVIDER_KEYS) {
+ const value = process.env[key];
+ if (value) env[key] = value;
+ }
+ // Operator one-off override, forwarded by NAME (never via ...process.env).
+ if (process.env.PI_PEER_MODE) env.PI_PEER_MODE = process.env.PI_PEER_MODE;
+ return { command: CMD, args: [MAIN], env };
+ },
+};
+
+registry.register(piConnector);
diff --git a/extensions/pi/src/index.ts b/extensions/pi/src/index.ts
new file mode 100644
index 00000000..cdcd3295
--- /dev/null
+++ b/extensions/pi/src/index.ts
@@ -0,0 +1,3 @@
+export * from "./connector.js";
+export { runPiPeer } from "./peer.js";
+export { runTuiClient } from "./tui-client.js";
diff --git a/extensions/pi/src/main.ts b/extensions/pi/src/main.ts
new file mode 100644
index 00000000..5f4aa897
--- /dev/null
+++ b/extensions/pi/src/main.ts
@@ -0,0 +1,15 @@
+import { configFromEnv } from "@cotal-ai/connector-core";
+import { runPiPeer } from "./peer.js";
+import { runTuiClient } from "./tui-client.js";
+
+const config = configFromEnv();
+const mode = process.env.PI_PEER_MODE;
+if (mode !== undefined && mode !== "" && mode !== "tui" && mode !== "headless")
+ throw new Error(
+ `PI_PEER_MODE must be "tui" or "headless" (got ${JSON.stringify(mode)}); unset => headless`,
+ );
+const entry = mode === "tui" ? runTuiClient : runPiPeer; // "headless" | unset => current path
+entry(config).catch((e) => {
+ process.stderr.write(`[pi-peer] fatal: ${(e as Error).message}\n`);
+ process.exit(1);
+});
diff --git a/extensions/pi/src/peer.ts b/extensions/pi/src/peer.ts
new file mode 100644
index 00000000..2bf8e110
--- /dev/null
+++ b/extensions/pi/src/peer.ts
@@ -0,0 +1,284 @@
+import { MeshAgent, InboxTurn, configFromEnv } from "@cotal-ai/connector-core";
+import type { InboxItem, AgentConfig } from "@cotal-ai/connector-core";
+import { loadAgentFile } from "@cotal-ai/core";
+import {
+ AuthStorage,
+ DefaultResourceLoader,
+ ModelRegistry,
+ SessionManager,
+ SettingsManager,
+ createAgentSession,
+ defineTool,
+ getAgentDir,
+} from "@earendil-works/pi-coding-agent";
+import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
+import { Type, type Api, type Model } from "@earendil-works/pi-ai";
+
+function log(e: unknown): void {
+ process.stderr.write(`[pi-peer] ${e instanceof Error ? e.message : String(e)}\n`);
+}
+
+/**
+ * Read-only / awareness tools. Replies are NOT sent by the model — the run loop delivers
+ * the agent's final text on the right delivery mode (see runPiPeer), so the model can't
+ * mis-route or duplicate a reply. These just let it see who is present and report its own
+ * status. Mirrors the openai-agents / vercel-ai adapters.
+ */
+function buildTools(mesh: MeshAgent) {
+ const cotal_roster = defineTool({
+ name: "cotal_roster",
+ label: "Cotal roster",
+ description: "List the peers currently present on the Cotal mesh.",
+ parameters: Type.Object({}),
+ execute: async () => {
+ const peers = mesh.roster();
+ const text = peers.length
+ ? peers
+ .map((p) => `${p.card.name}${p.card.role ? `/${p.card.role}` : ""} [${p.status}]`)
+ .join("\n")
+ : "roster is empty";
+ return { content: [{ type: "text", text }], details: {} };
+ },
+ });
+
+ const cotal_status = defineTool({
+ name: "cotal_status",
+ label: "Cotal status",
+ description: "Update this peer's presence status on the mesh.",
+ parameters: Type.Object({
+ status: Type.Union([Type.Literal("idle"), Type.Literal("waiting"), Type.Literal("working")]),
+ activity: Type.Optional(Type.String()),
+ }),
+ execute: async (_id, params) => {
+ await mesh.setStatus(params.status, params.activity);
+ return { content: [{ type: "text", text: `status set to ${params.status}` }], details: {} };
+ },
+ });
+
+ return [cotal_roster, cotal_status];
+}
+
+/** Actionable = a DM, an anycast to our role, or a channel message that names us — and not
+ * our own echo. Pure ambient channel chatter is dropped (acked, never answered). */
+function actionable(mesh: MeshAgent, item: InboxItem): boolean {
+ if (item.fromId === mesh.id) return false;
+ return item.kind !== "channel" || item.mentionsMe;
+}
+
+/** The audience a reply goes back to. A channel message is answered ON that channel
+ * (sender-independent — everyone there already saw it); a DM/anycast is answered privately
+ * to its sender. Two messages with the same scope can share one turn + reply; mixing scopes
+ * cannot (a DM folded into a channel turn would broadcast private content), so different-
+ * scope messages get their own scope-isolated turn. */
+function scopeKey(item: InboxItem): string {
+ return item.kind === "channel" && item.channel ? `channel:${item.channel}` : `dm:${item.fromId}`;
+}
+
+/** Pull this turn's final assistant text from the agent_end payload (not the session-wide
+ * last message), so a turn that produced no text never re-delivers a previous reply. */
+function turnReplyText(messages: readonly unknown[]): string | undefined {
+ for (let i = messages.length - 1; i >= 0; i--) {
+ const m = messages[i] as { role?: unknown; content?: unknown };
+ if (m.role !== "assistant" || !Array.isArray(m.content)) continue;
+ const text = m.content
+ .map((p) =>
+ p && typeof p === "object" && (p as { type?: unknown }).type === "text"
+ ? String((p as { text?: unknown }).text ?? "")
+ : "",
+ )
+ .join("");
+ return text.length ? text : undefined;
+ }
+ return undefined;
+}
+
+/**
+ * Resolve a persona's pinned `model:` string to a pi {@link Model} object using
+ * only public API (`ModelRegistry.getAvailable` + the public `Model.provider`/
+ * `Model.id` fields). Exact-match only — mirrors pi's internal
+ * `findExactModelReferenceMatch` contract: canonical `provider/id`, then a split
+ * `provider`+`id`, then a bare `id` (rejected if ambiguous across providers). No
+ * fuzzy/partial matching: that is pi's CLI convenience (internal, not exported)
+ * and not appropriate for a declarative persona pin. Throws on not-found/
+ * ambiguous so a bad pin fails loud at startup instead of silently falling back
+ * to pi's default model. (TUI mode passes the string to pi's CLI which fuzzy-
+ * resolves it; headless runs in-process where only exact resolution is public.)
+ */
+function resolveModel(ref: string, registry: ModelRegistry): Model {
+ const avail = registry.getAvailable();
+ const r = ref.trim().toLowerCase();
+ if (!r) throw new Error("pi peer: empty persona model");
+ // canonical "provider/id"
+ let m = avail.find((x) => `${x.provider}/${x.id}`.toLowerCase() === r);
+ // split provider + id
+ if (!m && r.includes("/")) {
+ const slash = ref.indexOf("/");
+ const provider = ref.slice(0, slash).trim().toLowerCase();
+ const id = ref.slice(slash + 1).trim().toLowerCase();
+ if (provider && id) m = avail.find((x) => x.provider.toLowerCase() === provider && x.id.toLowerCase() === id);
+ }
+ // bare id — must be unique across providers
+ if (!m) {
+ const ids = avail.filter((x) => x.id.toLowerCase() === r);
+ if (ids.length === 1) m = ids[0];
+ }
+ if (!m) throw new Error(`pi peer: persona model "${ref}" not found or ambiguous — use provider/id`);
+ return m;
+}
+
+/**
+ * Embed a pi coding-agent session in-process and drive it from mesh traffic. This is the
+ * native-embed pattern (cf. docs/agent-frameworks.md): MeshAgent owns the NATS connection,
+ * presence, and a stream-backed inbox; pi's loop is driven straight off that inbox via an
+ * {@link InboxTurn} — `prompt()` wakes an idle session on the front message, `steer()`
+ * interjects into a live one (true mid-turn drive, pi's distinctive capability), and presence
+ * is read off the session's event stream. The loop owns reply routing, so the model never
+ * mis-routes.
+ *
+ * Delivery is ack-on-surface: the inbox is the single source of truth (no parallel buffer);
+ * a turn surfaces a front-contiguous run and `commit()`s (drainInbox-acks) it only once the
+ * turn completes, so a crash/interrupt redelivers. Each turn is owned by one reply scope —
+ * a mid-turn message is steered in only when it shares that scope; a different-scope message
+ * stays on the stream and becomes the next turn's origin — so a private DM is never folded
+ * into a channel broadcast.
+ */
+export async function runPiPeer(config: AgentConfig = configFromEnv()): Promise {
+ const mesh = new MeshAgent(config);
+ mesh.start();
+
+ const authStorage = AuthStorage.create();
+ const modelRegistry = ModelRegistry.create(authStorage);
+
+ // Persona + model from the agent file. buildLaunch already parsed the file for
+ // launch-time validation + `--model` precedence and forwarded COTAL_MODEL (the
+ // resolved model string). The persona body is read here, in-process, and handed
+ // to the ResourceLoader as appendSystemPrompt — the in-process equivalent of
+ // TUI's --append-system-prompt. Construct the loader with the same defaults
+ // createAgentSession would have used (cwd/agentDir/settingsManager) so passing
+ // our own doesn't silently drop extension/skill/theme loading.
+ const def = process.env.COTAL_AGENT_FILE ? loadAgentFile(process.env.COTAL_AGENT_FILE) : undefined;
+ const cwd = process.cwd();
+ const agentDir = getAgentDir();
+ const settingsManager = SettingsManager.create(cwd, agentDir);
+ const resourceLoader = new DefaultResourceLoader({
+ cwd,
+ agentDir,
+ settingsManager,
+ appendSystemPrompt: def?.persona ? [def.persona] : undefined,
+ });
+ await resourceLoader.reload();
+ const model = process.env.COTAL_MODEL ? resolveModel(process.env.COTAL_MODEL, modelRegistry) : undefined;
+
+ const { session } = await createAgentSession({
+ cwd,
+ agentDir,
+ authStorage,
+ modelRegistry,
+ settingsManager,
+ sessionManager: SessionManager.inMemory(),
+ customTools: buildTools(mesh),
+ resourceLoader,
+ model,
+ });
+
+ const turn = new InboxTurn(mesh);
+ let streaming = false; // gates steer(): only valid once the agent is actually streaming
+
+ const setStatus = (status: "idle" | "working", activity?: string): void => {
+ void mesh.setStatus(status, activity).catch(() => {});
+ };
+
+ const framed = (item: InboxItem): string =>
+ `from ${item.fromName} via ${item.kind}: ${item.text}`;
+
+ function deliver(to: InboxItem, text: string): void {
+ if (to.kind === "channel" && to.channel) void mesh.send(text, to.channel).catch(log);
+ else void mesh.dm(to.fromId, text).catch(log);
+ }
+
+ /** Start the next turn on the front actionable message, dropping leading non-actionable
+ * (own echoes, ambient chatter) first. No-op while a turn is in flight. */
+ function pump(): void {
+ if (turn.inFlight) return;
+ turn.drop((i) => !actionable(mesh, i));
+ const origin = turn.start();
+ if (!origin) {
+ setStatus("idle");
+ return;
+ }
+ streaming = false;
+ void session.prompt(framed(origin)).catch(onStartError); // wake into a fresh turn
+ }
+
+ /** Fold any front-contiguous, same-scope actionable messages into the live turn (mid-turn
+ * steer). A cross-scope or ambient message breaks contiguity and waits for its own turn. */
+ function foldSameScope(): void {
+ if (!turn.origin || !streaming) return;
+ for (const item of turn.extend((i, o) => actionable(mesh, i) && scopeKey(i) === scopeKey(o))) {
+ void session.steer(framed(item)).catch(log);
+ }
+ }
+
+ function onStartError(e: unknown): void {
+ log(e);
+ if (streaming) return; // already running → agent_end will complete the turn
+ turn.commit(); // pre-flight failure (e.g. no model/key): drop, no retry-loop
+ setStatus("idle");
+ pump();
+ }
+
+ mesh.on("incoming", () => {
+ if (turn.inFlight) foldSameScope();
+ else pump();
+ });
+ mesh.on("wake", () => {
+ if (!turn.inFlight) pump();
+ });
+
+ session.subscribe((event: AgentSessionEvent) => {
+ switch (event.type) {
+ case "agent_start":
+ streaming = true;
+ setStatus("working", "thinking");
+ foldSameScope(); // flush same-scope peers that landed before streaming began
+ break;
+ case "tool_execution_start":
+ setStatus("working", `running ${event.toolName}`);
+ break;
+ case "tool_execution_end":
+ setStatus("working", "thinking"); // clear the per-tool activity so it can't read stale
+ break;
+ case "agent_end": {
+ if (event.willRetry) break; // transient failure; a retry turn follows
+ const to = turn.origin;
+ const reply = turnReplyText(event.messages);
+ turn.commit(); // ack the surfaced run — clean or failed both consume (no retry-loop)
+ streaming = false;
+ if (to && reply) deliver(to, reply);
+ pump(); // next scope
+ break;
+ }
+ }
+ });
+
+ // Drain anything already buffered before the listeners were attached.
+ pump();
+
+ async function shutdown(): Promise {
+ try {
+ if (turn.inFlight) {
+ turn.abandon(); // leave the in-flight run on the stream → redeliver, no peer dropped
+ await session.abort();
+ }
+ session.dispose();
+ await mesh.stop();
+ } finally {
+ process.exit(0);
+ }
+ }
+ process.on("SIGINT", () => void shutdown());
+ process.on("SIGTERM", () => void shutdown());
+
+ // Keep alive.
+ await new Promise(() => {});
+}
diff --git a/extensions/pi/src/rpc-frames.ts b/extensions/pi/src/rpc-frames.ts
new file mode 100644
index 00000000..7e9e25ff
--- /dev/null
+++ b/extensions/pi/src/rpc-frames.ts
@@ -0,0 +1,74 @@
+import type { ChildProcess } from "node:child_process";
+import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
+
+/**
+ * pi RPC protocol frames, as used by the Cotal pi connector's TUI host.
+ *
+ * pi in `--mode rpc` reads NDJSON commands from stdin and emits NDJSON events on
+ * stdout. The agent activity events are raw {@link AgentSessionEvent}s (the
+ * same type the in-process loop subscribes to); command acknowledgements are
+ * `response` frames; extension UI calls (ctx.ui.confirm/select/input/editor/…)
+ * arrive as `extension_ui_request` frames and are answered with
+ * `extension_ui_response`. Shapes mirrored from the installed pi SDK
+ * (modes/rpc/rpc-types.ts + core/agent-session.ts) so this module has no
+ * dependency on pi's internal rpc-types export.
+ */
+
+/** Commands the host writes to the child's stdin. */
+export type RpcCommand =
+ | { type: "prompt"; message: string; id?: string }
+ | { type: "steer"; message: string; id?: string }
+ | { type: "follow_up"; message: string; id?: string }
+ | { type: "abort"; id?: string }
+ // Extension UI answers. `confirm` -> {confirmed}; `select`/`input`/`editor` -> {value};
+ // a dismissed dialog -> {cancelled: true}. All keyed by the request `id`.
+ | { type: "extension_ui_response"; id: string; confirmed: boolean }
+ | { type: "extension_ui_response"; id: string; value: string }
+ | { type: "extension_ui_response"; id: string; cancelled: true };
+
+/** A request from a pi extension for human input (ctx.ui.*). */
+export type RpcExtensionUIRequest =
+ | { type: "extension_ui_request"; id: string; method: "confirm"; title: string; message: string; timeout?: number }
+ | { type: "extension_ui_request"; id: string; method: "select"; title: string; options: string[]; timeout?: number }
+ | { type: "extension_ui_request"; id: string; method: "input"; title: string; placeholder?: string; timeout?: number }
+ | { type: "extension_ui_request"; id: string; method: "editor"; title: string; prefill?: string }
+ | { type: "extension_ui_request"; id: string; method: "notify"; message: string; notifyType?: "info" | "warning" | "error" }
+ | { type: "extension_ui_request"; id: string; method: "setStatus"; statusKey: string; statusText: string | undefined }
+ | { type: "extension_ui_request"; id: string; method: "setWidget"; widgetKey: string; widgetLines: string[] | undefined; widgetPlacement?: "aboveEditor" | "belowEditor" }
+ | { type: "extension_ui_request"; id: string; method: "setTitle"; title: string }
+ | { type: "extension_ui_request"; id: string; method: "set_editor_text"; text: string };
+
+/** A command acknowledgement. */
+export type RpcResponse =
+ | { type: "response"; command: string; success: true; id?: string; data?: unknown }
+ | { type: "response"; command: string; success: false; id?: string; error: string };
+
+/** Everything the host reads off the child's stdout. */
+export type RpcFrame = AgentSessionEvent | RpcExtensionUIRequest | RpcResponse | { type: "extension_error"; extensionPath?: string; event?: string; error: string };
+
+/** Write one command as an NDJSON line to the child's stdin. */
+export function writeRpc(child: ChildProcess, cmd: RpcCommand): void {
+ if (!child.stdin) throw new Error("rpc: child has no stdin");
+ child.stdin.write(JSON.stringify(cmd) + "\n");
+}
+
+/** Read NDJSON frames from the child's stdout. Throws on a malformed line (no fallback). */
+export async function* readJsonLines(stream: NodeJS.ReadableStream): AsyncGenerator {
+ let buf = "";
+ for await (const chunk of stream) {
+ buf += chunk;
+ let nl: number;
+ while ((nl = buf.indexOf("\n")) >= 0) {
+ const line = buf.slice(0, nl).trim();
+ buf = buf.slice(nl + 1);
+ if (!line) continue;
+ let parsed: RpcFrame;
+ try {
+ parsed = JSON.parse(line) as RpcFrame;
+ } catch (e) {
+ throw new Error(`rpc: malformed stdout line: ${(e as Error).message}: ${line}`);
+ }
+ yield parsed;
+ }
+ }
+}
\ No newline at end of file
diff --git a/extensions/pi/src/tui-client.ts b/extensions/pi/src/tui-client.ts
new file mode 100644
index 00000000..57e9adf4
--- /dev/null
+++ b/extensions/pi/src/tui-client.ts
@@ -0,0 +1,404 @@
+import { fileURLToPath } from "node:url";
+import { spawn } from "node:child_process";
+import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { loadAgentFile } from "@cotal-ai/core";
+import { MeshAgent, configFromEnv, launchEnv, type AgentConfig, type InboxItem } from "@cotal-ai/connector-core";
+import { writeRpc, readJsonLines } from "./rpc-frames.js";
+import { PROVIDER_KEYS } from "./connector.js";
+import { createTuiRenderer, type TuiRenderer } from "./tui-render.js";
+
+// Resolve the `pi` wrapper the same way connector.ts resolves `tsx`: as the
+// extension's own node_modules/.bin entry (pnpm provides it for the
+// @earendil-works/pi-coding-agent dependency). Spawn PI_CLI directly, matching
+// how the connector spawns TSX as the command.
+const PI_CLI = fileURLToPath(new URL("../node_modules/.bin/pi", import.meta.url));
+
+// ---- helpers duplicated from peer.ts. Kept inline rather than shared via a
+// `peer-util.ts` because the two paths diverge in places (e.g. `actionable`
+// writes `mentionsMe === true` here vs bare `mentionsMe` in peer.ts, and `framed`
+// is a top-level function here vs an inline arrow there) — forcing a shared module
+// would paper over those intentional differences. Small, contract-stable helpers;
+// revisit the extraction only if both paths converge to byte-identical bodies.
+
+/** Actionable = a DM, an anycast to our role, or a channel message that names us. */
+function actionable(mesh: MeshAgent, item: InboxItem): boolean {
+ if (item.fromId === mesh.id) return false;
+ return item.kind !== "channel" || item.mentionsMe === true;
+}
+
+/** The audience a reply goes back to. Same-scope messages share a turn. */
+function scopeKey(item: InboxItem): string {
+ return item.kind === "channel" && item.channel ? `channel:${item.channel}` : `dm:${item.fromId}`;
+}
+
+function framed(item: InboxItem): string {
+ return `from ${item.fromName} via ${item.kind}: ${item.text}`;
+}
+
+/** Pull this turn's final assistant text from the agent_end messages array. */
+function turnReplyText(messages: readonly unknown[]): string | undefined {
+ for (let i = messages.length - 1; i >= 0; i--) {
+ const m = messages[i] as { role?: unknown; content?: unknown };
+ if (m.role !== "assistant" || !Array.isArray(m.content)) continue;
+ const text = m.content
+ .map((p) =>
+ p && typeof p === "object" && (p as { type?: unknown }).type === "text"
+ ? String((p as { text?: unknown }).text ?? "")
+ : "",
+ )
+ .join("");
+ return text.length ? text : undefined;
+ }
+ return undefined;
+}
+
+function log(e: unknown): void {
+ process.stderr.write(`[pi-peer] ${e instanceof Error ? e.message : String(e)}\n`);
+}
+
+/**
+ * Interactive mesh-driven peer — the TUI launch mode selected by `PI_PEER_MODE=tui`.
+ * Spawns stock `pi --mode rpc` as a child and drives it from the mesh while rendering
+ * `extension_ui_request` dialogs locally in the pane, so any pi extension that calls
+ * `ctx.ui.*` (approval gates, prompts, selects, editors) becomes live and
+ * operator-answerable per-pane.
+ */
+export async function runTuiClient(config: AgentConfig = configFromEnv()): Promise {
+ const mesh = new MeshAgent(config);
+ mesh.start();
+
+ const renderer: TuiRenderer & { _setStreaming?: (on: boolean) => void } = createTuiRenderer();
+ renderer.start();
+
+ // Persona body → tempfile → --append-system-prompt . pi reads the file
+ // contents (resource-loader resolvePromptInput: existsSync → readFileSync), so
+ // only the short temp path rides in argv — no ARG_MAX ceiling for a large
+ // persona. The body is frontmatter-stripped via loadAgentFile; the raw persona
+ // file has `---` metadata that must not enter the system prompt. The resolved
+ // model string (COTAL_MODEL) is forwarded by buildLaunch; pi's CLI resolves it
+ // (exact + fuzzy) the same as any `pi --model` invocation.
+ let personaDir: string | undefined;
+ const agentFile = process.env.COTAL_AGENT_FILE;
+ if (agentFile) {
+ const def = loadAgentFile(agentFile);
+ if (def.persona) {
+ personaDir = mkdtempSync(join(tmpdir(), "cotal-persona-"));
+ writeFileSync(join(personaDir, "persona.md"), def.persona, { mode: 0o600 });
+ }
+ }
+ const cleanupPersona = (): void => {
+ if (personaDir) {
+ try { rmSync(personaDir, { recursive: true, force: true }); } catch { /* */ }
+ personaDir = undefined;
+ }
+ };
+
+ const childArgs = ["--mode", "rpc", "--no-session"];
+ if (personaDir) childArgs.push("--append-system-prompt", join(personaDir, "persona.md"));
+ if (process.env.COTAL_MODEL) childArgs.push("--model", process.env.COTAL_MODEL);
+ const child = spawn(PI_CLI, childArgs, {
+ cwd: process.cwd(),
+ stdio: ["pipe", "pipe", "pipe"],
+ env: launchEnv({ providerKeys: PROVIDER_KEYS }),
+ });
+
+ // turn/pending/streaming/halt — the loop state.
+ // `InboxTurn` in `extensions/connector-core` — same API as peer.ts.
+ const { InboxTurn } = await import("@cotal-ai/connector-core");
+ const turn = new InboxTurn(mesh);
+ let pending = false; // prompt sent, awaiting preflight response
+ let halt = false; // set on preflight failure → STOP pump
+ let stderrBuf = "";
+
+ const setMeshStatus = (status: "idle" | "working" | "waiting", activity?: string): void => {
+ void mesh.setStatus(status, activity).catch(log);
+ };
+
+ const deliver = (to: InboxItem, text: string): void => {
+ if (to.kind === "channel" && to.channel) void mesh.send(text, to.channel).catch(log);
+ else void mesh.dm(to.fromId, text).catch(log);
+ };
+
+ function pump(): void {
+ if (turn.inFlight || pending || halt) return;
+ turn.drop((i) => !actionable(mesh, i));
+ const origin = turn.start();
+ if (!origin) {
+ setMeshStatus("idle");
+ return;
+ }
+ pending = true;
+ renderer.flushTrailing();
+ writeRpc(child, { type: "prompt", message: framed(origin) });
+ }
+
+ function foldSameScope(): void {
+ if (!turn.origin || pending) return;
+ for (const item of turn.extend((i, o) => actionable(mesh, i) && scopeKey(i) === scopeKey(o))) {
+ void renderer; // mark touched
+ writeRpc(child, { type: "steer", message: framed(item) });
+ }
+ }
+
+ // ----- child stderr → transcript (line 6 of slice 3 design) -----
+ child.stderr?.on("data", (d: Buffer) => {
+ stderrBuf += d.toString();
+ });
+
+ // ----- child exit → renderer.stop() + parent exits loud with child code -----
+ child.on("exit", (code) => {
+ if (stderrBuf) renderer.pushError(`child stderr: ${stderrBuf}`);
+ renderer.pushError(`child exit ${code}`);
+ cleanupPersona();
+ renderer.stop();
+ process.exit(code ?? 1);
+ });
+
+ // ----- Esc while streaming → writeRpc {type:"abort"} -----
+ renderer.onAbort(() => {
+ writeRpc(child, { type: "abort" });
+ });
+
+ // ----- mesh → pump or foldSameScope -----
+ mesh.on("incoming", () => {
+ if (turn.inFlight) foldSameScope();
+ else pump();
+ });
+ mesh.on("wake", () => {
+ if (!turn.inFlight) pump();
+ });
+
+ // ----- the frame loop -----
+ void (async () => {
+ try {
+ for await (const frame of readJsonLines(child.stdout)) {
+ const t = (frame as { type?: string }).type;
+ switch (t) {
+ // ---- preflight ack ----
+ case "response": {
+ const r = frame as { command?: string; success?: boolean; error?: string };
+ if (r.command === "prompt") {
+ if (r.success === true) {
+ pending = false;
+ // streaming will start when agent_start arrives
+ } else {
+ // Prompt preflight failed. This is NOT the no-model / no-key
+ // startup case: pi resolves --model only to providers with
+ // configured auth and falls back to a scoped model otherwise, and
+ // zero configured models makes pi exit(1) before rpc mode starts.
+ // The reachable trigger is an OAuth credential that expires
+ // MID-SESSION (short-lived-token providers like GitHub Copilot —
+ // the reason pi's getApiKey callback exists): the next prompt throws
+ // "authentication failed" here. Abandon so JetStream redelivers the
+ // message once auth is restored, surface a visible misconfigured
+ // status, and halt the pump so we don't burn the inbox retrying.
+ pending = false;
+ turn.abandon();
+ setMeshStatus("waiting", "misconfigured: preflight failed; inbox will redeliver");
+ halt = true;
+ renderer.pushError(`preflight failed: ${r.error ?? "(no error message)"}`);
+ }
+ }
+ // other command acks (steer, abort, ...) are silent.
+ break;
+ }
+
+ // ---- agent activity (AgentEvent union: agent_start/end, turn_start/end,
+ // message_start/update/end, tool_execution_start/update/end) ----
+ case "agent_start": {
+ renderer._setStreaming?.(true);
+ setMeshStatus("working", "thinking");
+ foldSameScope(); // flush same-scope peers that landed before streaming began
+ break;
+ }
+ case "turn_start": {
+ // A new turn. Set streaming here too — some providers emit turn_start
+ // without a preceding agent_start, so relying on agent_start alone
+ // would leave streaming=false and Esc-while-streaming wouldn't abort.
+ renderer._setStreaming?.(true);
+ break;
+ }
+ case "turn_end": {
+ // Turn finished; agent_end (with messages + willRetry) follows and is the
+ // commit point. No-op here.
+ break;
+ }
+ case "message_start": {
+ // A message (user/assistant/toolResult) begins. The streamed text lands
+ // via message_update.text_delta; the final assistant text also appears
+ // in message_end + agent_end.messages. No-op.
+ break;
+ }
+ case "message_end": {
+ // Message complete. No-op (text was streamed via message_update; the
+ // turn's final text is committed from agent_end.messages).
+ break;
+ }
+ case "message_update": {
+ const ev = frame as {
+ assistantMessageEvent?: { type?: string; delta?: string };
+ };
+ const am = ev.assistantMessageEvent;
+ if (am?.type === "text_delta" && typeof am.delta === "string") {
+ renderer.pushAssistantText(am.delta);
+ }
+ break;
+ }
+ case "tool_execution_start": {
+ const ev = frame as { toolName?: string; args?: Record };
+ const name = ev.toolName ?? "tool";
+ const args = ev.args ?? {};
+ const pathLike =
+ (typeof args["path"] === "string" ? args["path"] as string : undefined) ??
+ (typeof args["file_path"] === "string" ? args["file_path"] as string : undefined);
+ renderer.pushToolEvent(name, pathLike);
+ setMeshStatus("working", `running ${name}`);
+ break;
+ }
+ case "tool_execution_update": {
+ // Partial tool result streaming (e.g. long bash output). No-op — the
+ // tool_execution_end line + status reset is enough for the pane.
+ break;
+ }
+ case "tool_execution_end": {
+ setMeshStatus("working", "thinking");
+ break;
+ }
+ // ---- session metadata (AgentSessionEvent extensions) — no-op, do not
+ // spam the pane. These are session lifecycle signals, not turn data. ----
+ case "queue_update":
+ case "compaction_start":
+ case "compaction_end":
+ case "session_info_changed":
+ case "thinking_level_changed":
+ case "auto_retry_start":
+ case "auto_retry_end":
+ break;
+
+ // ---- extension UI ----
+ case "extension_ui_request": {
+ const req = frame as {
+ id: string;
+ method: string;
+ title?: string;
+ message?: string;
+ options?: string[];
+ placeholder?: string;
+ prefill?: string;
+ timeout?: number;
+ statusKey?: string;
+ statusText?: string | undefined;
+ widgetKey?: string;
+ widgetLines?: string[] | undefined;
+ widgetPlacement?: "aboveEditor" | "belowEditor";
+ text?: string;
+ };
+ switch (req.method) {
+ case "confirm": {
+ const ok = await renderer.popConfirm(req.title ?? "Confirm", req.message ?? "", { timeout: req.timeout });
+ writeRpc(child, { type: "extension_ui_response", id: req.id, confirmed: ok });
+ break;
+ }
+ case "select": {
+ const v = await renderer.popSelect(req.title ?? "Select", req.options ?? [], { timeout: req.timeout });
+ writeRpc(child, { type: "extension_ui_response", id: req.id, value: v });
+ break;
+ }
+ case "input": {
+ const v = await renderer.popInput(req.title ?? "Input", req.placeholder, { timeout: req.timeout });
+ writeRpc(child, { type: "extension_ui_response", id: req.id, value: v });
+ break;
+ }
+ case "editor": {
+ const v = await renderer.popEditor(req.title ?? "Editor", req.prefill, { timeout: req.timeout });
+ // popEditor returns "" on cancel/timeout, the post-edit string otherwise.
+ // Extension API (per per-edit-approval.ts) treats undefined as cancel
+ // and string as content — but we never send undefined; we send the
+ // content (or "" on cancel). The rpc-side handler (rpc-mode.js) maps
+ // any non-cancelled response to the string value, so passing ""
+ // resolves to "" on the extension end (which the extension then
+ // can treat as "empty/cancelled" by convention).
+ writeRpc(child, { type: "extension_ui_response", id: req.id, value: v });
+ break;
+ }
+ case "notify": {
+ // Fire-and-forget. Render in pane.
+ renderer.pushError(`[notify] ${req.message ?? ""}`.trimEnd());
+ break;
+ }
+ case "setStatus": {
+ renderer.setStatus("working", `${req.statusKey}: ${req.statusText ?? ""}`);
+ break;
+ }
+ case "setWidget":
+ case "setTitle":
+ case "set_editor_text":
+ // Render-in-place; no response.
+ renderer.pushError(`[extension ui: ${req.method}] (render-only)`);
+ break;
+ default:
+ renderer.pushError(`[extension ui: unknown method ${req.method}]`);
+ }
+ break;
+ }
+
+ case "extension_error": {
+ const err = frame as { error?: string; event?: string; extensionPath?: string };
+ renderer.pushError(`extension error (${err.event ?? ""}): ${err.error ?? ""}`);
+ // do not crash; keep processing.
+ break;
+ }
+
+ // ---- turn completion ----
+ case "agent_end": {
+ const ev = frame as { willRetry?: boolean; messages?: readonly unknown[] };
+ if (ev.willRetry) {
+ // Auto-retry follows; do NOT commit, do NOT pump.
+ break;
+ }
+ // Clean finish (or failed-but-terminal — both consume the turn).
+ renderer._setStreaming?.(false);
+ renderer.flushTrailing();
+ const to = turn.origin;
+ const reply = turnReplyText(ev.messages ?? []);
+ turn.commit(); // SOLE ack site
+ if (to && reply) deliver(to, reply);
+ setMeshStatus("idle");
+ pump();
+ break;
+ }
+
+ default:
+ renderer.pushError(`[unknown frame: ${JSON.stringify(frame)}]`);
+ }
+ }
+ } catch (e) {
+ renderer.pushError(`bridge read error: ${(e as Error).message}`);
+ renderer.stop();
+ process.exit(1);
+ }
+ })();
+
+ // ----- crash safety: SIGINT / SIGTERM → renderer.stop() + child.kill + mesh.stop + exit 0 -----
+ let shuttingDown = false;
+ const shutdown = async (): Promise => {
+ if (shuttingDown) return;
+ shuttingDown = true;
+ renderer.stop();
+ cleanupPersona();
+ try { child.kill("SIGTERM"); } catch { /* */ }
+ try { await mesh.stop(); } catch { /* */ }
+ process.exit(0);
+ };
+ process.on("SIGINT", () => void shutdown());
+ process.on("SIGTERM", () => void shutdown());
+ process.on("exit", () => { cleanupPersona(); renderer.stop(); });
+
+ // Drain anything already buffered before the listeners were attached.
+ pump();
+
+ // Keep alive.
+ await new Promise(() => {});
+}
diff --git a/extensions/pi/src/tui-render.ts b/extensions/pi/src/tui-render.ts
new file mode 100644
index 00000000..eaba88ac
--- /dev/null
+++ b/extensions/pi/src/tui-render.ts
@@ -0,0 +1,398 @@
+import { spawn as childSpawn } from "node:child_process";
+import { promises as fsp, mkdtempSync, type Dirent } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import * as readline from "node:readline";
+
+/** Why this file exists: the pi connector's tui mode needs a renderer that
+ * depends only on Node builtins so it can ride on top of any pi release that
+ * keeps the rpc wire stable. No @earendil-works/pi-tui dep. No terminal-
+ * screen takeover — line-oriented, so a terminal crash leaves the pane
+ * cookable and avoids the restore-rawmode death-spiral that full-screen TUIs
+ * die in.
+ *
+ * Dialog block semantics: only one dialog at a time. While a dialog is open,
+ * the renderer ignores subsequent `popXxx` calls (returns a never-resolving
+ * promise) — rpc-mode's extensions block on a Promise per dialog, so child
+ * emits are inherently serial, but in case a fire-and-forget method arrives
+ * mid-dialog, render-in-place (pushError / setStatus) doesn't disturb the
+ * active dialog. */
+
+export interface TuiRenderer {
+ start(): void;
+ stop(): void;
+ pushAssistantText(delta: string): void;
+ flushTrailing(): void;
+ pushToolEvent(toolName: string, pathLike?: string): void;
+ pushError(line: string): void;
+ setStatus(status: string, activity?: string): void;
+ popConfirm(title: string, message: string, opts?: { timeout?: number }): Promise;
+ popSelect(title: string, options: string[], opts?: { timeout?: number }): Promise;
+ popInput(title: string, placeholder?: string, opts?: { timeout?: number }): Promise;
+ popEditor(title: string, prefill?: string, opts?: { timeout?: number; editorOverride?: string }): Promise;
+ onAbort(cb: () => void): void;
+}
+
+/** Options for the renderer. `editor` lets the platform override $EDITOR; the
+ * default picks $VISUAL then $EDITOR then `vi`. */
+export interface TuiRendererOptions {
+ editor?: string;
+ out?: NodeJS.WritableStream;
+ err?: NodeJS.WritableStream;
+}
+
+export function createTuiRenderer(opts: TuiRendererOptions = {}): TuiRenderer {
+ const out = opts.out ?? process.stdout;
+ const err = opts.err ?? process.stderr;
+
+ // Raw-mode + keypress state. `keyListener` is the readline-attached keypress
+ // handler; we keep a reference so stop() can detach.
+ let raw = false;
+ let keyListener: ((chunk: string, key: readline.Key) => void) | null = null;
+ let stopped = false; // stop() is called from multiple shutdown paths; guard the offline line
+
+ // Abort-on-Esc callback: only fires OUTSIDE a dialog while STREAMING. Client
+ // sets this; renderer fires it; client sends {type:"abort"} to the child.
+ let onAbortCb: (() => void) | null = null;
+
+ // Dialog state. While `dialogActive` is true, keypresses route to the
+ // dialog's resolver; outside, plain Esc while STREAMING triggers onAbortCb.
+ // STREAMING is owned by the client, which calls renderer.markStreaming(true/false).
+ let streaming = false;
+ let dialogActive = false;
+
+ // Line buffer for pushAssistantText — accumulates partial lines, writes
+ // whole lines so the terminal isn't re-painted mid-word.
+ let trailing = "";
+
+ function writeLine(line: string): void {
+ out.write(line.endsWith("\n") ? line : line + "\n");
+ }
+
+ function writeRaw(s: string): void {
+ out.write(s);
+ }
+
+ function emitError(line: string): void {
+ err.write(`[pi-peer] ! ${line}\n`);
+ }
+
+ function setRaw(on: boolean): void {
+ if (on === raw) return;
+ if (!process.stdin.isTTY) {
+ // No TTY (e.g. piped CI): the renderer degrades — dialogs cannot be
+ // answered interactively and auto-cancel. Throwing would be louder but
+ // would break scripted use; degrading with a visible banner is friendlier.
+ raw = false;
+ return;
+ }
+ process.stdin.setRawMode(on);
+ raw = on;
+ }
+
+ function attachKeypress(): void {
+ if (keyListener) return;
+ readline.emitKeypressEvents(process.stdin);
+ keyListener = (_chunk, key) => onKey(key);
+ process.stdin.on("keypress", keyListener);
+ }
+
+ function detachKeypress(): void {
+ if (!keyListener) return;
+ process.stdin.off("keypress", keyListener);
+ keyListener = null;
+ }
+
+ function onKey(key: readline.Key): void {
+ // Plain Esc: in-dialog cancels (resolver handles); OUT-of-dialog while
+ // STREAMING triggers abort; otherwise ignore (idle Esc is a no-op).
+ if (key.name === "escape") {
+ if (dialogActive) {
+ // dialog resolver handles this — they listen via the raw stream too.
+ } else if (streaming) {
+ const cb = onAbortCb;
+ if (cb) cb();
+ }
+ return;
+ }
+ if (key.ctrl && key.name === "c") {
+ // SIGINT equivalent: tear the renderer down and let the client's handler exit.
+ // Don't call process.exit here — the client owns lifecycle.
+ emitError("Ctrl-C in pane (renderer will close)");
+ // No-op for now; client decides. We surface the keypress.
+ }
+ // Other keys are routed to the active dialog's own listener (via raw stdin)
+ // — none here at the renderer level.
+ }
+
+ // restore on every exit / signal path.
+ function restore(): void {
+ detachKeypress();
+ if (raw) {
+ try { setRaw(false); } catch { /* terminal may already be gone */ }
+ }
+ }
+
+ // Lifecycle: register once at start(); stop() unregisters (idempotent).
+ let installed = false;
+ function installLifecycle(): void {
+ if (installed) return;
+ installed = true;
+ process.on("exit", restore);
+ process.on("SIGINT", restore);
+ process.on("SIGTERM", restore);
+ }
+
+ // ----- dialog helpers -----
+
+ /** Run an interactive dialog: turn on raw mode, push the prompt to the
+ * pane, attach a one-shot keypress handler that resolves the promise.
+ * `parse(key)` returns {done, value?}; done=true resolves the dialog. */
+ function runDialog(
+ title: string,
+ body: () => string,
+ parse: (key: readline.Key) => { done: true; value: T } | { done: false },
+ defaultValue: T,
+ timeout?: number,
+ ): Promise {
+ installLifecycle();
+ setRaw(true);
+ dialogActive = true;
+ writeLine(`\n┌─ ${title}\n${body()}\n└─`);
+ writeRaw("> ");
+ return new Promise((resolve) => {
+ let settled = false;
+ const settle = (v: T): void => {
+ if (settled) return;
+ settled = true;
+ dialogActive = false;
+ writeRaw("\n");
+ try { setRaw(false); } catch { /* terminal may already be gone */ }
+ process.stdin.off("keypress", dialogListener);
+ resolve(v);
+ };
+ const dialogListener = (_chunk: string, key: readline.Key): void => {
+ // Esc cancels — confirm→false, select/input/editor→"" (matches rpc-side
+ // `createDialogPromise(opts, defaultValue, ...)` resolve-on-timeout).
+ if (key.name === "escape") { settle(defaultValue); return; }
+ if (key.ctrl && key.name === "c") { settle(defaultValue); return; }
+ const r = parse(key);
+ if (r.done) settle(r.value);
+ };
+ process.stdin.on("keypress", dialogListener);
+ if (timeout !== undefined) {
+ setTimeout(() => settle(defaultValue), timeout);
+ }
+ });
+ }
+
+ // ----- public API -----
+
+ return {
+ start() {
+ installLifecycle();
+ attachKeypress();
+ // Keep raw mode ON for the renderer's lifetime so Esc (and other keystrokes)
+ // are captured by onKey, not eaten by the cooked-mode line discipline. The
+ // renderer owns the pane's input surface; dialogs toggle raw mode redundantly
+ // (idempotent). Ctrl-C in raw mode arrives as a keypress, not SIGINT — the
+ // supervisor's `cotal stop` is the operator's exit path for a managed peer.
+ setRaw(true);
+ writeLine("[pi-peer] renderer online");
+ },
+ stop() {
+ // Idempotent: unregisters key listener and exits raw mode. Safe to call
+ // from process.on("exit") / SIGINT / SIGTERM / finally. Emits a visible
+ // offline line so the operator sees a clean shutdown (and so the shutdown
+ // path is observable in tests).
+ if (!stopped) { writeLine("[pi-peer] renderer offline"); stopped = true; }
+ restore();
+ },
+ setStatus(_status, activity) {
+ // Render the status as a brief annotation line at the bottom of the pane
+ // (no flicker on repaint). Activity null/undefined clears it.
+ if (activity === undefined) writeRaw("\n\u001b[2m[working]\u001b[0m\n");
+ else writeRaw(`\n\u001b[2m[working: ${activity}]\u001b[0m\n`);
+ },
+ markStreaming(_on: boolean): void {
+ // The renderer's "streaming" state is set via attachStreaming() so that
+ // the keypress handler can route Esc to onAbortCb while STREAMING.
+ // Exposed on the returned object to keep the public surface narrow.
+ },
+ // Internal hooks — exposed via private fields on the returned object below.
+ _streaming: (on: boolean) => { streaming = on; },
+ pushAssistantText(delta) {
+ // Buffered line rendering: accumulate until a full line, then write.
+ // flushTrailing() is called by the client on agent_end to handle a final
+ // partial line.
+ const i = trailing.length ? trailing + delta : delta;
+ const parts = i.split("\n");
+ trailing = parts.pop() ?? "";
+ for (const line of parts) writeLine(line);
+ },
+ flushTrailing() {
+ if (trailing) {
+ writeLine(trailing);
+ trailing = "";
+ }
+ },
+ pushToolEvent(toolName, pathLike) {
+ const target = pathLike ? ` ${pathLike}` : "";
+ writeLine(`▸ ${toolName}${target}`);
+ },
+ pushError(line) {
+ emitError(line);
+ },
+ onAbort(cb) {
+ onAbortCb = cb;
+ },
+ async popConfirm(title, message, opts) {
+ // y/Enter → true; n/Esc/Ctrl-C → false.
+ const defaultV: boolean = false;
+ return runDialog(
+ title,
+ () => `${message}\n(y/n, Esc to cancel)`,
+ (key) => {
+ if (key.name === "return" || key.name === "y") return { done: true, value: true };
+ if (key.name === "n") return { done: true, value: false };
+ return { done: false };
+ },
+ defaultV,
+ opts?.timeout,
+ );
+ },
+ async popSelect(title, options, opts) {
+ // arrow keys: up/down move; enter commits; esc cancels. Default value
+ // "" matches rpc-side `"value" in r ? r.value : undefined` for select.
+ let idx = 0;
+ const render = (): void => {
+ writeRaw("\u001b[2J\u001b[H"); // clear screen, home
+ writeLine(`┌─ ${title} (↑/↓, Enter, Esc to cancel)`);
+ options.forEach((o, i) => writeLine(` ${i === idx ? "▶" : " "} ${i + 1}. ${o}`));
+ writeRaw("└─");
+ };
+ const defaultV: string = ""; // undefined if absolute blank
+ return new Promise((resolve) => {
+ installLifecycle();
+ setRaw(true);
+ dialogActive = true;
+ render();
+ let settled = false;
+ const settle = (v: string): void => {
+ if (settled) return;
+ settled = true;
+ dialogActive = false;
+ writeRaw("\n");
+ try { setRaw(false); } catch { /* */ }
+ process.stdin.off("keypress", dialogListener);
+ resolve(v);
+ };
+ const dialogListener = (_c: string, key: readline.Key): void => {
+ if (key.name === "escape") { settle(defaultV); return; }
+ if (key.ctrl && key.name === "c") { settle(defaultV); return; }
+ if (key.name === "up") { idx = (idx - 1 + options.length) % options.length; render(); return; }
+ if (key.name === "down") { idx = (idx + 1) % options.length; render(); return; }
+ if (key.name === "return") { settle(options[idx] ?? defaultV); return; }
+ };
+ process.stdin.on("keypress", dialogListener);
+ if (opts?.timeout !== undefined) setTimeout(() => settle(defaultV), opts.timeout);
+ });
+ },
+ async popInput(title, placeholder, opts) {
+ // Raw-mode single-line input so Esc cancels cleanly without readline eating it.
+ // Handles backspace editing + echo (the cooked tty would do echo; raw doesn't, so we
+ // echo printable chars ourselves, render backspace as `\b \b`).
+ const defaultV = ""; // matches rpc-side `"value" in r ? r.value : undefined` for input
+ installLifecycle();
+ setRaw(true);
+ dialogActive = true;
+ writeLine(`\n┌─ ${title}${placeholder ? ` (${placeholder})` : ""}`);
+ writeRaw("└─ > ");
+ let buf = "";
+ const echo = (s: string): void => {
+ writeRaw(s.replace(/\r/g, "").replace(/\u0007/g, ""));
+ };
+ return new Promise((resolve) => {
+ let settled = false;
+ const settle = (v: string): void => {
+ if (settled) return;
+ settled = true;
+ dialogActive = false;
+ writeRaw("\n");
+ try { setRaw(false); } catch { /* */ }
+ process.stdin.off("keypress", dialogListener);
+ resolve(v);
+ };
+ const dialogListener = (_c: string, key: readline.Key): void => {
+ if (key.name === "escape") { settle(defaultV); return; }
+ if (key.ctrl && key.name === "c") { settle(defaultV); return; }
+ if (key.name === "return") { writeRaw("\n"); settle(buf); return; }
+ if (key.name === "backspace") {
+ if (buf.length) {
+ buf = buf.slice(0, -1);
+ writeRaw("\b \b");
+ }
+ return;
+ }
+ // Printable: append to buffer + echo.
+ if (!key.ctrl && key.sequence && key.sequence.length === 1) {
+ buf += key.sequence;
+ echo(key.sequence);
+ }
+ };
+ process.stdin.on("keypress", dialogListener);
+ if (opts?.timeout !== undefined) setTimeout(() => settle(defaultV), opts.timeout);
+ });
+ },
+ async popEditor(title, prefill, opts) {
+ // Spawn $EDITOR on a tempfile; on save → return content; on :q/non-zero
+ // exit or Esc → empty string. The tempfile is unlinked on every exit path.
+ const editor = opts?.editorOverride
+ ?? process.env.VISUAL
+ ?? process.env.EDITOR
+ ?? "vi";
+ const tmpDir = mkdtempSync(join(tmpdir(), "pi-edit-"));
+ const file = join(tmpDir, "edit.md");
+ if (prefill) await fsp.writeFile(file, prefill, "utf8");
+ else await fsp.writeFile(file, "", "utf8");
+
+ installLifecycle();
+ // Editor runs in normal tty mode (not raw) so vi/nano/etc. work.
+ dialogActive = true;
+ writeLine(`┌─ ${title} (external editor: ${editor})`);
+
+ const defaultV = ""; // matches rpc-side editor resolved value
+ const result = await new Promise((resolve) => {
+ const proc = childSpawn(editor, [file], {
+ stdio: "inherit",
+ env: process.env,
+ });
+ const settle = (v: string): void => {
+ dialogActive = false;
+ resolve(v);
+ };
+ proc.on("exit", async (code) => {
+ if (code !== 0) { settle(defaultV); return; }
+ try {
+ const content = await fsp.readFile(file, "utf8");
+ settle(content);
+ } catch {
+ settle(defaultV);
+ }
+ });
+ if (opts?.timeout !== undefined) {
+ setTimeout(() => { try { proc.kill("SIGTERM"); } catch { /* */ } settle(defaultV); }, opts.timeout);
+ }
+ });
+ try { await fsp.rm(tmpDir, { recursive: true, force: true }); } catch { /* */ }
+ return result;
+ },
+ // Force-render a status set by client (test path).
+ _setStreaming(on: boolean): void { streaming = on; },
+ } as TuiRenderer & { _setStreaming: (on: boolean) => void };
+}
+
+/** Note: we mark the returned object with `_setStreaming` so the client can
+ * transition the streaming flag on agent_start / agent_end. The TypeScript
+ * intersection isn't visible externally but the runtime hook is there.
+ * (Avoiding an `as any` cast at call sites that need to drive Esc routing.) */
diff --git a/extensions/pi/tsconfig.json b/extensions/pi/tsconfig.json
new file mode 100644
index 00000000..051d08e2
--- /dev/null
+++ b/extensions/pi/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist"
+ },
+ "include": ["src"]
+}
diff --git a/package.json b/package.json
index 75eef978..d91f0c7f 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"smoke": "tsx packages/core/smoke.ts",
"smoke:auth": "tsx packages/core/smoke-auth.ts",
"smoke:orientation": "tsx extensions/connector-core/smoke/orientation.smoke.ts",
+ "smoke:inbox": "tsx extensions/connector-core/inbox-turn.smoke.ts",
"smoke:view": "tsx implementations/cli/smoke/view.smoke.ts",
"smoke:members": "tsx packages/core/smoke/members.smoke.ts",
"smoke:plane3:auth": "tsx packages/core/smoke/plane3-auth.smoke.ts",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 70f74e8c..4936ce80 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -117,6 +117,22 @@ importers:
specifier: ^4.22.4
version: 4.22.4
+ examples/03-pi:
+ dependencies:
+ '@cotal-ai/core':
+ specifier: workspace:*
+ version: link:../../packages/core
+ '@cotal-ai/manager':
+ specifier: workspace:*
+ version: link:../../implementations/manager
+ '@cotal-ai/pi':
+ specifier: workspace:*
+ version: link:../../extensions/pi
+ devDependencies:
+ tsx:
+ specifier: ^4.22.4
+ version: 4.22.4
+
extensions/cmux:
dependencies:
'@cotal-ai/core':
@@ -200,6 +216,25 @@ importers:
specifier: ^4.22.4
version: 4.22.4
+ extensions/pi:
+ dependencies:
+ '@cotal-ai/connector-core':
+ specifier: workspace:*
+ version: link:../connector-core
+ '@earendil-works/pi-ai':
+ specifier: ^0.79.0
+ version: 0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)
+ '@earendil-works/pi-coding-agent':
+ specifier: ^0.79.0
+ version: 0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)
+ tsx:
+ specifier: ^4.22.4
+ version: 4.22.4
+ devDependencies:
+ '@cotal-ai/core':
+ specifier: workspace:*
+ version: link:../../packages/core
+
extensions/tmux:
dependencies:
'@cotal-ai/core':
@@ -329,6 +364,116 @@ packages:
resolution: {integrity: sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==}
engines: {node: '>=18'}
+ '@anthropic-ai/sdk@0.91.1':
+ resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==}
+ hasBin: true
+ peerDependencies:
+ zod: ^3.25.0 || ^4.0.0
+ peerDependenciesMeta:
+ zod:
+ optional: true
+
+ '@aws-crypto/crc32@5.2.0':
+ resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
+ engines: {node: '>=16.0.0'}
+
+ '@aws-crypto/sha256-browser@5.2.0':
+ resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==}
+
+ '@aws-crypto/sha256-js@5.2.0':
+ resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==}
+ engines: {node: '>=16.0.0'}
+
+ '@aws-crypto/supports-web-crypto@5.2.0':
+ resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==}
+
+ '@aws-crypto/util@5.2.0':
+ resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
+
+ '@aws-sdk/client-bedrock-runtime@3.1048.0':
+ resolution: {integrity: sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/core@3.974.23':
+ resolution: {integrity: sha512-MiWR/uWjxjFXGzrE0Ghc5lWxUxzHsUWFhV+OX7M4cR9SrmrnZs6TXavnCWnzzdwJeFri34xQo81rvGNzK3c4BQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-env@3.972.49':
+ resolution: {integrity: sha512-liB3yQNHCM9k/gu/w36XHMKPluT7HTlnGUhRbBGSISDQkcr/Sy1zsZabiuvQj8WG5yW573u9RehrBvvnIQ9OEQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-http@3.972.51':
+ resolution: {integrity: sha512-XET0H2oofciJ5lMRWNIvRjAP7Q3wv2XT+JtJJEdhPWUMwe3TvQ9qcxonpu7vXmNngncvFpi4E2It+Tamas/naA==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-ini@3.972.56':
+ resolution: {integrity: sha512-IAmc61hbgQiHht9U3x0tnRwz0lzdwOwD/i9voRgdJrKamF+JtmrBOsW9GwB7mfFonNWOWL4qARWYrF8veEMe3w==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-login@3.972.55':
+ resolution: {integrity: sha512-hBBkANo3cDn+h2qxxzER4a+J8JCO9o9Z/YYmU7iky6AcaarX5RRdRcHNC6SLdwY0vAXQygn6soUbDqPn3GghaA==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-node@3.972.58':
+ resolution: {integrity: sha512-OyCLVmSI7pZO8hxwNVX6pXhTVlJqRBTp+ijdEfJSUj0RyjHnF602OfAarOzGq6wkGodeFkYBt8MmJ6A6ycRgWw==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-process@3.972.49':
+ resolution: {integrity: sha512-C8h36lBuC/RnBSsjlO+dn6xZm3KbAl5vpJaVPAfQnMmz2/OISmKOc8XZcqMQgO2ADwBYNRMM6Kf3vz9G/TulMQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-sso@3.972.55':
+ resolution: {integrity: sha512-1FkOz74Ea5QGS9jtIoXp55T/IkSS3spv+nLTT07fRY/+T5xmEOqaYBVIaEmX4zTNvbV6g2lrtlaVKWEoNyJt3w==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/credential-provider-web-identity@3.972.55':
+ resolution: {integrity: sha512-g2BoECD1q01kTPByi56+VLVvdWDzMkKIcr77qixpqH0okw2t0U5CoPv+6S8v/D1Y2Wa6QKKtn6XAtDzP+Kfpvg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/eventstream-handler-node@3.972.22':
+ resolution: {integrity: sha512-tqPJv0dz4+O0hWGm1a6YekcMZyPhDFs/zH73Von7icaVT5n0Jqvm86typ3jRrG+qoUdPhALOnboRLTmnWQTlYQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-eventstream@3.972.18':
+ resolution: {integrity: sha512-OHpk8YoZi3yexPq8aFt1vN1IxA2zLKvsIR5GpWYylX/ve6kQmY7wxHNSFy/D3t2apMZ16rs76Co4dJWcDyIk3A==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/middleware-websocket@3.972.31':
+ resolution: {integrity: sha512-ps1rumU1LybSFHaW9dTDgkhCMJLVaedEY78kKSzUDDY+b9974/g6aiaYYA0U9WV0oL4CJCJrVWG+EZ/qr4or7g==}
+ engines: {node: '>= 14.0.0'}
+
+ '@aws-sdk/nested-clients@3.997.23':
+ resolution: {integrity: sha512-gO93ZPsI2bxeFZD42f1/qjDw6FAZkNZcKRO94LIiT03fzOmcJ9e/tunxjVjA1Rl69ClmVJzz8H3G9CdKef10PA==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/signature-v4-multi-region@3.996.35':
+ resolution: {integrity: sha512-6L/VWs+Wch2stHemCGTmUNqKLMzURxQDK5boNG3Jn3kAOp71meDUuS5sbObpEvFxHDq0uWeSLFDNSYsjNt+Dlg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/token-providers@3.1048.0':
+ resolution: {integrity: sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/token-providers@3.1074.0':
+ resolution: {integrity: sha512-pv80IzgGW4RnXWtft692chZOM9i6PhebVsLCcnaM4dBEPZva2fE6FXAHs76G7Rc7s3yGyX/68G0nZMrUy+Vmpg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/types@3.973.13':
+ resolution: {integrity: sha512-pEHZqRkAlHfnfAU9tK+WpKv/gBNjGJrHMgA3A0iYRGyswBS2t0pfez+lWlwktb3Bqa0ovh7w/QJTFwp3fDxLNg==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/util-locate-window@3.965.8':
+ resolution: {integrity: sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws-sdk/xml-builder@3.972.31':
+ resolution: {integrity: sha512-SzE4Pgyl+hDF+BuyuzxUSpwnuUu9lJuO1YGgteG89/4Qv0+2IQiVQqdbPV32IozLvXWQChPQcdkk/sKvb1QHiQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@aws/lambda-invoke-store@0.2.4':
+ resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==}
+ engines: {node: '>=18.0.0'}
+
'@babel/runtime@7.29.7':
resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
engines: {node: '>=6.9.0'}
@@ -396,6 +541,24 @@ packages:
resolution: {integrity: sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw==}
engines: {node: '>= 20.12.0'}
+ '@earendil-works/pi-agent-core@0.79.10':
+ resolution: {integrity: sha512-XKxgdjhcPuyjrthCOFSgfzT3xZ1uBrJ1IMVDxci1to6hIN6BIg9J5iY8q0pGXK1DLgATLP23da+1UyZLwA360Q==}
+ engines: {node: '>=22.19.0'}
+
+ '@earendil-works/pi-ai@0.79.10':
+ resolution: {integrity: sha512-9jR23tOl0BIUdQMn70Gr72xYBpM7Xgl9Lyv7gAnU1USfkNRuYG/f/edLl+n/Dp/RafDW3JI4DF7y/GhgkORuew==}
+ engines: {node: '>=22.19.0'}
+ hasBin: true
+
+ '@earendil-works/pi-coding-agent@0.79.10':
+ resolution: {integrity: sha512-YxaRhmgyDTvLDdGVbe7YzTHV80oL5mX5odg6EhGHz3w5Wu1Ix8DCw7bhtiOBLGQNFRcknia0zPmVWIj30XP1EA==}
+ engines: {node: '>=22.19.0'}
+ hasBin: true
+
+ '@earendil-works/pi-tui@0.79.10':
+ resolution: {integrity: sha512-FUVOjDn1DVwM1uHD5MNYboXQrXjIDbSt+BQ3py7nQWCY62tKfxgiM1OBMxTcwRWLfSdZHUPpV0hm1loIdUJnPw==}
+ engines: {node: '>=22.19.0'}
+
'@eplightning/nats-server-darwin-arm64@2.14.0':
resolution: {integrity: sha512-tBCOf4anrlTnbQimh3KSz6C+0IbdJTyfTFLcol2YfGWzfmMgsXqjfrSZvnRnqMK2hWD/TJ6eIhsv3/UlfdTzEg==}
engines: {bun: '>=1', deno: '>=2', node: '>=22'}
@@ -744,6 +907,15 @@ packages:
cpu: [x64]
os: [win32]
+ '@google/genai@1.52.0':
+ resolution: {integrity: sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@modelcontextprotocol/sdk': ^1.25.2
+ peerDependenciesMeta:
+ '@modelcontextprotocol/sdk':
+ optional: true
+
'@hono/node-server@1.19.14':
resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==}
engines: {node: '>=18.14.1'}
@@ -798,6 +970,82 @@ packages:
'@manypkg/get-packages@1.1.3':
resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==}
+ '@mariozechner/clipboard-darwin-arm64@0.3.9':
+ resolution: {integrity: sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@mariozechner/clipboard-darwin-universal@0.3.9':
+ resolution: {integrity: sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==}
+ engines: {node: '>= 10'}
+ os: [darwin]
+
+ '@mariozechner/clipboard-darwin-x64@0.3.9':
+ resolution: {integrity: sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@mariozechner/clipboard-linux-arm64-gnu@0.3.9':
+ resolution: {integrity: sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@mariozechner/clipboard-linux-arm64-musl@0.3.9':
+ resolution: {integrity: sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@mariozechner/clipboard-linux-riscv64-gnu@0.3.9':
+ resolution: {integrity: sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==}
+ engines: {node: '>= 10'}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@mariozechner/clipboard-linux-x64-gnu@0.3.9':
+ resolution: {integrity: sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@mariozechner/clipboard-linux-x64-musl@0.3.9':
+ resolution: {integrity: sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@mariozechner/clipboard-win32-arm64-msvc@0.3.9':
+ resolution: {integrity: sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@mariozechner/clipboard-win32-x64-msvc@0.3.9':
+ resolution: {integrity: sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@mariozechner/clipboard@0.3.9':
+ resolution: {integrity: sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==}
+ engines: {node: '>= 10'}
+
+ '@mistralai/mistralai@2.2.6':
+ resolution: {integrity: sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==}
+ peerDependencies:
+ '@opentelemetry/api': ^1.9.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+
'@modelcontextprotocol/sdk@1.29.0':
resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
engines: {node: '>=18'}
@@ -895,6 +1143,84 @@ packages:
'@opencode-ai/sdk@1.16.2':
resolution: {integrity: sha512-Z/xZ7q79dYeE0afqIk/yFEcRNGEQFcE+H8ssYivUiy+xGZ1mGwT72jpaQZKBwPn3JH4sRCu4KA2lcktBQfcOjg==}
+ '@opentelemetry/api@1.9.0':
+ resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
+ engines: {node: '>=8.0.0'}
+
+ '@opentelemetry/semantic-conventions@1.41.1':
+ resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==}
+ engines: {node: '>=14'}
+
+ '@protobufjs/aspromise@1.1.2':
+ resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
+
+ '@protobufjs/base64@1.1.2':
+ resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
+
+ '@protobufjs/codegen@2.0.5':
+ resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==}
+
+ '@protobufjs/eventemitter@1.1.1':
+ resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==}
+
+ '@protobufjs/fetch@1.1.1':
+ resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==}
+
+ '@protobufjs/float@1.0.2':
+ resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
+
+ '@protobufjs/path@1.1.2':
+ resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
+
+ '@protobufjs/pool@1.1.0':
+ resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
+
+ '@protobufjs/utf8@1.1.1':
+ resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==}
+
+ '@silvia-odwyer/photon-node@0.3.4':
+ resolution: {integrity: sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==}
+
+ '@smithy/core@3.26.0':
+ resolution: {integrity: sha512-mLUktFAn+Pa2agl1J7VgtYNFWCX8/b4GMJSK1hCu4YCvtBfM6F8Os3EP4ry+DFFlXOf3wyvlgXhuUdFoy52D3g==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/credential-provider-imds@4.4.2':
+ resolution: {integrity: sha512-18UMDMyrAbDcpmL1gLUA7ww0fRTcdCrSjSJOi2Sbld+tVjwD/pW+OAwjlScFLR7vvBnhZrIPQ7kVuTf1mnJLug==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/fetch-http-handler@5.5.2':
+ resolution: {integrity: sha512-Ei/UK/QMhq0rKaMqGPlOAkE2yS9DZeYmZdk1RAKc3vp3zxgleZHZyBLlZv8yLsxljX4svCRuMTD6u3LLIcU4Bg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/is-array-buffer@2.2.0':
+ resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
+ engines: {node: '>=14.0.0'}
+
+ '@smithy/node-http-handler@4.7.3':
+ resolution: {integrity: sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/node-http-handler@4.8.2':
+ resolution: {integrity: sha512-wfl1uwrAqMH9/pi4kqBo5LBcFwrJLxuDLqL7p7qNcJIFcyZDUc6pzhYk4CYv+DP7fIUpQCZumwNnkhPKS52osQ==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/signature-v4@5.5.2':
+ resolution: {integrity: sha512-7xHpmPY4rt0IOmeAA8EfjgEH8isT+587TCdy9H6a7d4OMi5CQ0oEHhWllunvPu4j4Cq0vTFwdxXN/kABWPjdyA==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/types@4.15.0':
+ resolution: {integrity: sha512-Z5TAOxygoFvybJV3igo5SloFflSokHx2hu1eFA+DxDTcn+FtKxUSui+rbTRG1pAafMA888Z3MVvCWUuvCrTXjg==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-buffer-from@2.2.0':
+ resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
+ engines: {node: '>=14.0.0'}
+
+ '@smithy/util-utf8@2.3.0':
+ resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
+ engines: {node: '>=14.0.0'}
+
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@@ -913,6 +1239,9 @@ packages:
'@types/react@19.2.17':
resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==}
+ '@types/retry@0.12.0':
+ resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
+
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
@@ -933,6 +1262,10 @@ packages:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
ajv-formats@3.0.1:
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
peerDependencies:
@@ -982,14 +1315,23 @@ packages:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22}
+ base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
better-path-resolve@1.0.0:
resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==}
engines: {node: '>=4'}
+ bignumber.js@9.3.1:
+ resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
+
body-parser@2.2.2:
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
engines: {node: '>=18'}
+ bowser@2.14.1:
+ resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
+
brace-expansion@5.0.6:
resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
engines: {node: 18 || 20 || >=22}
@@ -998,6 +1340,9 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
+ buffer-equal-constant-time@1.0.1:
+ resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
+
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
@@ -1072,6 +1417,10 @@ packages:
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+ data-uri-to-buffer@4.0.1:
+ resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
+ engines: {node: '>= 12'}
+
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -1093,6 +1442,10 @@ packages:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
+ diff@8.0.4:
+ resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==}
+ engines: {node: '>=0.3.1'}
+
dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@@ -1101,6 +1454,9 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
+ ecdsa-sig-formatter@1.0.11:
+ resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
+
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
@@ -1181,6 +1537,9 @@ packages:
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
engines: {node: '>= 18'}
+ extend@3.0.2:
+ resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+
extendable-error@0.1.7:
resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==}
@@ -1210,6 +1569,10 @@ packages:
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
+ fetch-blob@3.2.0:
+ resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
+ engines: {node: ^12.20 || >= 14.13}
+
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@@ -1225,6 +1588,10 @@ packages:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
+ formdata-polyfill@4.0.10:
+ resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
+ engines: {node: '>=12.20.0'}
+
forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
@@ -1249,6 +1616,14 @@ packages:
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+ gaxios@7.1.5:
+ resolution: {integrity: sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg==}
+ engines: {node: '>=18'}
+
+ gcp-metadata@8.1.2:
+ resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==}
+ engines: {node: '>=18'}
+
get-east-asian-width@1.6.0:
resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
engines: {node: '>=18'}
@@ -1273,6 +1648,14 @@ packages:
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
engines: {node: '>=10'}
+ google-auth-library@10.9.0:
+ resolution: {integrity: sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==}
+ engines: {node: '>=18'}
+
+ google-logging-utils@1.1.3:
+ resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==}
+ engines: {node: '>=14'}
+
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
@@ -1288,14 +1671,29 @@ packages:
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
engines: {node: '>= 0.4'}
+ highlight.js@10.7.3:
+ resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
+
hono@4.12.23:
resolution: {integrity: sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==}
engines: {node: '>=16.9.0'}
+ hosted-git-info@9.0.3:
+ resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==}
+ engines: {node: ^20.17.0 || >=22.9.0}
+
http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
+ http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
human-id@4.2.0:
resolution: {integrity: sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==}
hasBin: true
@@ -1308,6 +1706,10 @@ packages:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
+ ignore@7.0.5:
+ resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
+ engines: {node: '>= 4'}
+
indent-string@5.0.0:
resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
engines: {node: '>=12'}
@@ -1375,6 +1777,10 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ jiti@2.7.0:
+ resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
+ hasBin: true
+
jose@6.2.3:
resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==}
@@ -1386,6 +1792,13 @@ packages:
resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==}
hasBin: true
+ json-bigint@1.0.0:
+ resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==}
+
+ json-schema-to-ts@3.1.1:
+ resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==}
+ engines: {node: '>=16'}
+
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
@@ -1400,6 +1813,12 @@ packages:
jsonfile@4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
+ jwa@2.0.1:
+ resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==}
+
+ jws@4.0.1:
+ resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
+
kubernetes-types@1.30.0:
resolution: {integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==}
@@ -1410,10 +1829,18 @@ packages:
lodash.startcase@4.4.0:
resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
+ long@5.3.2:
+ resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
+
lru-cache@11.5.1:
resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==}
engines: {node: 20 || >=22}
+ marked@18.0.5:
+ resolution: {integrity: sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==}
+ engines: {node: '>= 20'}
+ hasBin: true
+
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -1475,6 +1902,15 @@ packages:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
+ node-domexception@1.0.0:
+ resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
+ engines: {node: '>=10.5.0'}
+ deprecated: Use your platform's native DOMException instead
+
+ node-fetch@3.3.2:
+ resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
node-gyp-build-optional-packages@5.2.2:
resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
hasBin: true
@@ -1502,6 +1938,18 @@ packages:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'}
+ openai@6.26.0:
+ resolution: {integrity: sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==}
+ hasBin: true
+ peerDependencies:
+ ws: ^8.18.0
+ zod: ^3.25 || ^4.0
+ peerDependenciesMeta:
+ ws:
+ optional: true
+ zod:
+ optional: true
+
outdent@0.5.0:
resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==}
@@ -1521,6 +1969,10 @@ packages:
resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==}
engines: {node: '>=6'}
+ p-retry@4.6.2:
+ resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
+ engines: {node: '>=8'}
+
p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
@@ -1532,6 +1984,9 @@ packages:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
+ partial-json@0.1.7:
+ resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==}
+
patch-console@2.0.0:
resolution: {integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -1575,6 +2030,13 @@ packages:
engines: {node: '>=10.13.0'}
hasBin: true
+ proper-lockfile@4.1.2:
+ resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==}
+
+ protobufjs@7.6.4:
+ resolution: {integrity: sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==}
+ engines: {node: '>=12.0.0'}
+
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
@@ -1626,6 +2088,14 @@ packages:
resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ retry@0.12.0:
+ resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
+ engines: {node: '>= 4'}
+
+ retry@0.13.1:
+ resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
+ engines: {node: '>= 4'}
+
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -1637,6 +2107,9 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
@@ -1647,6 +2120,11 @@ packages:
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+ semver@7.8.0:
+ resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
semver@7.8.5:
resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
engines: {node: '>=10'}
@@ -1763,6 +2241,9 @@ packages:
resolution: {integrity: sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==}
engines: {node: '>=20'}
+ ts-algebra@2.0.0:
+ resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==}
+
ts-json-schema-generator@2.9.0:
resolution: {integrity: sha512-NR5ZE108uiPtBHBJNGnhwoUaUx5vWTDJzDFG9YlRoqxPU76n+5FClRh92dcGgysbe1smRmYalM9Saj97GW1J4Q==}
engines: {node: '>=22.0.0'}
@@ -1787,6 +2268,9 @@ packages:
resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==}
engines: {node: '>= 18'}
+ typebox@1.1.38:
+ resolution: {integrity: sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==}
+
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
@@ -1798,6 +2282,10 @@ packages:
undici-types@8.3.0:
resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
+ undici@8.5.0:
+ resolution: {integrity: sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==}
+ engines: {node: '>=22.19.0'}
+
universalify@0.1.2:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
engines: {node: '>= 4.0.0'}
@@ -1814,6 +2302,10 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
+ web-streams-polyfill@3.3.3:
+ resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
+ engines: {node: '>= 8'}
+
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -1868,6 +2360,234 @@ snapshots:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
+ '@anthropic-ai/sdk@0.91.1(zod@4.4.3)':
+ dependencies:
+ json-schema-to-ts: 3.1.1
+ optionalDependencies:
+ zod: 4.4.3
+
+ '@aws-crypto/crc32@5.2.0':
+ dependencies:
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.973.13
+ tslib: 2.8.1
+
+ '@aws-crypto/sha256-browser@5.2.0':
+ dependencies:
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-crypto/supports-web-crypto': 5.2.0
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.973.13
+ '@aws-sdk/util-locate-window': 3.965.8
+ '@smithy/util-utf8': 2.3.0
+ tslib: 2.8.1
+
+ '@aws-crypto/sha256-js@5.2.0':
+ dependencies:
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.973.13
+ tslib: 2.8.1
+
+ '@aws-crypto/supports-web-crypto@5.2.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@aws-crypto/util@5.2.0':
+ dependencies:
+ '@aws-sdk/types': 3.973.13
+ '@smithy/util-utf8': 2.3.0
+ tslib: 2.8.1
+
+ '@aws-sdk/client-bedrock-runtime@3.1048.0':
+ dependencies:
+ '@aws-crypto/sha256-browser': 5.2.0
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-sdk/core': 3.974.23
+ '@aws-sdk/credential-provider-node': 3.972.58
+ '@aws-sdk/eventstream-handler-node': 3.972.22
+ '@aws-sdk/middleware-eventstream': 3.972.18
+ '@aws-sdk/middleware-websocket': 3.972.31
+ '@aws-sdk/token-providers': 3.1048.0
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/fetch-http-handler': 5.5.2
+ '@smithy/node-http-handler': 4.7.3
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/core@3.974.23':
+ dependencies:
+ '@aws-sdk/types': 3.973.13
+ '@aws-sdk/xml-builder': 3.972.31
+ '@aws/lambda-invoke-store': 0.2.4
+ '@smithy/core': 3.26.0
+ '@smithy/signature-v4': 5.5.2
+ '@smithy/types': 4.15.0
+ bowser: 2.14.1
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-env@3.972.49':
+ dependencies:
+ '@aws-sdk/core': 3.974.23
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-http@3.972.51':
+ dependencies:
+ '@aws-sdk/core': 3.974.23
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/fetch-http-handler': 5.5.2
+ '@smithy/node-http-handler': 4.8.2
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-ini@3.972.56':
+ dependencies:
+ '@aws-sdk/core': 3.974.23
+ '@aws-sdk/credential-provider-env': 3.972.49
+ '@aws-sdk/credential-provider-http': 3.972.51
+ '@aws-sdk/credential-provider-login': 3.972.55
+ '@aws-sdk/credential-provider-process': 3.972.49
+ '@aws-sdk/credential-provider-sso': 3.972.55
+ '@aws-sdk/credential-provider-web-identity': 3.972.55
+ '@aws-sdk/nested-clients': 3.997.23
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/credential-provider-imds': 4.4.2
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-login@3.972.55':
+ dependencies:
+ '@aws-sdk/core': 3.974.23
+ '@aws-sdk/nested-clients': 3.997.23
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-node@3.972.58':
+ dependencies:
+ '@aws-sdk/credential-provider-env': 3.972.49
+ '@aws-sdk/credential-provider-http': 3.972.51
+ '@aws-sdk/credential-provider-ini': 3.972.56
+ '@aws-sdk/credential-provider-process': 3.972.49
+ '@aws-sdk/credential-provider-sso': 3.972.55
+ '@aws-sdk/credential-provider-web-identity': 3.972.55
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/credential-provider-imds': 4.4.2
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-process@3.972.49':
+ dependencies:
+ '@aws-sdk/core': 3.974.23
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-sso@3.972.55':
+ dependencies:
+ '@aws-sdk/core': 3.974.23
+ '@aws-sdk/nested-clients': 3.997.23
+ '@aws-sdk/token-providers': 3.1074.0
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/credential-provider-web-identity@3.972.55':
+ dependencies:
+ '@aws-sdk/core': 3.974.23
+ '@aws-sdk/nested-clients': 3.997.23
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/eventstream-handler-node@3.972.22':
+ dependencies:
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-eventstream@3.972.18':
+ dependencies:
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/middleware-websocket@3.972.31':
+ dependencies:
+ '@aws-sdk/core': 3.974.23
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/fetch-http-handler': 5.5.2
+ '@smithy/signature-v4': 5.5.2
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/nested-clients@3.997.23':
+ dependencies:
+ '@aws-crypto/sha256-browser': 5.2.0
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-sdk/core': 3.974.23
+ '@aws-sdk/signature-v4-multi-region': 3.996.35
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/fetch-http-handler': 5.5.2
+ '@smithy/node-http-handler': 4.8.2
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/signature-v4-multi-region@3.996.35':
+ dependencies:
+ '@aws-sdk/types': 3.973.13
+ '@smithy/signature-v4': 5.5.2
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/token-providers@3.1048.0':
+ dependencies:
+ '@aws-sdk/core': 3.974.23
+ '@aws-sdk/nested-clients': 3.997.23
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/token-providers@3.1074.0':
+ dependencies:
+ '@aws-sdk/core': 3.974.23
+ '@aws-sdk/nested-clients': 3.997.23
+ '@aws-sdk/types': 3.973.13
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/types@3.973.13':
+ dependencies:
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws-sdk/util-locate-window@3.965.8':
+ dependencies:
+ tslib: 2.8.1
+
+ '@aws-sdk/xml-builder@3.972.31':
+ dependencies:
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@aws/lambda-invoke-store@0.2.4': {}
+
'@babel/runtime@7.29.7': {}
'@changesets/apply-release-plan@7.1.1':
@@ -2025,6 +2745,76 @@ snapshots:
fast-wrap-ansi: 0.2.2
sisteransi: 1.0.5
+ '@earendil-works/pi-agent-core@0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)':
+ dependencies:
+ '@earendil-works/pi-ai': 0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)
+ ignore: 7.0.5
+ typebox: 1.1.38
+ yaml: 2.9.0
+ transitivePeerDependencies:
+ - '@modelcontextprotocol/sdk'
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ - ws
+ - zod
+
+ '@earendil-works/pi-ai@0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)':
+ dependencies:
+ '@anthropic-ai/sdk': 0.91.1(zod@4.4.3)
+ '@aws-sdk/client-bedrock-runtime': 3.1048.0
+ '@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))
+ '@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0)
+ '@opentelemetry/api': 1.9.0
+ '@smithy/node-http-handler': 4.7.3
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ openai: 6.26.0(ws@8.21.0)(zod@4.4.3)
+ partial-json: 0.1.7
+ typebox: 1.1.38
+ transitivePeerDependencies:
+ - '@modelcontextprotocol/sdk'
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ - ws
+ - zod
+
+ '@earendil-works/pi-coding-agent@0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)':
+ dependencies:
+ '@earendil-works/pi-agent-core': 0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)
+ '@earendil-works/pi-ai': 0.79.10(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)
+ '@earendil-works/pi-tui': 0.79.10
+ '@silvia-odwyer/photon-node': 0.3.4
+ chalk: 5.6.2
+ cross-spawn: 7.0.6
+ diff: 8.0.4
+ glob: 13.0.6
+ highlight.js: 10.7.3
+ hosted-git-info: 9.0.3
+ ignore: 7.0.5
+ jiti: 2.7.0
+ minimatch: 10.2.5
+ proper-lockfile: 4.1.2
+ semver: 7.8.0
+ typebox: 1.1.38
+ undici: 8.5.0
+ yaml: 2.9.0
+ optionalDependencies:
+ '@mariozechner/clipboard': 0.3.9
+ transitivePeerDependencies:
+ - '@modelcontextprotocol/sdk'
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ - ws
+ - zod
+
+ '@earendil-works/pi-tui@0.79.10':
+ dependencies:
+ get-east-asian-width: 1.6.0
+ marked: 18.0.5
+
'@eplightning/nats-server-darwin-arm64@2.14.0':
optional: true
@@ -2199,6 +2989,19 @@ snapshots:
'@esbuild/win32-x64@0.28.1':
optional: true
+ '@google/genai@1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))':
+ dependencies:
+ google-auth-library: 10.9.0
+ p-retry: 4.6.2
+ protobufjs: 7.6.4
+ ws: 8.21.0
+ optionalDependencies:
+ '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3)
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
'@hono/node-server@1.19.14(hono@4.12.23)':
dependencies:
hono: 4.12.23
@@ -2253,6 +3056,62 @@ snapshots:
globby: 11.1.0
read-yaml-file: 1.1.0
+ '@mariozechner/clipboard-darwin-arm64@0.3.9':
+ optional: true
+
+ '@mariozechner/clipboard-darwin-universal@0.3.9':
+ optional: true
+
+ '@mariozechner/clipboard-darwin-x64@0.3.9':
+ optional: true
+
+ '@mariozechner/clipboard-linux-arm64-gnu@0.3.9':
+ optional: true
+
+ '@mariozechner/clipboard-linux-arm64-musl@0.3.9':
+ optional: true
+
+ '@mariozechner/clipboard-linux-riscv64-gnu@0.3.9':
+ optional: true
+
+ '@mariozechner/clipboard-linux-x64-gnu@0.3.9':
+ optional: true
+
+ '@mariozechner/clipboard-linux-x64-musl@0.3.9':
+ optional: true
+
+ '@mariozechner/clipboard-win32-arm64-msvc@0.3.9':
+ optional: true
+
+ '@mariozechner/clipboard-win32-x64-msvc@0.3.9':
+ optional: true
+
+ '@mariozechner/clipboard@0.3.9':
+ optionalDependencies:
+ '@mariozechner/clipboard-darwin-arm64': 0.3.9
+ '@mariozechner/clipboard-darwin-universal': 0.3.9
+ '@mariozechner/clipboard-darwin-x64': 0.3.9
+ '@mariozechner/clipboard-linux-arm64-gnu': 0.3.9
+ '@mariozechner/clipboard-linux-arm64-musl': 0.3.9
+ '@mariozechner/clipboard-linux-riscv64-gnu': 0.3.9
+ '@mariozechner/clipboard-linux-x64-gnu': 0.3.9
+ '@mariozechner/clipboard-linux-x64-musl': 0.3.9
+ '@mariozechner/clipboard-win32-arm64-msvc': 0.3.9
+ '@mariozechner/clipboard-win32-x64-msvc': 0.3.9
+ optional: true
+
+ '@mistralai/mistralai@2.2.6(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/semantic-conventions': 1.41.1
+ ws: 8.21.0
+ zod: 4.4.3
+ zod-to-json-schema: 3.25.2(zod@4.4.3)
+ optionalDependencies:
+ '@opentelemetry/api': 1.9.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
'@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)':
dependencies:
'@hono/node-server': 1.19.14(hono@4.12.23)
@@ -2349,6 +3208,86 @@ snapshots:
dependencies:
cross-spawn: 7.0.6
+ '@opentelemetry/api@1.9.0': {}
+
+ '@opentelemetry/semantic-conventions@1.41.1': {}
+
+ '@protobufjs/aspromise@1.1.2': {}
+
+ '@protobufjs/base64@1.1.2': {}
+
+ '@protobufjs/codegen@2.0.5': {}
+
+ '@protobufjs/eventemitter@1.1.1': {}
+
+ '@protobufjs/fetch@1.1.1':
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+
+ '@protobufjs/float@1.0.2': {}
+
+ '@protobufjs/path@1.1.2': {}
+
+ '@protobufjs/pool@1.1.0': {}
+
+ '@protobufjs/utf8@1.1.1': {}
+
+ '@silvia-odwyer/photon-node@0.3.4': {}
+
+ '@smithy/core@3.26.0':
+ dependencies:
+ '@aws-crypto/crc32': 5.2.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@smithy/credential-provider-imds@4.4.2':
+ dependencies:
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@smithy/fetch-http-handler@5.5.2':
+ dependencies:
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@smithy/is-array-buffer@2.2.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/node-http-handler@4.7.3':
+ dependencies:
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@smithy/node-http-handler@4.8.2':
+ dependencies:
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@smithy/signature-v4@5.5.2':
+ dependencies:
+ '@smithy/core': 3.26.0
+ '@smithy/types': 4.15.0
+ tslib: 2.8.1
+
+ '@smithy/types@4.15.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/util-buffer-from@2.2.0':
+ dependencies:
+ '@smithy/is-array-buffer': 2.2.0
+ tslib: 2.8.1
+
+ '@smithy/util-utf8@2.3.0':
+ dependencies:
+ '@smithy/util-buffer-from': 2.2.0
+ tslib: 2.8.1
+
'@standard-schema/spec@1.1.0': {}
'@types/json-schema@7.0.15': {}
@@ -2367,6 +3306,8 @@ snapshots:
dependencies:
csstype: 3.2.3
+ '@types/retry@0.12.0': {}
+
'@types/ws@8.18.1':
dependencies:
'@types/node': 26.0.0
@@ -2386,6 +3327,8 @@ snapshots:
mime-types: 3.0.2
negotiator: 1.0.0
+ agent-base@7.1.4: {}
+
ajv-formats@3.0.1(ajv@8.20.0):
optionalDependencies:
ajv: 8.20.0
@@ -2421,10 +3364,14 @@ snapshots:
balanced-match@4.0.4: {}
+ base64-js@1.5.1: {}
+
better-path-resolve@1.0.0:
dependencies:
is-windows: 1.0.2
+ bignumber.js@9.3.1: {}
+
body-parser@2.2.2:
dependencies:
bytes: 3.1.2
@@ -2439,6 +3386,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ bowser@2.14.1: {}
+
brace-expansion@5.0.6:
dependencies:
balanced-match: 4.0.4
@@ -2447,6 +3396,8 @@ snapshots:
dependencies:
fill-range: 7.1.1
+ buffer-equal-constant-time@1.0.1: {}
+
bytes@3.1.2: {}
call-bind-apply-helpers@1.0.2:
@@ -2505,6 +3456,8 @@ snapshots:
csstype@3.2.3: {}
+ data-uri-to-buffer@4.0.1: {}
+
debug@4.4.3:
dependencies:
ms: 2.1.3
@@ -2516,6 +3469,8 @@ snapshots:
detect-libc@2.1.2:
optional: true
+ diff@8.0.4: {}
+
dir-glob@3.0.1:
dependencies:
path-type: 4.0.0
@@ -2526,6 +3481,10 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
+ ecdsa-sig-formatter@1.0.11:
+ dependencies:
+ safe-buffer: 5.2.1
+
ee-first@1.1.1: {}
effect@4.0.0-beta.74:
@@ -2672,6 +3631,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ extend@3.0.2: {}
+
extendable-error@0.1.7: {}
fast-check@4.8.0:
@@ -2704,6 +3665,11 @@ snapshots:
dependencies:
reusify: 1.1.0
+ fetch-blob@3.2.0:
+ dependencies:
+ node-domexception: 1.0.0
+ web-streams-polyfill: 3.3.3
+
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@@ -2726,6 +3692,10 @@ snapshots:
locate-path: 5.0.0
path-exists: 4.0.0
+ formdata-polyfill@4.0.10:
+ dependencies:
+ fetch-blob: 3.2.0
+
forwarded@0.2.0: {}
fresh@2.0.0: {}
@@ -2747,6 +3717,22 @@ snapshots:
function-bind@1.1.2: {}
+ gaxios@7.1.5:
+ dependencies:
+ extend: 3.0.2
+ https-proxy-agent: 7.0.6
+ node-fetch: 3.3.2
+ transitivePeerDependencies:
+ - supports-color
+
+ gcp-metadata@8.1.2:
+ dependencies:
+ gaxios: 7.1.5
+ google-logging-utils: 1.1.3
+ json-bigint: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+
get-east-asian-width@1.6.0: {}
get-intrinsic@1.3.0:
@@ -2786,6 +3772,19 @@ snapshots:
merge2: 1.4.1
slash: 3.0.0
+ google-auth-library@10.9.0:
+ dependencies:
+ base64-js: 1.5.1
+ ecdsa-sig-formatter: 1.0.11
+ gaxios: 7.1.5
+ gcp-metadata: 8.1.2
+ google-logging-utils: 1.1.3
+ jws: 4.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ google-logging-utils@1.1.3: {}
+
gopd@1.2.0: {}
graceful-fs@4.2.11: {}
@@ -2796,8 +3795,14 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ highlight.js@10.7.3: {}
+
hono@4.12.23: {}
+ hosted-git-info@9.0.3:
+ dependencies:
+ lru-cache: 11.5.1
+
http-errors@2.0.1:
dependencies:
depd: 2.0.0
@@ -2806,6 +3811,20 @@ snapshots:
statuses: 2.0.2
toidentifier: 1.0.1
+ http-proxy-agent@7.0.2:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
human-id@4.2.0: {}
iconv-lite@0.7.2:
@@ -2814,6 +3833,8 @@ snapshots:
ignore@5.3.2: {}
+ ignore@7.0.5: {}
+
indent-string@5.0.0: {}
inherits@2.0.4: {}
@@ -2882,6 +3903,8 @@ snapshots:
isexe@2.0.0: {}
+ jiti@2.7.0: {}
+
jose@6.2.3: {}
js-yaml@3.14.2:
@@ -2893,6 +3916,15 @@ snapshots:
dependencies:
argparse: 2.0.1
+ json-bigint@1.0.0:
+ dependencies:
+ bignumber.js: 9.3.1
+
+ json-schema-to-ts@3.1.1:
+ dependencies:
+ '@babel/runtime': 7.29.7
+ ts-algebra: 2.0.0
+
json-schema-traverse@1.0.0: {}
json-schema-typed@8.0.2: {}
@@ -2903,6 +3935,17 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
+ jwa@2.0.1:
+ dependencies:
+ buffer-equal-constant-time: 1.0.1
+ ecdsa-sig-formatter: 1.0.11
+ safe-buffer: 5.2.1
+
+ jws@4.0.1:
+ dependencies:
+ jwa: 2.0.1
+ safe-buffer: 5.2.1
+
kubernetes-types@1.30.0: {}
locate-path@5.0.0:
@@ -2911,8 +3954,12 @@ snapshots:
lodash.startcase@4.4.0: {}
+ long@5.3.2: {}
+
lru-cache@11.5.1: {}
+ marked@18.0.5: {}
+
math-intrinsics@1.1.0: {}
media-typer@1.1.0: {}
@@ -2964,6 +4011,14 @@ snapshots:
negotiator@1.0.0: {}
+ node-domexception@1.0.0: {}
+
+ node-fetch@3.3.2:
+ dependencies:
+ data-uri-to-buffer: 4.0.1
+ fetch-blob: 3.2.0
+ formdata-polyfill: 4.0.10
+
node-gyp-build-optional-packages@5.2.2:
dependencies:
detect-libc: 2.1.2
@@ -2987,6 +4042,11 @@ snapshots:
dependencies:
mimic-fn: 2.1.0
+ openai@6.26.0(ws@8.21.0)(zod@4.4.3):
+ optionalDependencies:
+ ws: 8.21.0
+ zod: 4.4.3
+
outdent@0.5.0: {}
p-filter@2.1.0:
@@ -3003,6 +4063,11 @@ snapshots:
p-map@2.1.0: {}
+ p-retry@4.6.2:
+ dependencies:
+ '@types/retry': 0.12.0
+ retry: 0.13.1
+
p-try@2.2.0: {}
package-manager-detector@0.2.11:
@@ -3011,6 +4076,8 @@ snapshots:
parseurl@1.3.3: {}
+ partial-json@0.1.7: {}
+
patch-console@2.0.0: {}
path-exists@4.0.0: {}
@@ -3036,6 +4103,26 @@ snapshots:
prettier@2.8.8: {}
+ proper-lockfile@4.1.2:
+ dependencies:
+ graceful-fs: 4.2.11
+ retry: 0.12.0
+ signal-exit: 3.0.7
+
+ protobufjs@7.6.4:
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/base64': 1.1.2
+ '@protobufjs/codegen': 2.0.5
+ '@protobufjs/eventemitter': 1.1.1
+ '@protobufjs/fetch': 1.1.1
+ '@protobufjs/float': 1.0.2
+ '@protobufjs/path': 1.1.2
+ '@protobufjs/pool': 1.1.0
+ '@protobufjs/utf8': 1.1.1
+ '@types/node': 22.20.0
+ long: 5.3.2
+
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
@@ -3083,6 +4170,10 @@ snapshots:
onetime: 5.1.2
signal-exit: 3.0.7
+ retry@0.12.0: {}
+
+ retry@0.13.1: {}
+
reusify@1.1.0: {}
router@2.2.0:
@@ -3099,12 +4190,16 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
+ safe-buffer@5.2.1: {}
+
safe-stable-stringify@2.5.0: {}
safer-buffer@2.1.2: {}
scheduler@0.27.0: {}
+ semver@7.8.0: {}
+
semver@7.8.5: {}
send@1.2.1:
@@ -3229,6 +4324,8 @@ snapshots:
toml@4.1.1: {}
+ ts-algebra@2.0.0: {}
+
ts-json-schema-generator@2.9.0:
dependencies:
'@types/json-schema': 7.0.15
@@ -3260,12 +4357,16 @@ snapshots:
media-typer: 1.1.0
mime-types: 3.0.2
+ typebox@1.1.38: {}
+
typescript@5.9.3: {}
undici-types@6.21.0: {}
undici-types@8.3.0: {}
+ undici@8.5.0: {}
+
universalify@0.1.2: {}
unpipe@1.0.0: {}
@@ -3274,6 +4375,8 @@ snapshots:
vary@1.1.2: {}
+ web-streams-polyfill@3.3.3: {}
+
which@2.0.2:
dependencies:
isexe: 2.0.0
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 0774c3ad..c4976edf 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -6,5 +6,7 @@ packages:
- "examples/*/*"
- "bin"
allowBuilds:
+ '@google/genai': false
esbuild: true
msgpackr-extract: false
+ protobufjs: false