diff --git a/examples/04-oh-my-pi/package.json b/examples/04-oh-my-pi/package.json new file mode 100644 index 00000000..3bb61c7e --- /dev/null +++ b/examples/04-oh-my-pi/package.json @@ -0,0 +1,20 @@ +{ + "name": "@cotal-ai/example-04-oh-my-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/oh-my-pi": "workspace:*" + }, + "devDependencies": { + "tsx": "^4.22.4" + } +} diff --git a/examples/04-oh-my-pi/src/manager.ts b/examples/04-oh-my-pi/src/manager.ts new file mode 100644 index 00000000..7aa724d7 --- /dev/null +++ b/examples/04-oh-my-pi/src/manager.ts @@ -0,0 +1,28 @@ +/** + * Composition root for example 04 (oh-my-pi coding agent). Runs a manager that + * spawns oh-my-pi peers into the space. Each spawn is a real oh-my-pi agent + * session (extensions/connector-oh-my-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 "oh-my-pi". + */ +import { DEFAULT_SERVER, isReachable } from "@cotal-ai/core"; +import { Manager } from "@cotal-ai/manager"; +import "@cotal-ai/oh-my-pi"; // self-registers "oh-my-pi" + +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-04-oh-my-pi manager up in space "${space}" — connector: oh-my-pi`); +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/04-oh-my-pi/tsconfig.json b/examples/04-oh-my-pi/tsconfig.json new file mode 100644 index 00000000..051d08e2 --- /dev/null +++ b/examples/04-oh-my-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/package.json b/extensions/connector-core/package.json index 0bfd1f7a..db0694bb 100644 --- a/extensions/connector-core/package.json +++ b/extensions/connector-core/package.json @@ -20,6 +20,9 @@ "scripts": { "typecheck": "tsc -p tsconfig.json --noEmit", "build": "tsc -p tsconfig.json", + "smoke:inbox": "tsx inbox-turn.smoke.ts", + "smoke:reconnect-log": "tsx smoke/reconnect-log.smoke.ts", + "test": "pnpm run smoke:inbox && pnpm run smoke:reconnect-log", "prepublishOnly": "pnpm run build" }, "dependencies": { diff --git a/extensions/connector-core/smoke/reconnect-log.smoke.ts b/extensions/connector-core/smoke/reconnect-log.smoke.ts new file mode 100644 index 00000000..eb0b860f --- /dev/null +++ b/extensions/connector-core/smoke/reconnect-log.smoke.ts @@ -0,0 +1,80 @@ +/** + * Reconnect-logging smoke (no NATS) — proves a mesh drop can't flood the host or corrupt its TUI. + * CotalEndpoint is an EventEmitter, so we drive MeshAgent's endpoint events directly (never + * connecting) and assert the anti-flood + off-terminal contract: + * - a drop logs exactly ONE "connection lost" line; recovery logs exactly ONE "reconnected" line; + * - the repeated endpoint errors during the outage (the TIMEOUT flood) are SUPPRESSED; + * - a live-connection error still surfaces (ACL denial, etc.), and an identical repeat is deduped; + * - an INJECTED logger receives every line and process.stderr is NEVER touched — so the in-process + * OMP extension (which passes pi.logger) can't scribble on the shared terminal. + * Run: pnpm smoke:reconnect-log + */ +import { MeshAgent, type MeshLogLevel } from "../src/agent.js"; +import type { AgentConfig } from "../src/config.js"; + +let failures = 0; +function check(label: string, cond: boolean, extra?: unknown): void { + console.log(`${cond ? "✓" : "✗"} ${label}${cond ? "" : ` — ${JSON.stringify(extra)}`}`); + if (!cond) failures++; +} + +const cfg: AgentConfig = { + space: "smoke", + name: "log-canary", + servers: "nats://127.0.0.1:1", + subscribe: [], + allowSubscribe: [], + allowPublish: [], + kind: "agent", + tls: false, +}; + +const lines: { msg: string; level: MeshLogLevel }[] = []; +const agent = new MeshAgent(cfg, (msg, level) => lines.push({ msg, level: level ?? "info" })); +const endpointErrors = () => lines.filter((l) => l.msg.includes("endpoint error")); + +// Guard: with a logger injected, NOTHING may reach the shared terminal. +let stderrWrites = 0; +const realWrite = process.stderr.write.bind(process.stderr); +(process.stderr as unknown as { write: (s: string) => boolean }).write = () => { + stderrWrites++; + return true; +}; + +try { + const ep = agent.ep; + + // Initial connect: the observer must NOT announce a "reconnect" (connectLoop logs the first connect). + ep.emit("connection", { connected: true }); + check("initial connect logs no 'reconnected'", lines.filter((l) => l.msg.includes("reconnected")).length === 0, lines); + + // Drop. + ep.emit("connection", { connected: false }); + const lost = lines.filter((l) => l.msg.includes("connection lost")); + check("drop logs exactly one 'connection lost' at warn", lost.length === 1 && lost[0].level === "warn", lost); + + // The flood: repeated endpoint errors while disconnected — the exact spam that broke the TUI. + for (let i = 0; i < 8; i++) ep.emit("error", new Error("TIMEOUT")); + check("outage endpoint errors are suppressed", endpointErrors().length === 0, lines); + + // Recover. + ep.emit("connection", { connected: true }); + const recon = lines.filter((l) => l.msg.includes("reconnected to the mesh")); + check("recovery logs exactly one 'reconnected' at info", recon.length === 1 && recon[0].level === "info", recon); + + // A live-connection error DOES surface (genuine, actionable). + ep.emit("error", new Error("NATS permission denied: cannot publish")); + check("live error surfaces once", endpointErrors().length === 1, lines); + + // An identical consecutive error is deduped (spam guard for a connected-but-flapping error). + ep.emit("error", new Error("NATS permission denied: cannot publish")); + check("identical consecutive live error is deduped", endpointErrors().length === 1, lines); + + // The whole sequence never touched the terminal. + check("no writes to process.stderr (no TUI corruption)", stderrWrites === 0, stderrWrites); +} finally { + (process.stderr as unknown as { write: typeof realWrite }).write = realWrite; +} + +console.log(`\nRECONNECT-LOG SMOKE ${failures === 0 ? "OK ✅" : "FAILED ❌"} (${lines.length} lines)`); +process.exit(failures === 0 ? 0 : 1); diff --git a/extensions/connector-core/src/agent.ts b/extensions/connector-core/src/agent.ts index f6ffc780..6470f2ba 100644 --- a/extensions/connector-core/src/agent.ts +++ b/extensions/connector-core/src/agent.ts @@ -62,8 +62,31 @@ interface Pending { ack: () => void; } +/** Severity for a connector log line. The default sink maps everything to stderr; an injected + * sink (the in-process oh-my-pi extension passes `pi.logger`) routes by level to a FILE so a + * mesh blip never scribbles on the host TUI's shared terminal. */ +export type MeshLogLevel = "info" | "warn" | "error"; + +/** Where a {@link MeshAgent}'s diagnostics go. Default: one prefixed line per call to stderr — + * correct for the out-of-process connectors (Claude Code MCP, OpenCode, Hermes) that own their + * stderr. An in-process host (oh-my-pi) MUST inject its own file logger, or reconnect churn + * corrupts the rendered screen. */ +export type MeshLogger = (msg: string, level?: MeshLogLevel) => void; + const MAX_INBOX = 200; +/** Backoff ceiling for the initial-connect + self-heal retry loops. Growth from the first + * `retryMs` is exponential up to this, so a mesh that's down at launch (or dropped mid-session) + * is retried politely rather than hammered every 3s. */ +const MAX_RETRY_MS = 30_000; + +/** Default diagnostics sink: one prefixed line per call to stderr. Correct for the out-of-process + * connectors (Claude Code MCP, OpenCode, Hermes) that own their own stderr; the in-process + * oh-my-pi extension injects a file logger instead so mesh churn can't corrupt the TUI. */ +function defaultLogger(msg: string, level: MeshLogLevel = "info"): void { + process.stderr.write(`[cotal-connector:${level}] ${msg}\n`); +} + function sleep(ms: number): Promise { return new Promise((r) => setTimeout(r, ms)); } @@ -110,10 +133,27 @@ export class MeshAgent extends EventEmitter { * published after it ("since you entered focus"). Undefined unless in focus. */ private focusSince?: number; private stopping = false; - - constructor(config: AgentConfig) { + /** In-flight connect-retry backoff, so {@link stop} can interrupt it instead of leaking the + * event loop for up to MAX_RETRY_MS when the mesh is unreachable at shutdown. Mirrors the + * endpoint's kickBackoff. */ + private retryTimer?: ReturnType; + private retryResolve?: () => void; + /** Diagnostics sink. Defaults to one prefixed stderr line per call; an in-process host injects + * a file logger so mesh churn never touches the shared terminal. */ + private readonly logger: MeshLogger; + /** Connectivity as last OBSERVED from the endpoint's `connection` event — drives the + * drop/recover edge so a lost mesh logs ONCE, not every retry. Starts true so the first + * `connection:false` before any connect (initial-connect failure) isn't mis-logged as a "drop"; + * the initial-connect banner is {@link connectLoop}'s job. */ + private _observedConnected = true; + /** The last `endpoint error` text logged while connected — suppresses an identical consecutive + * repeat (a connected-but-flapping error) without hiding a genuinely new one. */ + private lastLoggedError?: string; + + constructor(config: AgentConfig, logger?: MeshLogger) { super(); this.config = config; + this.logger = logger ?? defaultLogger; // Seed per-channel attention from the operator's file default (one-way: the runtime never writes // back — the persona file is a shared template). muted/quiet are validated disjoint at file load. for (const c of config.quiet ?? []) this.channelModes.set(c, "quiet"); @@ -142,11 +182,11 @@ export class MeshAgent extends EventEmitter { }, }); this.ep.on("message", (m: CotalMessage, d: Delivery, meta?: MessageMeta) => this.ingest(m, d, meta)); - this.ep.on("error", (e: Error) => this.log(`endpoint error: ${e.message}`)); + this.ep.on("error", (e: Error) => this.onEndpointError(e)); // The endpoint's (re)binds are the single source of truth for connectedness: this fires on // initial start, manual reconnect, AND the background self-heal — so a recovery the endpoint // did on its own can't leave us thinking we're offline (which would skip stop() → leak). - this.ep.on("connection", (e: { connected: boolean }) => { this._connected = e.connected; }); + this.ep.on("connection", (e: { connected: boolean }) => this.onConnectionChange(e.connected)); } get id(): string { @@ -163,28 +203,48 @@ export class MeshAgent extends EventEmitter { this._contextId = clean ? clean : undefined; } - /** Begin connecting (with background retry). Returns immediately. */ + /** Begin connecting (with background retry). Returns immediately. `retryMs` is the FIRST + * backoff; it grows exponentially to {@link MAX_RETRY_MS} so a mesh that's down at launch is + * retried politely, not hammered every 3s. */ start(retryMs = 3000): void { void this.connectLoop(retryMs); } private async connectLoop(retryMs: number): Promise { + let delay = retryMs; while (!this.stopping && !this._connected) { try { await this.ep.start(); // _connected is set by the endpoint's "connection" event (fired inside start()), not here. this.log( `connected to ${this.config.servers} as ${this.who()} in space "${this.config.space}" on #${this.config.subscribe.join(", #")}`, + "info", ); } catch (e) { - this.log(`mesh unreachable (${(e as Error).message}); retrying in ${retryMs}ms`); - await sleep(retryMs); + // Log the FIRST failure of an outage once (at warn), then stay quiet through the retries — + // the drop/recover edge is tracked by _observedConnected so a down mesh never floods. + if (this._observedConnected) { + this._observedConnected = false; + this.log(`mesh unreachable (${(e as Error).message}); retrying in the background`, "warn"); + } + // Cancellable backoff: stop() clears the timer + resolves this so shutdown isn't blocked + // for up to MAX_RETRY_MS on an unreachable mesh (the loop then exits on `this.stopping`). + await new Promise((resolve) => { + this.retryResolve = resolve; + this.retryTimer = setTimeout(resolve, delay); + }); + this.retryTimer = undefined; + this.retryResolve = undefined; + delay = Math.min(delay * 2, MAX_RETRY_MS); } } } async stop(): Promise { this.stopping = true; + // Interrupt any in-flight connect-retry backoff so shutdown doesn't wait out the timer. + clearTimeout(this.retryTimer); + this.retryResolve?.(); // Unconditional: a background self-heal can flip _connected without us, so a `_connected` // guard could skip the stop and leak the live connection/heartbeat/supervisor. ep.stop() is // idempotent (early-returns once stopped), so calling it when already-down is a noop. @@ -304,6 +364,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 a 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. */ @@ -721,7 +799,34 @@ export class MeshAgent extends EventEmitter { } } - private log(msg: string): void { - process.stderr.write(`[cotal-connector] ${msg}\n`); + /** React to an endpoint connectivity change (initial connect, manual reconnect, or background + * self-heal). The drop → recover edges each log ONCE, so a mesh outage can't flood the host: + * the endpoint's `reestablishLoop` emits an `error` per failed retry, all of which + * {@link onEndpointError} then suppresses while we're disconnected. */ + private onConnectionChange(connected: boolean): void { + const was = this._observedConnected; + this._connected = connected; + this._observedConnected = connected; + if (was && !connected) { + this.log("mesh connection lost — retrying in the background", "warn"); + } else if (!was && connected) { + this.lastLoggedError = undefined; // a fresh connection: the next genuine error is worth logging + this.log("reconnected to the mesh", "info"); + } + } + + /** An endpoint `error`. While DISCONNECTED it's reconnect churn (the `reestablishLoop` TIMEOUT + * per retry) — suppressed, since {@link onConnectionChange} already logged the drop once. While + * connected it's a genuine, actionable fault (e.g. an ACL denial); log it, deduping an identical + * consecutive repeat so a flapping error can't spam either. */ + private onEndpointError(e: Error): void { + if (!this._connected) return; + if (e.message === this.lastLoggedError) return; + this.lastLoggedError = e.message; + this.log(`endpoint error: ${e.message}`, "error"); + } + + private log(msg: string, level: MeshLogLevel = "info"): void { + this.logger(msg, level); } } diff --git a/extensions/connector-core/src/inbox-turn.ts b/extensions/connector-core/src/inbox-turn.ts new file mode 100644 index 00000000..f2df0bcf --- /dev/null +++ b/extensions/connector-core/src/inbox-turn.ts @@ -0,0 +1,133 @@ +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; + } + + /** + * Un-surface a single id that this turn surfaced but could not deliver (e.g. a `steer()` + * the session rejected). It drops off the ack set so {@link commit} won't consume it — the + * message stays on the stream and redelivers on a later turn. A no-op for an id this turn + * never surfaced (or one already evicted by overflow). + */ + unsurface(id: string): void { + const i = this.surfacedIds.indexOf(id); + if (i !== -1) this.surfacedIds.splice(i, 1); + } + + /** + * 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/connector-oh-my-pi/README.md b/extensions/connector-oh-my-pi/README.md new file mode 100644 index 00000000..267f90e1 --- /dev/null +++ b/extensions/connector-oh-my-pi/README.md @@ -0,0 +1,37 @@ +# @cotal-ai/oh-my-pi + +The Cotal connector for [oh-my-pi](https://github.com/can1357/oh-my-pi). It ships two entry +points onto one mesh runtime (`MeshAgent` + the shared `cotal_*` tools from +[`@cotal-ai/connector-core`](../connector-core)): + +## 1. Headless native-embed peer (`runOmpPeer` / `connector`) + +Embeds a Cotal endpoint inside an oh-my-pi process and answers mesh traffic through the agent's +own loop, driven by the shared `InboxTurn` embed loop — like the [`@cotal-ai/pi`](../pi) adapter, +it drives a *live* turn (`steer()` folds a same-scope message into an in-flight one). This is the +path a Cotal manager uses to spawn and supervise an oh-my-pi worker (`connector.buildLaunch`). + +## 2. Interactive session extension (`src/extension.ts`) + +A `pi --extension` (default export = the extension factory) that joins a **human- or +Compass-launched** oh-my-pi session to the mesh — the interactive sibling of the +[opencode](../connector-opencode) plugin. It holds a `MeshAgent`, registers the `cotal_*` tools +via `pi.registerTool`, maps the session's event stream to presence, and delivers inbound mesh +traffic into the session with `pi.sendMessage(..., { deliverAs })` (waking an idle session, +steering a live one, never interrupting a running turn; acks on turn end so a crash redelivers). +Identity comes from `COTAL_*` env — no identity → inert, so a plain `omp` never joins as a stray +peer. `pnpm build` bundles it to `dist/extension.bundle.js` (esbuild, host `@oh-my-pi/*` +external), the artifact a session loads via `--extension`. + +## Fork divergences handled here + +oh-my-pi is a fork of Pi, so this mirrors [`@cotal-ai/pi`](../pi) but targets +`@oh-my-pi/pi-coding-agent`: retries surface as session `auto_retry_*` events (not an +`agent_end.willRetry` flag), and imports use the package's subpath entrypoints while the published +root type barrel is fixed upstream (see the header comment in `src/peer.ts` / `src/extension.ts`). + +**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/connector-oh-my-pi/interactive-loop.smoke.ts b/extensions/connector-oh-my-pi/interactive-loop.smoke.ts new file mode 100644 index 00000000..33248812 --- /dev/null +++ b/extensions/connector-oh-my-pi/interactive-loop.smoke.ts @@ -0,0 +1,329 @@ +/** + * Behavioral smoke for the cotal-mesh delivery loop (`runPeerLoop` in interactive-loop.ts). Repo + * style: plain assert + console.log, run via `bun interactive-loop.smoke.ts`, non-zero exit on + * failure. No test framework. + * + * Drives the loop with a structural FakeMesh (captures the on(...) handlers so the test can emit + * incoming/mention-wake/wake, array-backed peek/drain inbox, controllable attention/channelMode/ + * pendingWake, recording setStatus/stop) and a fake host recording sendMessage. The 8 documented + * invariants of interactive-loop.ts are the spec; each is asserted below. + */ +import { formatInjection, ORIENTATION_BOOTSTRAP, type InboxItem } from "@cotal-ai/connector-core"; +import type { AttentionMode, ChannelMode, PresenceStatus } from "@cotal-ai/core"; +import { runPeerLoop, INCOMING, NUDGE, type PeerMesh, type PeerHost } from "./src/interactive-loop.ts"; + +function assert(cond: unknown, msg: string): asserts cond { + if (!cond) { + console.error(`FAIL: ${msg}`); + process.exit(1); + } +} + +/** Minimal InboxItem builder — required fields per @cotal-ai/connector-core agent.d.ts + * (id, ts, fromId, fromName, kind, mentionsMe, historical, text); channel added for kind:"channel". */ +function item(partial: Partial & Pick): InboxItem { + return { + ts: 0, + fromId: "u1", + fromName: "Alice", + kind: "dm", + mentionsMe: false, + historical: false, + text: `msg-${partial.id}`, + ...partial, + }; +} + +type MeshEvent = "incoming" | "mention-wake" | "wake"; + +/** A structural PeerMesh whose handlers, inbox, and modes the test controls directly. */ +class FakeMesh implements PeerMesh { + connected = true; + attention: AttentionMode = "open"; + inbox: InboxItem[] = []; + private _channelMode: ChannelMode | undefined = undefined; + private _pendingWake = 0; + readonly statusCalls: { status: PresenceStatus; activity?: string }[] = []; + stopCalls = 0; + private readonly handlers: Partial void>> = {}; + + on(event: "incoming" | "mention-wake", handler: (item: InboxItem) => void): void; + on(event: "wake", handler: () => void): void; + on(event: MeshEvent, handler: (item: InboxItem) => void): void { + this.handlers[event] = handler; + } + + /** Fire a captured handler as the mesh would. */ + emit(event: MeshEvent, it?: InboxItem): void { + const h = this.handlers[event]; + assert(h, `loop registered a "${event}" handler`); + h(it as InboxItem); + } + + peekInbox(): InboxItem[] { + return this.inbox; + } + drainInbox(limit?: number): InboxItem[] { + return this.inbox.splice(0, limit ?? this.inbox.length); + } + pendingWake(): number { + return this._pendingWake; + } + setPendingWake(n: number): void { + this._pendingWake = n; + } + setChannelMode(mode: ChannelMode | undefined): void { + this._channelMode = mode; + } + channelMode(_channel?: string): ChannelMode | undefined { + return this._channelMode; + } + async setStatus(status: PresenceStatus, activity?: string): Promise { + this.statusCalls.push({ status, activity }); + } + async stop(): Promise { + this.stopCalls++; + } +} + +interface SentCall { + message: { customType: string; content: string; display: boolean; details: unknown; attribution: "user" | "agent" }; + options: { deliverAs: "steer"; triggerTurn: true }; +} + +/** A fake host that records every sendMessage(message, options). */ +class FakeHost implements PeerHost { + readonly sent: SentCall[] = []; + sendMessage(message: SentCall["message"], options: SentCall["options"]): void { + this.sent.push({ message, options }); + } + get last(): SentCall { + return this.sent[this.sent.length - 1]; + } +} + +/** Assert a sendMessage call carries the fixed steer/turn envelope the loop always uses. */ +function assertEnvelope(call: SentCall, customType: string, ctx: string): void { + assert(call.message.customType === customType, `${ctx}: customType === ${customType}`); + assert(call.message.display === true, `${ctx}: display true`); + assert(call.message.attribution === "user", `${ctx}: attribution "user"`); + assert(JSON.stringify(call.message.details) === "{}", `${ctx}: details {}`); + assert(call.options.deliverAs === "steer", `${ctx}: deliverAs "steer"`); + assert(call.options.triggerTurn === true, `${ctx}: triggerTurn true`); +} + +// ---- 1. Directed drives when idle, with the correct message/options shape ---------------------- +{ + const mesh = new FakeMesh(); + const host = new FakeHost(); + runPeerLoop({ mesh, host }); + + // A DM: directed regardless of attention. + const dm = item({ id: "d1", kind: "dm", text: "hey there" }); + mesh.inbox = [dm]; + mesh.emit("incoming", dm); + assert(host.sent.length === 1, "1) DM drives one sendMessage when idle"); + assertEnvelope(host.last, INCOMING, "1/dm"); + const inj = formatInjection([dm]); + assert(inj && host.last.message.content.includes(inj), "1) content carries the formatted injection"); + + // A channel message with mentionsMe is also directed even when the channel is quiet + attention dnd. + const mesh2 = new FakeMesh(); + const host2 = new FakeHost(); + mesh2.attention = "dnd"; + mesh2.setChannelMode("quiet"); + runPeerLoop({ mesh: mesh2, host: host2 }); + const mention = item({ id: "c1", kind: "channel", channel: "general", mentionsMe: true, text: "@me look" }); + mesh2.inbox = [mention]; + mesh2.emit("incoming", mention); + assert(host2.sent.length === 1, "1) channel @mention drives even in dnd + quiet"); + assertEnvelope(host2.last, INCOMING, "1/mention"); + console.log("1) directed drives when idle OK ✅"); +} + +// ---- 2. Ambient gating by attention + quiet channelMode ---------------------------------------- +{ + // open + non-quiet → drives. + { + const mesh = new FakeMesh(); + const host = new FakeHost(); + mesh.attention = "open"; + mesh.setChannelMode(undefined); // non-quiet channel + runPeerLoop({ mesh, host }); + const it = item({ id: "a1", kind: "channel", channel: "general", mentionsMe: false, text: "ambient chatter" }); + mesh.inbox = [it]; + mesh.emit("incoming", it); + assert(host.sent.length === 1, "2) ambient drives when open + non-quiet"); + } + // dnd → does NOT drive (buffered). + for (const att of ["dnd", "focus"] as AttentionMode[]) { + const mesh = new FakeMesh(); + const host = new FakeHost(); + mesh.attention = att; + mesh.setChannelMode(undefined); // non-quiet channel + runPeerLoop({ mesh, host }); + const it = item({ id: "a2", kind: "channel", channel: "general", mentionsMe: false, text: "ambient" }); + mesh.inbox = [it]; + mesh.emit("incoming", it); + assert(host.sent.length === 0, `2) ambient does NOT drive when attention=${att}`); + } + // open but quiet channel → does NOT drive. + { + const mesh = new FakeMesh(); + const host = new FakeHost(); + mesh.attention = "open"; + mesh.setChannelMode("quiet"); + runPeerLoop({ mesh, host }); + const it = item({ id: "a3", kind: "channel", channel: "quietc", mentionsMe: false, text: "ambient" }); + mesh.inbox = [it]; + mesh.emit("incoming", it); + assert(host.sent.length === 0, "2) ambient does NOT drive on a quiet channel even when open"); + } + console.log("2) ambient gating by attention + quiet channelMode OK ✅"); +} + +// ---- 3. No-interrupt while busy ---------------------------------------------------------------- +{ + const mesh = new FakeMesh(); + const host = new FakeHost(); + const loop = runPeerLoop({ mesh, host }); + loop.onAgentStart(); // turn in progress + const dm = item({ id: "b1", kind: "dm", text: "urgent" }); + mesh.inbox = [dm]; + mesh.emit("incoming", dm); // directed, but busy + assert(host.sent.length === 0, "3) directed item during a turn does NOT interrupt (buffers)"); + const amb = item({ id: "b2", kind: "channel", channel: "general", mentionsMe: false, text: "chatter" }); + mesh.inbox = [dm, amb]; + mesh.emit("incoming", amb); + assert(host.sent.length === 0, "3) ambient item during a turn does NOT interrupt"); + console.log("3) no-interrupt while busy OK ✅"); +} + +// ---- 4. Ack-on-surface by id, incl. eviction/reorder cases ------------------------------------- +{ + // 4a. Partial: front stays matched for a leading prefix, then diverges → only the prefix drains. + { + const mesh = new FakeMesh(); + const host = new FakeHost(); + const loop = runPeerLoop({ mesh, host }); + const a = item({ id: "s1" }), b = item({ id: "s2" }), c = item({ id: "s3" }); + mesh.inbox = [a, b, c]; + mesh.emit("wake"); // surfaces [s1,s2,s3], busy=true + assert(host.sent.length === 1, "4a) wake surfaced the batch"); + // Before turn end, the front's 3rd slot is replaced (c evicted, x arrived at that position). + const x = item({ id: "sX" }); + mesh.inbox = [a, b, x]; + loop.onAgentEnd(); + assert(mesh.inbox.map((i) => i.id).join(",") === "sX", "4a) only leading matched prefix [s1,s2] drained; sX remains"); + } + // 4b. Non-matching front: front[0] no longer matches → NOTHING drained (all redelivered). + { + const mesh = new FakeMesh(); + const host = new FakeHost(); + const loop = runPeerLoop({ mesh, host }); + const a = item({ id: "t1" }), b = item({ id: "t2" }); + mesh.inbox = [a, b]; + mesh.emit("wake"); // surfaces [t1,t2] + // Front-eviction shifts our surfaced prefix out entirely. + mesh.inbox = [b]; + loop.onAgentEnd(); + assert(mesh.inbox.map((i) => i.id).join(",") === "t2", "4b) non-matching front drains nothing; t2 redelivered"); + } + // 4c. Ack only happens on onAgentEnd, not at surface time. + { + const mesh = new FakeMesh(); + const host = new FakeHost(); + const loop = runPeerLoop({ mesh, host }); + const a = item({ id: "u1" }); + mesh.inbox = [a]; + mesh.emit("wake"); + assert(mesh.inbox.length > 0, "4c) surfacing does NOT drain the inbox"); + loop.onAgentEnd(); + assert(mesh.inbox.length === 0, "4c) onAgentEnd drains the still-matching surfaced batch"); + } + console.log("4) ack-on-surface by id (incl. eviction/reorder) OK ✅"); +} + +// ---- 5. Flush next after turn when pendingWake > 0 --------------------------------------------- +{ + const mesh = new FakeMesh(); + const host = new FakeHost(); + const loop = runPeerLoop({ mesh, host }); + loop.onAgentStart(); // busy + // A batch arrives mid-turn; buffered (no send), and the mesh reports it as pending. + const buffered = item({ id: "f1", kind: "dm", text: "arrived during turn" }); + mesh.inbox = [buffered]; + mesh.emit("incoming", buffered); + assert(host.sent.length < 1, "5) buffered during turn — no send yet"); + mesh.setPendingWake(1); + loop.onAgentEnd(); + assert(host.sent.length === 1, "5) onAgentEnd flushes the buffered batch when pendingWake > 0"); + assertEnvelope(host.last, INCOMING, "5/flush"); + + // And when pendingWake === 0, onAgentEnd does NOT drive spuriously. + const mesh2 = new FakeMesh(); + const host2 = new FakeHost(); + const loop2 = runPeerLoop({ mesh: mesh2, host: host2 }); + loop2.onAgentStart(); + mesh2.inbox = [item({ id: "f2", kind: "dm" })]; + mesh2.setPendingWake(0); + loop2.onAgentEnd(); + assert(host2.sent.length === 0, "5) onAgentEnd does not flush when pendingWake === 0"); + console.log("5) flush-next after turn when pendingWake > 0 OK ✅"); +} + +// ---- 6. First turn primed once (orientation bootstrap) ----------------------------------------- +{ + const mesh = new FakeMesh(); + const host = new FakeHost(); + const loop = runPeerLoop({ mesh, host }); + + const first = item({ id: "p1", kind: "dm", text: "first" }); + mesh.inbox = [first]; + mesh.emit("incoming", first); + loop.onAgentEnd(); // ack + no pending → no reflow + assert(host.sent[0].message.content.startsWith(ORIENTATION_BOOTSTRAP), "6) first delivery is prefixed with the orientation bootstrap"); + + const second = item({ id: "p2", kind: "dm", text: "second" }); + mesh.inbox = [second]; + mesh.emit("incoming", second); + assert(!host.sent[1].message.content.startsWith(ORIENTATION_BOOTSTRAP), "6) later deliveries are NOT re-primed"); + console.log("6) first turn primed once OK ✅"); +} + +// ---- 7. mention-wake drives a NUDGE naming the sender + cotal_inbox ------------------------------ +{ + const mesh = new FakeMesh(); + const host = new FakeHost(); + const loop = runPeerLoop({ mesh, host }); + const mw = item({ id: "m1", kind: "channel", channel: "general", fromName: "Bob", mentionsMe: true, text: "@me" }); + mesh.emit("mention-wake", mw); + assert(host.sent.length === 1, "7) mention-wake drives one message when idle"); + assertEnvelope(host.last, NUDGE, "7/nudge"); + const content = host.last.message.content; + assert(content.includes("Bob"), "7) nudge names the sender"); + assert(content.includes("cotal_inbox"), "7) nudge says to use cotal_inbox"); + assert(content.includes("#general"), "7) nudge names the channel"); + + // mention-wake while busy does NOT drive. + const mesh2 = new FakeMesh(); + const host2 = new FakeHost(); + const loop2 = runPeerLoop({ mesh: mesh2, host: host2 }); + loop2.onAgentStart(); + mesh2.emit("mention-wake", mw); + assert(host2.sent.length === 0, "7) mention-wake during a turn does NOT interrupt"); + console.log("7) mention-wake NUDGE OK ✅"); +} + +// ---- 8. shutdown stops the mesh ---------------------------------------------------------------- +{ + const mesh = new FakeMesh(); + const host = new FakeHost(); + const loop = runPeerLoop({ mesh, host }); + await loop.shutdown(); + assert(mesh.stopCalls === 1, "8) shutdown calls mesh.stop() exactly once"); + console.log("8) shutdown stops mesh OK ✅"); +} + +console.log("\nCOTAL-MESH LOOP SMOKE OK ✅"); +process.exit(0); diff --git a/extensions/connector-oh-my-pi/oh-my-pi-extension.smoke.ts b/extensions/connector-oh-my-pi/oh-my-pi-extension.smoke.ts new file mode 100644 index 00000000..b9148b22 --- /dev/null +++ b/extensions/connector-oh-my-pi/oh-my-pi-extension.smoke.ts @@ -0,0 +1,137 @@ +/** + * Smoke test for the cotal-mesh OMP extension. Repo style: plain assert + console.log, run via + * `bun cotal-mesh.smoke.ts`, non-zero exit on failure. No test framework. + * + * Drives the extension factory with a FAKE ExtensionAPI (records tool registrations, event + * handlers, and sendMessage calls) so the whole load path is exercised with no NATS connection. + * Asserts: inert without identity; with identity it registers the cotal_* tool surface, subscribes + * to the lifecycle events, and cotal_inbox is read-only. + */ +import cotalMesh from "./src/extension.ts"; +import * as zodV4 from "zod/v4"; +import { MeshAgent } from "@cotal-ai/connector-core"; + +function assert(cond: unknown, msg: string): asserts cond { + if (!cond) { + console.error(`FAIL: ${msg}`); + process.exit(1); + } +} + +interface RegisteredTool { + name: string; + label: string; + description: string; + parameters: unknown; + approval?: string; + execute: (...a: unknown[]) => Promise<{ content: { type: string; text: string }[]; details: unknown }>; +} + +/** A fake ExtensionAPI that records everything the factory does. */ +function fakePi() { + const tools = new Map(); + const events = new Map unknown>(); + const sent: { message: Record; options: Record }[] = []; + const z = zodV4.z; + const pi = { + zod: zodV4, + logger: console, + registerTool: (t: RegisteredTool) => tools.set(t.name, t), + on: (event: string, handler: (e: unknown) => unknown) => events.set(event, handler), + sendMessage: (message: Record, options: Record) => + sent.push({ message, options }), + registerCommand: () => {}, + setLabel: () => {}, + }; + return { pi, tools, events, sent, z }; +} + +// ---- 1. inert without identity ------------------------------------------------ +delete process.env.COTAL_NAME; +delete process.env.COTAL_LINK; +delete process.env.COTAL_AGENT_FILE; +{ + const { pi, tools, events } = fakePi(); + cotalMesh(pi as never); + assert(tools.size === 0, "no identity → registers no tools"); + assert(events.size === 0, "no identity → subscribes to no events"); + console.log("1) inert without identity OK ✅"); +} + +// ---- 2. with identity: tools + events registered ------------------------------ +process.env.COTAL_NAME = "smoke-peer"; +process.env.COTAL_SERVERS = "nats://127.0.0.1:4222"; // never actually connected in this smoke +{ + const { pi, tools, events } = fakePi(); + cotalMesh(pi as never); + + // The shared cotal_* surface is registered. + for (const name of ["cotal_orientation", "cotal_roster", "cotal_inbox", "cotal_send", "cotal_dm", "cotal_status"]) { + assert(tools.has(name), `registers ${name}`); + } + console.log(` registered ${tools.size} cotal_* tools`); + + // Lifecycle events are subscribed for presence + turn tracking. + for (const ev of ["agent_start", "agent_end", "tool_execution_start", "session_shutdown"]) { + assert(events.has(ev), `subscribes to ${ev}`); + } + console.log("2) identity → tools + events registered OK ✅"); + + // ---- 3. cotal_inbox is read-only (peek) ----------------------------------- + const inbox = tools.get("cotal_inbox")!; + assert(inbox.approval === "read", "cotal_inbox is approval:read"); + // Its schema takes no args (peek is forced), so execute must run without throwing on {}. + const inboxResult = await inbox.execute("", {}, undefined, undefined, undefined); + assert(inboxResult.content.length === 1, "cotal_inbox execute returns one content part"); + assert(!inboxResult.content[0].text.startsWith("⚠"), "cotal_inbox execute does not error on empty inbox"); + console.log("3) cotal_inbox read-only OK ✅"); + + // The factory started a MeshAgent with a background reconnect loop; fire session_shutdown to stop + // it so the smoke process can exit (no live mesh in this test). + const shutdown = events.get("session_shutdown"); + if (shutdown) await shutdown(undefined); +} + +// ---- 4. session_start gates the mesh-join on ctx.hasUI ------------------------ +// A task/print/RPC subagent inherits the parent's COTAL_* env, so hasIdentity() alone would make +// every subagent a stray same-named peer. The extension defers the mesh-join to session_start and +// only calls agent.start() when ctx.hasUI is true (interactive/top-level). Observe agent.start via a +// spy on MeshAgent.prototype.start — no-op'd so no real NATS reconnect loop spins — then restore it. +// Each branch loads a fresh factory: the `started` guard is per-instance, so one instance can't be +// re-driven. Env from block 2 (COTAL_NAME/COTAL_SERVERS) is still set → identity is present. +{ + const origStart = MeshAgent.prototype.start; + let startCalls = 0; + MeshAgent.prototype.start = function () { + startCalls++; + }; + try { + // (a) non-interactive session (subagent/print/RPC): hasUI:false → stays off the mesh. + { + const { pi, events } = fakePi(); + cotalMesh(pi as never); + const sessionStart = events.get("session_start") as + | ((event: unknown, ctx: { hasUI: boolean }) => unknown) + | undefined; + assert(sessionStart, "identity → subscribes to session_start"); + startCalls = 0; + await sessionStart(undefined, { hasUI: false }); + assert(startCalls === 0, "hasUI:false → agent.start NOT invoked (subagent stays off mesh)"); + } + // (b) interactive top-level session: hasUI:true → joins the mesh. + { + const { pi, events } = fakePi(); + cotalMesh(pi as never); + const sessionStart = events.get("session_start") as (event: unknown, ctx: { hasUI: boolean }) => unknown; + startCalls = 0; + await sessionStart(undefined, { hasUI: true }); + assert(startCalls === 1, "hasUI:true → agent.start invoked (interactive session joins)"); + } + } finally { + MeshAgent.prototype.start = origStart; + } + console.log("4) session_start + hasUI gates mesh-join OK ✅"); +} + +console.log("\nCOTAL-MESH EXTENSION SMOKE OK ✅"); +process.exit(0); diff --git a/extensions/connector-oh-my-pi/oh-my-pi-peer.smoke.ts b/extensions/connector-oh-my-pi/oh-my-pi-peer.smoke.ts new file mode 100644 index 00000000..031ca3db --- /dev/null +++ b/extensions/connector-oh-my-pi/oh-my-pi-peer.smoke.ts @@ -0,0 +1,707 @@ +/** + * Smoke test for the @cotal-ai/oh-my-pi peer loop (no NATS/LLM needed): drives + * `runPeerLoop({ mesh, session })` against a fake MeshAgent and a scripted stub session, and + * asserts the reply-routing / scope-isolation / ack-on-surface invariants the native embed + * relies on — including the oh-my-pi fork divergence (an `agent_end` carries no `willRetry`, + * so it is always the turn's terminal event). + * + * pnpm smoke:oh-my-pi + */ +import { EventEmitter } from "node:events"; +import { runPeerLoop, type PeerMesh, type PeerSession } from "./src/loop.js"; +import type { InboxItem } from "@cotal-ai/connector-core"; +import type { AgentSessionEvent } from "@oh-my-pi/pi-coding-agent/session/agent-session"; + +function assert(cond: boolean, msg: string): void { + if (!cond) throw new Error(`FAIL: ${msg}`); +} + +/** Drain all pending microtasks (a macrotask tick) so an async prompt/steer callback chain has + * settled before we assert — hop-count-independent, unlike a single `await Promise.resolve()`. */ +const drain = (): Promise => { + const { promise, resolve } = Promise.withResolvers(); + setTimeout(resolve, 0); + return promise; +}; + +// --- message factories ------------------------------------------------------------------- +function dm(id: string, fromId: string, text = id): InboxItem { + return { id, ts: 0, fromId, fromName: fromId, kind: "dm", mentionsMe: false, historical: false, text }; +} +function chan( + id: string, + fromId: string, + opts: { mentionsMe?: boolean; channel?: string; text?: string } = {}, +): InboxItem { + const { mentionsMe = true, channel = "general", text = id } = opts; + return { + id, + ts: 0, + fromId, + fromName: fromId, + kind: "channel", + channel, + mentionsMe, + historical: false, + text, + }; +} +const framed = (i: InboxItem): string => `from ${i.fromName} via ${i.kind}: ${i.text}`; + +// --- scripted session-event factories ---------------------------------------------------- +const START: AgentSessionEvent = { type: "agent_start" }; +const toolStart = (toolName: string): AgentSessionEvent => + ({ type: "tool_execution_start", toolCallId: "t", toolName, args: {} }) as AgentSessionEvent; +/** A NORMAL `agent_end` — the oh-my-pi fork has no `willRetry` field, so this is terminal. */ +const end = (reply?: string): AgentSessionEvent => + ({ + type: "agent_end", + messages: reply ? [{ role: "assistant", content: [{ type: "text", text: reply }] }] : [], + }) as AgentSessionEvent; + +// --- fakes ------------------------------------------------------------------------------- +interface Sent { + text: string; + channel?: string; +} +interface DirectMsg { + target: string; + text: string; +} + +/** Mirrors the MeshAgent slice the loop uses: an EventEmitter (`incoming`/`wake`) plus a + * stream-backed inbox (drainInbox acks by front position, ackInbox acks by id, absent id is a + * no-op) and record-only presence/delivery. */ +class FakeMesh extends EventEmitter implements PeerMesh { + readonly id = "me"; + items: InboxItem[] = []; + acked: InboxItem[] = []; + statuses: { status: string; activity?: string }[] = []; + sends: Sent[] = []; + dms: DirectMsg[] = []; + + 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; + } + async setStatus(status: "idle" | "waiting" | "working", activity?: string): Promise { + this.statuses.push({ status, activity }); + } + async send(text: string, channel?: string): Promise { + this.sends.push({ text, channel }); + return {}; + } + async dm(target: string, text: string): Promise { + this.dms.push({ target, text }); + return {}; + } + + /** Deliver a message: buffer it, then wake the loop on the given mesh event. */ + arrive(item: InboxItem, event: "incoming" | "wake" = "incoming"): void { + this.items.push(item); + this.emit(event); + } +} + +/** A scripted oh-my-pi session: `prompt`/`steer` only RECORD (the test drives the + * `agent_start`/`tool_execution_*`/`agent_end` stream explicitly via {@link emit}), so a turn's + * lifecycle is fully controllable — a live turn can be held open while same-scope peers are + * folded in before it ends. */ +class StubSession implements PeerSession { + prompts: string[] = []; + steers: string[] = []; + aborted = 0; + disposed = 0; + /** Opt-in: value `prompt()` resolves to. Default `true` (session accepts the wake). Set + * `false` to simulate a DECLINED wake (no agent_start/agent_end follows). */ + promptResult = true; + /** Opt-in: when `true`, `steer()` records then REJECTS (the fold couldn't reach the model + * turn). Default `false` (records + resolves as before). */ + steerReject = false; + /** Opt-in: when `true`, `dispose()` records then REJECTS (an SDK teardown that throws). + * Default `false` (records + resolves as before, so tests 1-9 are unchanged). */ + disposeReject = false; + /** Opt-in: when `true`, `abort()` records then REJECTS (an SDK abort that throws). Default + * `false` (records + resolves as before, so tests 1-14 are unchanged). Mirror of {@link disposeReject}. */ + abortReject = false; + /** Opt-in (test #6, sync-throw): when `true`, `abort()` increments `aborted` then THROWS + * SYNCHRONOUSLY — the throw happens BEFORE a promise is returned, so a bare `.catch()` on the + * call cannot catch it; only a surrounding try/catch in shutdown() does. Default `false` (tests + * 1-15 unchanged). Distinct from {@link abortReject} (async rejection). */ + abortThrowSync = false; + /** Opt-in (test 14): when `true`, `steer()` records then returns a promise that NEVER settles, + * so a fold stays pending forever. Exercises the BOUNDED deferred commit — the terminal commit + * must still fire (the macrotask boundary wins the race). Default `false` (tests 1-13 unchanged). */ + steerHang = false; + /** Opt-in (test 12): when `true`, `steer()` records then returns a PENDING promise whose reject + * fn is pushed onto {@link rejectSteer}, so the test controls exactly WHEN the fold settles. + * Firing the reject after the fold's turn committed exercises the generation guard. Default + * `false` (tests 1-11/13/14 keep the one-hop resolve/reject path). */ + deferSteer = false; + /** Captured reject fns for deferred steers (see {@link deferSteer}): `rejectSteer[i]()` rejects + * the i-th folded steer on demand. Empty unless `deferSteer` is set. */ + rejectSteer: (() => void)[] = []; + /** Opt-in (test #5, slow-accept): when `true`, `steer()` records then returns a promise that + * resolves on `setTimeout(resolve, 5)` — a REAL ~5ms accept (a genuine settle doing work, e.g. a + * future images-carrying steer's normalize/resize), long past the first microtask. 5ms is chosen + * so the race is decided by DELAY MAGNITUDE, not timer-registration order: it LOSES to a 0ms + * boundary (0 < 5 → timeout fires first → fold un-surfaced, finding #5) but WINS against a 50ms + * human-scale boundary (5 < 50 → allSettled resolves first → fold acked). A `setTimeout(0)` accept + * would be non-load-bearing here: registered at fold-time it always beats a later-registered 0ms + * boundary, so it could never go red. Default `false` (tests 1-15 keep the microtask resolve). */ + steerSlowAccept = false; + private listeners: ((event: AgentSessionEvent) => void)[] = []; + + subscribe(listener: (event: AgentSessionEvent) => void): () => void { + this.listeners.push(listener); + return () => {}; + } + emit(event: AgentSessionEvent): void { + for (const l of this.listeners) l(event); + } + async prompt(text: string): Promise { + this.prompts.push(text); + return this.promptResult; + } + steer(text: string): Promise { + this.steers.push(text); + // A never-settling steer (test 14): the fold stays pending; the deferred commit must still + // fire off its bounded macrotask boundary, so a hung steer can't wedge the turn. + if (this.steerHang) return new Promise(() => {}); + // A test-controlled deferred steer (test 12): capture the reject so the test can settle the + // fold at a chosen moment (e.g. AFTER its turn committed, to exercise the generation guard). + if (this.deferSteer) { + const { promise, reject } = Promise.withResolvers(); + this.rejectSteer.push(() => reject(new Error("steer rejected (deferred)"))); + return promise; + } + // A real ~5ms accept (test #5, slow-accept): the steer resolves on setTimeout(resolve, 5) — + // genuine settle work, well past the first microtask. It loses to a 0ms boundary (finding #5's + // window) but wins against a 50ms human-scale boundary, so commitAfterSteers awaits and acks it. + // 5ms (a real delay, not setTimeout(0)) makes the race decided by magnitude, not registration + // order — the only shape that actually goes red when the timeout is reverted to 0. + if (this.steerSlowAccept) { + const { promise, resolve } = Promise.withResolvers(); + setTimeout(resolve, 5); + return promise; + } + // A non-async return so a rejected steer settles in ONE microtask (an `async` method would + // adopt the thenable and take extra ticks) — the loop's `.catch → unsurface` then runs after + // a single `await Promise.resolve()`, matching how the real session rejects. + return this.steerReject ? Promise.reject(new Error("steer rejected")) : Promise.resolve(); + } + abort(): Promise { + this.aborted++; + // A SYNCHRONOUS throw (test #6): the throw happens before any promise is returned, so a bare + // `.catch()` on the call never sees it — only shutdown()'s surrounding try/catch does. This is + // the seam a non-conforming adapter could hit; the per-call try/catch must still run dispose(). + if (this.abortThrowSync) throw new Error("abort sync-throw"); + // A non-async return so a rejected abort settles in ONE microtask (mirrors `dispose`) — the + // loop's `.catch(log)` then swallows it and `shutdown()` still proceeds to dispose()/resolve. + return this.abortReject ? Promise.reject(new Error("abort rejected")) : Promise.resolve(); + } + dispose(): Promise { + this.disposed++; + // A non-async return so a rejected dispose settles in ONE microtask (mirrors `steer`) — the + // loop's `.catch(log)` then swallows it and `shutdown()` still resolves. + return this.disposeReject ? Promise.reject(new Error("dispose rejected")) : Promise.resolve(); + } +} + +const ids = (xs: InboxItem[]): string => xs.map((x) => x.id).join(","); +const last = (xs: T[]): T => xs[xs.length - 1]; + +// 1) reply routing by scope: a DM is answered privately; a channel message on the channel; +// a DM is NEVER broadcast to a channel and vice-versa. +{ + // (a) DM → dm, never send + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("q1", "alice", "hi")]; + runPeerLoop({ mesh, session }); + assert(session.prompts[0] === framed(dm("q1", "alice", "hi")), "DM origin framed + prompted"); + session.emit(START); + session.emit(end("hello alice")); + assert(mesh.dms.length === 1 && mesh.dms[0].target === "alice", "DM answered privately to sender"); + assert(mesh.dms[0].text === "hello alice", "DM reply is this turn's text"); + assert(mesh.sends.length === 0, "a DM is never broadcast to a channel"); +} +{ + // (b) channel (mentions us) → send on that channel, never dm + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [chan("c1", "bob", { channel: "eng", text: "hey me" })]; + runPeerLoop({ mesh, session }); + session.emit(START); + session.emit(end("hi eng")); + assert(mesh.sends.length === 1 && mesh.sends[0].channel === "eng", "channel msg answered on its channel"); + assert(mesh.sends[0].text === "hi eng", "channel reply is this turn's text"); + assert(mesh.dms.length === 0, "a channel reply is never sent as a private DM"); +} +console.log("1) reply routing by scope OK ✅"); + +// 2) actionable filter: own echoes (fromId === mesh.id) and ambient channel chatter (not +// mentionsMe) are dropped — acked, never prompted, never answered. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("echo", "me"), chan("amb", "bob", { mentionsMe: false })]; + runPeerLoop({ mesh, session }); + assert(session.prompts.length === 0, "no actionable message → nothing prompted"); + assert(ids(mesh.acked) === "echo,amb", "own echo + ambient chatter are ack-dropped"); + assert(mesh.dms.length === 0 && mesh.sends.length === 0, "neither is ever answered"); + assert(last(mesh.statuses).status === "idle", "empty of actionable → idle"); +} +console.log("2) actionable filter OK ✅"); + +// 3) same-scope fold: two same-scope messages arriving during a live turn fold in via steer; +// a different-scope message breaks contiguity and opens its own next turn. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("a1", "alice", "q1")]; + runPeerLoop({ mesh, session }); + session.emit(START); // turn is now live (streaming) + mesh.arrive(dm("a2", "alice", "q2")); // same scope → folded via steer + mesh.arrive(dm("b1", "bob", "qb")); // different scope → NOT folded, waits its turn + assert(session.steers.length === 1 && session.steers[0] === framed(dm("a2", "alice", "q2")), + "same-scope peer folded via steer; cross-scope not folded"); + assert(mesh.items.some((x) => x.id === "b1"), "cross-scope message stays on the stream"); + await Promise.resolve(); // let the folded steer's .then (pendingSteerIds.delete) confirm before commit + session.emit(end("ans")); // terminal → commit alice run, deliver, pump next scope + assert(ids(mesh.acked) === "a1,a2", "the surfaced same-scope run [a1,a2] was acked on end"); + assert(mesh.dms.length === 1 && mesh.dms[0].target === "alice", "one reply delivered to the shared scope"); + assert(session.prompts[1] === framed(dm("b1", "bob", "qb")), "cross-scope msg opens its own next turn"); +} +console.log("3) same-scope fold OK ✅"); + +// 4) ack-on-surface: a surfaced message is acked ONLY on agent_end; an in-flight turn on +// shutdown abandons (no ack → redelivers). +{ + // (a) acked only on agent_end + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("s1", "alice")]; + runPeerLoop({ mesh, session }); + assert(mesh.acked.length === 0, "surfaced-but-unacked before the turn completes"); + session.emit(START); + assert(mesh.acked.length === 0, "agent_start does not ack"); + session.emit(end("done")); + assert(ids(mesh.acked) === "s1", "agent_end acks the surfaced run"); +} +{ + // (b) in-flight shutdown abandons — no ack, redelivers + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("s1", "alice")]; + const loop = runPeerLoop({ mesh, session }); + session.emit(START); // turn in flight + await loop.shutdown(); + assert(mesh.acked.length === 0, "in-flight shutdown acks nothing → redeliver"); + assert(ids(mesh.items) === "s1", "the in-flight message stays on the stream"); + assert(session.aborted === 1, "shutdown aborts the live turn"); + assert(session.disposed === 1, "shutdown disposes the session"); +} +console.log("4) ack-on-surface OK ✅"); + +// 5) presence mapping: agent_start → working(thinking), tool_execution_start → +// working(running ), agent_end → idle (via mesh.setStatus). +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("p1", "alice")]; + runPeerLoop({ mesh, session }); + session.emit(START); + assert(last(mesh.statuses).status === "working" && last(mesh.statuses).activity === "thinking", + "agent_start → working(thinking)"); + session.emit(toolStart("bash")); + assert(last(mesh.statuses).status === "working" && last(mesh.statuses).activity === "running bash", + "tool_execution_start → working(running )"); + session.emit(end("ok")); + assert(last(mesh.statuses).status === "idle", "agent_end (inbox now empty) → idle"); +} +console.log("5) presence mapping OK ✅"); + +// 6) oh-my-pi fork specific: an `agent_end` has NO `willRetry`; assert a normal agent_end is +// treated as terminal — it commits, delivers, and pumps the next scope's turn. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("a1", "alice", "qa"), dm("b1", "bob", "qb")]; // two distinct scopes, FIFO + runPeerLoop({ mesh, session }); + assert(session.prompts[0] === framed(dm("a1", "alice", "qa")), "first turn starts on the front DM"); + session.emit(START); // fold pass: bob is a different scope → not folded + assert(session.steers.length === 0, "cross-scope bob is not folded into alice's turn"); + const terminal = end("ra"); + assert(!("willRetry" in terminal), "the oh-my-pi agent_end carries no willRetry flag"); + session.emit(terminal); // terminal: commit + deliver + pump next + assert(ids(mesh.acked) === "a1", "terminal agent_end committed alice's run"); + assert(mesh.dms.length === 1 && mesh.dms[0].target === "alice", "terminal agent_end delivered alice's reply"); + assert(session.prompts[1] === framed(dm("b1", "bob", "qb")), "terminal agent_end pumped the next scope's turn"); + session.emit(START); + session.emit(end("rb")); + assert(ids(mesh.acked) === "a1,b1" && mesh.dms.length === 2, "the pumped bob turn also commits + delivers"); +} +console.log("6) agent_end terminal without willRetry OK ✅"); + +// 7) declined prompt completes the turn (no wedge): prompt() resolving false means the session +// DECLINED the wake — no agent_start/agent_end follows. The turn must still complete (ack the +// origin, go idle) and the NEXT message must pump; pre-fix the origin was never acked and the +// peer wedged with an in-flight-but-never-streaming turn. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("d1", "alice", "q")]; + session.promptResult = false; // the session declines the wake + runPeerLoop({ mesh, session }); // pump fires → prompt called → resolves false, no START emitted + await drain(); // let the async prompt().then chain settle (2 hops) before asserting + assert(session.prompts.length === 1, "the declined origin was prompted exactly once"); + assert(ids(mesh.acked) === "d1", "declined origin committed (acked, drop/no-retry) — not wedged"); + assert(last(mesh.statuses).status === "idle", "the peer went idle after the decline"); + mesh.arrive(dm("d2", "bob", "q2")); // a fresh message must pump — the peer is not wedged + await drain(); + assert(session.prompts[1] === framed(dm("d2", "bob", "q2")), "a fresh turn pumped after the decline"); +} +console.log("7) declined prompt completes the turn (no wedge) OK ✅"); + +// 8) rejected steer un-surfaces its message (redelivers, not lost): a folded same-scope message +// is surfaced then steered; if steer() REJECTS the message never reached the model turn, so the +// terminal commit must NOT ack it — it stays on the stream and redelivers. Pre-fix commit acked +// the undelivered fold (acked === "a1,a2") and the message was lost from the stream. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("a1", "alice", "q1")]; + session.steerReject = true; // the fold's steer will reject (never reaches the model turn) + runPeerLoop({ mesh, session }); + session.emit(START); // turn live, streaming + mesh.arrive(dm("a2", "alice", "q2")); // same scope → foldSameScope → extend surfaces a2, steer rejects + await drain(); // let the steer().catch → turn.unsurface(a2) run + session.emit(end("ans")); // terminal → commit acks the surfaced run + assert(ids(mesh.acked) === "a1", "only the delivered origin a1 acked; the rejected fold a2 is not"); + assert(mesh.items.some((x) => x.id === "a2"), "a2 stays on the stream (redelivers, not lost)"); + assert(session.steers.length === 1, "the fold attempted the steer exactly once"); +} +console.log("8) rejected steer un-surfaces its message (redelivers) OK ✅"); + +// 9) the steer-ack RACE (agent_end commits before the rejection's .catch runs): the stricter +// sibling of test 8. Test 8 drains BEFORE agent_end, so the rejected fold's +// `.catch → unsurface` has already run and commit() sees a2 gone — it passes even on the +// intermediate c09b21e (plain `.catch(→unsurface)` fold + immediate commit()). Here we do NOT +// drain at the critical point: we emit agent_end in the SAME tick the steer rejected, so +// commit() runs while a2's rejection `.catch` is still a queued microtask. Only the final fix +// (7c73207: un-surface every still-pending fold in agent_end, before commit) survives — on +// c09b21e commit() acks a2 before the late `.catch` (which then no-ops on a reset turn) and a2 +// is lost. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("a1", "alice", "q1")]; + session.steerReject = true; // the fold's steer will reject (never reaches the model turn) + runPeerLoop({ mesh, session }); + session.emit(START); // turn live, streaming + mesh.arrive(dm("a2", "alice", "q2")); // same scope → foldSameScope surfaces a2, steer() rejects + // RACE WINDOW: do NOT drain. Emit agent_end immediately — commit() runs while a2's rejection + // `.catch` is still queued, so only the final fix's in-agent_end un-surface saves a2. + session.emit(end("ans")); // terminal → commit + await drain(); // now let the steer rejection's `.catch` settle before asserting + assert(ids(mesh.acked) === "a1", + "RACE: only the delivered origin a1 acked; the rejected fold a2 is not (c09b21e acks a1,a2)"); + assert(mesh.items.some((x) => x.id === "a2"), + "RACE: a2 stayed on the stream → redelivers (c09b21e loses it)"); + assert(session.steers.length === 1, "the fold attempted the steer exactly once"); +} +console.log("9) steer-ack race: agent_end before rejection settles does not ack the fold OK ✅"); + +// 10) a rejecting dispose() must NOT reject loop.shutdown(): peer.ts does `await loop.shutdown()` +// THEN `await mesh.stop()`. If shutdown() rejects (an SDK dispose() that throws), mesh.stop() +// is skipped → the peer leaves a ghost presence on the mesh. The final fix +// (`await session.dispose().catch(log)`) swallows the dispose failure so shutdown() resolves; +// on c09b21e (bare `await session.dispose()`) it rejects. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("s1", "alice")]; + session.disposeReject = true; // the SDK teardown will throw + const loop = runPeerLoop({ mesh, session }); + // Complete a turn so turn.inFlight is false at shutdown and it goes straight to the dispose() line. + session.emit(START); + await drain(); + session.emit(end("x")); + await drain(); + let resolved = false; + try { + await loop.shutdown(); + resolved = true; + } catch { + resolved = false; + } + assert(resolved === true, + "loop.shutdown() RESOLVED despite the rejecting dispose (c09b21e rejects → mesh.stop skipped)"); + assert(session.disposed === 1, "dispose was still attempted exactly once"); +} +console.log("10) rejecting dispose does not reject shutdown (mesh.stop still runs) OK ✅"); + +// 11) an ACCEPTED steer is ACKED, not redelivered (the exact inverse of test 9): a same-scope +// fold whose steer() RESOLVES (the session accepted the message into its turn) must be acked +// even when agent_end fires in the SAME tick — before the accept's `.then(delete)` microtask +// has flushed. The deferred commit awaits the still-pending fold, sees it accepted, and acks +// it. Un-acking an accepted fold would REDELIVER a message the model already got (the mesh +// dedups only ACKED ids, so an un-acked id re-surfaces to the model). This is the greptile +// finding: 6f82f76 un-surfaces every still-pending fold in agent_end unconditionally, so an +// accepted-but-not-yet-flushed fold is dropped from the ack set → double delivery. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("a1", "alice", "q1")]; // steer ACCEPTS (default steerReject=false) + runPeerLoop({ mesh, session }); + session.emit(START); // turn live, streaming + mesh.arrive(dm("a2", "alice", "q2")); // same scope → fold; steer() RESOLVES, its .then(delete) queued + // RACE WINDOW: do NOT drain. Emit agent_end while a2's accept `.then` is still a queued microtask, + // so pendingSteers is non-empty → the commit is DEFERRED until the accept settles. + session.emit(end("ans")); + await drain(); // the deferred commit awaits the accept (microtask) then commits within this tick + assert(ids(mesh.acked) === "a1,a2", + "ACCEPTED fold a2 is acked with the origin (6f82f76 acks only a1 → a2 redelivers)"); + assert(!mesh.items.some((x) => x.id === "a2"), + "a2 left the stream (acked, won't redeliver); 6f82f76 leaves it → double delivery"); + assert(session.steers.length === 1, "the fold attempted the steer exactly once"); +} +console.log("11) accepted steer is acked, not redelivered OK ✅"); + +// 12) a late steer settle does NOT mutate a LATER turn (cubic P1, the generation guard): a fold's +// steer that settles AFTER its turn committed must be a no-op — it must never strip an id the +// NEXT turn re-surfaced. Turn 1 folds a2 with a test-controlled (deferred) steer; the fold is +// stranded past the macrotask boundary → un-surfaced (redelivered) and turn 1 commits, bumping +// the generation. a2 redelivers as turn 2's origin. THEN, while turn 2 holds a2 surfaced-but- +// uncommitted, we fire the STALE turn-1 steer reject. On the fixed loop the callback captured +// turn 1's generation and no-ops. On 6f82f76 the reject's `.catch` calls turn.unsurface("a2") +// on the live turn 2 (no generation guard) → strips a2 from turn 2's ack set → a2 is never +// acked and redelivers forever. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + session.deferSteer = true; // turn 1's fold steer stays pending until we fire rejectSteer[0]() + mesh.items = [dm("a1", "alice", "q1")]; + runPeerLoop({ mesh, session, steerSettleTimeoutMs: 0 }); // strand path: timeout must fire within one drain() + session.emit(START); // turn 1 live + mesh.arrive(dm("a2", "alice", "q2")); // fold a2 under generation g0; steer deferred (unsettled) + session.emit(end("r1")); // pendingSteers non-empty → deferred commit races the macrotask boundary + await drain(); // boundary wins: a2 still pending → un-surfaced (redeliver), turn 1 commits, gen→g1 + assert(ids(mesh.acked) === "a1", "turn 1 acked only its origin a1; the stranded fold a2 redelivers"); + assert(session.prompts.length === 2 && session.prompts[1] === framed(dm("a2", "alice", "q2")), + "a2 redelivered as turn 2's origin"); + session.emit(START); // turn 2 live: a2 surfaced, NOT yet committed + // Fire the STALE turn-1 (g0) steer reject now, WHILE turn 2 holds a2 surfaced. On the fix the + // g0 callback sees generation moved on and no-ops; on 6f82f76 it strips a2 from turn 2. + session.rejectSteer[0](); + await drain(); + session.emit(end("r2")); // turn 2 commits + await drain(); + assert(mesh.acked.filter((x) => x.id === "a2").length === 1, + "a2 acked exactly once under turn 2; the stale g0 reject was a no-op (6f82f76 strips it → 0)"); + assert(!mesh.items.some((x) => x.id === "a2"), + "a2 left the stream after turn 2 (6f82f76 leaves it surfaced-then-stripped → redelivers)"); +} +console.log("12) late steer settle does not mutate a later turn (generation guard) OK ✅"); + +// 13) shutdown blocks further dispatch (cubic P2, the stopped-guards): once shutdown() begins, a +// mesh `incoming`/`wake` event must NOT start a new turn on the disposed session. The initial +// pump surfaces s1 (prompt pending, no START → not streaming); shutdown abandons it and disposes. +// A post-shutdown arrive+wake must not re-pump. 6f82f76's pump() has no stopped guard → the +// late incoming starts a turn and prompts the disposed session. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("s1", "alice")]; + const loop = runPeerLoop({ mesh, session }); // pump → surfaces s1, prompt(s1) pending (no START) + await drain(); // let the initial prompt(s1) settle; turn is surfaced-but-not-streaming + await loop.shutdown(); // stopped=true; abandon in-flight s1 (redeliver); dispose + assert(session.disposed === 1, "shutdown disposed the session"); + const promptsBefore = session.prompts.length; // exactly the one pre-shutdown s1 prompt + mesh.arrive(dm("s2", "bob")); // post-shutdown incoming → pump() must early-return (stopped) + mesh.emit("wake"); // post-shutdown wake → pump() must early-return (stopped) + await drain(); + assert(session.prompts.length === promptsBefore, + "no new prompt after shutdown (pump stopped-guard); 6f82f76 re-pumps the disposed session"); +} +console.log("13) shutdown blocks further dispatch (stopped-guard) OK ✅"); + +// 14) strand safety — a never-settling steer does NOT hang the terminal commit (mercator's +// insurance test, a forward guard). The deferred commit races the pending folds against a +// one-macrotask boundary, so even a steer that NEVER settles cannot wedge the turn: the +// boundary wins, the turn commits (origin acked), and the unconfirmed fold is un-surfaced +// (redeliver — the safe direction, since the model may never have received it). This is NOT a +// 6f82f76 differentiator: 6f82f76 commits synchronously so it also would not hang here. Test 14 +// GUARDS the new deferred path — it FAILS if someone later drops the boundary and awaits +// allSettled unbounded (the commit would then never fire and this test would hang, never +// reaching its asserts). See the throwaway boundary-removed probe reported alongside this file. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + session.steerHang = true; // the fold's steer never resolves or rejects + mesh.items = [dm("a1", "alice", "q1")]; + runPeerLoop({ mesh, session, steerSettleTimeoutMs: 0 }); // never-settle path: timeout must fire within one drain() + session.emit(START); // turn live, streaming + mesh.arrive(dm("a2", "alice", "q2")); // fold a2; steer hangs → a2 stays pending forever + session.emit(end("ans")); // pendingSteers non-empty → deferred commit races the boundary + await drain(); // ONE macrotask tick — the boundary wins the race, commit fires without the steer + assert(mesh.acked.some((x) => x.id === "a1"), + "the turn COMMITTED off the bounded wait (a1 acked) — a hung steer did not wedge it"); + assert(mesh.items.some((x) => x.id === "a2"), + "the never-confirmed fold a2 was un-surfaced → stays on the stream to redeliver (safe direction)"); + assert(session.steers.length === 1, "the hung fold attempted the steer exactly once"); +} +console.log("14) strand safety: never-settling steer does not hang commit OK ✅"); + +// 15) a rejecting abort() must NOT reject loop.shutdown() (sibling of test 10, one line up): the +// shutdown() sequence is `if (turn.inFlight) { turn.abandon(); await session.abort(); } await +// session.dispose().catch(log)`. peer.ts does `await loop.shutdown()` THEN `await mesh.stop()`. +// If abort() rejects and the loop doesn't swallow it, shutdown() rejects → dispose() is SKIPPED +// AND mesh.stop() never runs → ghost peer on the mesh. The fix (`await session.abort().catch(log)`) +// swallows the abort failure so teardown continues; on d213837 (bare `await session.abort()`) it +// rejects. The abort path only runs when the turn is IN FLIGHT at shutdown, so we emit START (turn +// open + streaming) with NO agent_end. `session.disposed === 1` proves teardown continued past the +// abort rejection (so mesh.stop would run). +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("s1", "alice")]; + session.abortReject = true; // the SDK abort will throw + const loop = runPeerLoop({ mesh, session }); + // Hold the turn IN FLIGHT: emit START (streaming, turn open) with no agent_end, so shutdown() enters + // the `if (turn.inFlight)` branch and calls abort() (the only path where a rejecting abort matters). + session.emit(START); + await drain(); + let resolved = false; + try { + await loop.shutdown(); + resolved = true; + } catch { + resolved = false; + } + assert(resolved === true, + "loop.shutdown() RESOLVED despite the rejecting abort (d213837 rejects → dispose/mesh.stop skipped)"); + assert(session.aborted === 1, "abort was attempted exactly once (turn was in flight)"); + assert(session.disposed === 1, + "dispose STILL ran after the abort rejection → teardown continued so mesh.stop would run (no ghost peer)"); +} +console.log("15) rejecting abort does not reject shutdown (dispose/mesh.stop still run) OK ✅"); + +// 16) slow-accept steer is STILL acked under the PRODUCTION-DEFAULT timeout (#5-killer, load-bearing): +// the whole point of B-simple. A fold whose steer() accepts after a REAL settle delay (~5ms — genuine +// work, not just one microtask) must still be awaited by the deferred commit and acked, because the +// production steerSettleTimeoutMs (5_000ms) is generous enough to wait for it. commitAfterSteers races +// allSettled(pending) against setTimeout(steerSettleTimeoutMs); with the prod-default 5_000ms boundary +// the 5ms accept resolves first (5 << 5_000), so allSettled wins and a2 is acked — the commit provably +// comes from allSettled, not the timeout. RED at boundary 0 (== the old setTimeout(0) window): 0 < 5, so +// the timeout fires before the accept settles → finishTurn un-surfaces a2 → acked==="a1", a2 redelivers +// (exactly finding #5). The delay is a REAL 5ms (not setTimeout(0)) so the race is decided by MAGNITUDE, +// not timer-registration order — the only shape that actually goes red at boundary 0. This test FAILS if +// someone reverts the timeout to a value below the accept delay (e.g. 0). Exercising the real 5_000ms +// default is only safe because #7 now CLEARS the settle timer when allSettled wins — with the leaked +// timer this test would hang the process ~5s at exit (see test 18). Red-green evidence in the task report. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + session.steerSlowAccept = true; // the fold's steer resolves after a real ~5ms settle (setTimeout 5) + mesh.items = [dm("a1", "alice", "q1")]; + runPeerLoop({ mesh, session }); // PROD DEFAULT boundary 5_000ms >> the 5ms accept (no override) + session.emit(START); // turn live, streaming + mesh.arrive(dm("a2", "alice", "q2")); // same scope → fold; slow-accept steer resolves ~5ms later + session.emit(end("ans")); // pendingSteers non-empty → deferred commit awaits the slow accept + // Wait past the slow accept's ~5ms delay but well UNDER the 5_000ms timeout, so the commit provably + // comes from allSettled resolving (a2 accepted), NOT from the timeout firing. A real delay (not + // drain()s): two macrotask ticks (~1ms) would assert before the 5ms accept and the deferred commit. + await new Promise((r) => setTimeout(r, 20)); + assert(ids(mesh.acked) === "a1,a2", + "the slow-accepted fold a2 IS acked (allSettled waited for it); at timeout 0 acked==='a1' (#5)"); + assert(!mesh.items.some((x) => x.id === "a2"), + "a2 left the stream (acked, not redelivered); at timeout 0 a2 stays → redelivers"); + assert(session.steers.length === 1, "the slow fold attempted the steer exactly once"); +} +console.log("16) slow-accept steer is still acked under a human-scale timeout (#5) OK ✅"); + +// 17) a synchronously-throwing abort() must STILL resolve shutdown() and run dispose() (#6, the +// ghost-peer seam): a `.catch()` only handles a REJECTED promise; a SYNC throw before the promise +// is even returned escapes it — only a surrounding try/catch in shutdown() catches it. The +// per-call try/catch (abort in one block, dispose in its own) resolves shutdown despite the sync +// throw AND still runs dispose so peer.ts's mesh.stop() runs → no ghost peer. Mirrors test 15 +// (async reject) with a SYNC throw instead. RED on /tmp/loop-bsimple-catchonly.ts (a `.catch`-only +// shutdown): the sync throw escapes `.catch` → shutdown throws → resolved=false, disposed=0. +{ + const mesh = new FakeMesh(); + const session = new StubSession(); + mesh.items = [dm("s1", "alice")]; + session.abortThrowSync = true; // abort() increments then THROWS synchronously (before any promise) + const loop = runPeerLoop({ mesh, session }); + // Hold the turn IN FLIGHT: emit START (streaming, turn open) with no agent_end, so shutdown() enters + // the `if (turn.inFlight)` branch and calls abort() — the only path where a sync-throwing abort matters. + session.emit(START); + await drain(); + let resolved = false; + try { + await loop.shutdown(); + resolved = true; + } catch { + resolved = false; + } + assert(resolved === true, + "loop.shutdown() RESOLVED despite the SYNC throw from abort (.catch-only shutdown → false)"); + assert(session.aborted === 1, "abort was attempted exactly once (turn was in flight)"); + assert(session.disposed === 1, + "dispose STILL ran after the sync throw → per-call try/catch let teardown continue (.catch-only → 0)"); +} +console.log("17) sync-throwing abort still resolves shutdown and runs dispose (#6) OK ✅"); + +// 18) the settle timer is CLEARED when allSettled wins — no leaked ref'd Timeout handle (#7, the +// event-loop-leak seam): commitAfterSteers races allSettled(pendingSteers) against +// setTimeout(steerSettleTimeoutMs). On the common path the steer settles fast so allSettled wins, +// but the 5_000ms settle timer is still ARMED — if it isn't cleared it stays ref'd on the Node +// event loop and delays process/CLI exit by up to steerSettleTimeoutMs (5s default) PER folded +// turn. The fix clears it in a `finally` on the allSettled-wins path. We fold a2 with a fast +// (~5ms) accept under the PROD default 5_000ms boundary (a leaked handle here is a 5s Timeout), +// let the deferred commit run, then assert the active 'Timeout' handle count returned to its +// pre-fold baseline — i.e. the settle timer left NO net handle. On /tmp/loop-leak-red.ts (timer +// not cleared) the count is before+1. The fold must STILL commit correctly (acked==='a1,a2'), so +// the test proves the timer is cleared WITHOUT breaking the commit. +{ + const before = process.getActiveResourcesInfo().filter((r) => r === "Timeout").length; + const mesh = new FakeMesh(); + const session = new StubSession(); + session.steerSlowAccept = true; // fold's steer resolves after a real ~5ms settle → allSettled wins + mesh.items = [dm("a1", "alice", "q1")]; + runPeerLoop({ mesh, session }); // PROD DEFAULT 5_000ms — a leaked settle timer would be a 5s handle + session.emit(START); // turn live, streaming + mesh.arrive(dm("a2", "alice", "q2")); // same scope → fold; slow-accept steer resolves ~5ms later + session.emit(end("ans")); // pendingSteers non-empty → commitAfterSteers races allSettled vs setTimeout(5_000) + // Wait past the ~5ms accept so allSettled wins the race, then one setImmediate tick so the deferred + // commit's `finally` (which clears the settle timer) has run before we sample the handle table. + await new Promise((r) => setTimeout(r, 20)); + await new Promise((r) => setImmediate(r)); + const after = process.getActiveResourcesInfo().filter((r) => r === "Timeout").length; + assert(after === before, + `the 5_000ms settle timer was CLEARED after allSettled won — no leaked Timeout handle ` + + `(before=${before}, after=${after}); on the leak-red copy after===before+1 (#7)`); + assert(ids(mesh.acked) === "a1,a2", + "the fold still committed correctly (a2 acked) — the timer is cleared WITHOUT breaking the commit"); +} +console.log("18) settle timer is cleared when allSettled wins (no event-loop leak) (#7) OK ✅"); + +console.log("OH-MY-PI PEER SMOKE OK ✅"); diff --git a/extensions/connector-oh-my-pi/package.json b/extensions/connector-oh-my-pi/package.json new file mode 100644 index 00000000..33686176 --- /dev/null +++ b/extensions/connector-oh-my-pi/package.json @@ -0,0 +1,52 @@ +{ + "name": "@cotal-ai/oh-my-pi", + "description": "Cotal connector for oh-my-pi: a headless native-embed peer, plus an interactive session extension that joins a live session to the mesh.", + "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" + } + }, + "omp": { + "extensions": [ + "./dist/extension.bundle.js" + ] + }, + "engines": { + "node": ">=22.19.0" + }, + "scripts": { + "typecheck": "tsc -p tsconfig.json --noEmit", + "build": "tsc -p tsconfig.json && pnpm run bundle", + "bundle": "esbuild src/extension.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/extension.bundle.js --external:@oh-my-pi/*", + "smoke:peer": "tsx oh-my-pi-peer.smoke.ts", + "smoke:extension": "tsx oh-my-pi-extension.smoke.ts", + "smoke:interactive-loop": "tsx interactive-loop.smoke.ts", + "test": "pnpm run smoke:peer && pnpm run smoke:extension && pnpm run smoke:interactive-loop", + "prepublishOnly": "pnpm run build" + }, + "dependencies": { + "@oh-my-pi/pi-coding-agent": "^16.3.12", + "@cotal-ai/connector-core": "workspace:*", + "tsx": "^4.22.4" + }, + "peerDependencies": { + "@cotal-ai/core": "workspace:*" + }, + "devDependencies": { + "@cotal-ai/core": "workspace:*", + "esbuild": "^0.28.0", + "zod": "^4.4.3" + }, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/extensions/connector-oh-my-pi/src/connector.ts b/extensions/connector-oh-my-pi/src/connector.ts new file mode 100644 index 00000000..54d2b1c0 --- /dev/null +++ b/extensions/connector-oh-my-pi/src/connector.ts @@ -0,0 +1,56 @@ +import { fileURLToPath } from "node:url"; +import { registry, type Connector, type LaunchOpts, type LaunchSpec } from "@cotal-ai/core"; + +/** The peer loop runs via tsx (resolved from this extension's own node_modules, so it works + * regardless of the spawned process's PATH/cwd). `main` is loaded with the same extension as + * this module — `main.ts` when running from source (dev), `main.js` when running 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 TSX = fileURLToPath(new URL("../node_modules/.bin/tsx", import.meta.url)); +const MAIN = fileURLToPath(new URL(`./main${ext}`, import.meta.url)); + +/** Provider API keys oh-my-pi resolves from the environment (AuthStorage falls back to env). + * Forwarded when present so a spawned peer has credentials for its model. */ +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 oh-my-pi connector: launches an embedded Cotal peer that runs the oh-my-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. Self-registers on + * import; the manager resolves it by agent type "oh-my-pi". + * + * oh-my-pi (https://github.com/can1357/oh-my-pi) is a fork of Pi, so this mirrors the `pi` + * connector but targets `@oh-my-pi/pi-coding-agent`; the divergences the fork introduced + * (e.g. retries surface as session `auto_retry_*` events, not an `agent_end.willRetry` + * flag) are handled in `peer.ts`. + */ +export const ohMyPiConnector: Connector = { + kind: "connector", + name: "oh-my-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; + for (const key of PROVIDER_KEYS) { + const value = process.env[key]; + if (value) env[key] = value; + } + return { command: TSX, args: [MAIN], env }; + }, +}; + +registry.register(ohMyPiConnector); diff --git a/extensions/connector-oh-my-pi/src/extension.ts b/extensions/connector-oh-my-pi/src/extension.ts new file mode 100644 index 00000000..6d3fce5a --- /dev/null +++ b/extensions/connector-oh-my-pi/src/extension.ts @@ -0,0 +1,167 @@ +/** + * Cotal mesh extension for the oh-my-pi coding agent. + * + * Loaded via `pi --extension` or from `~/.omp/agent/extensions/`, this turns an interactive + * OMP session into a first-class Cotal mesh peer — at parity with the Claude Code (MCP) and + * OpenCode (plugin) connectors, and rendered from the SAME shared source of truth + * (`cotalToolSpecs` in `@cotal-ai/connector-core`), so the cotal_* surface can't drift. + * + * • holds the MeshAgent (NATS endpoint, inbox, presence) for the session's lifetime; + * • registers the cotal_* tools natively via `pi.registerTool` (roster, inbox, send, dm, + * anycast, status, channels, …), rendered from the shared specs; + * • maps the agent's own event stream to presence (working | waiting | idle | offline); + * • DELIVERS inbound mesh traffic into the session via `pi.sendMessage(..., {deliverAs})`: + * an idle session is woken (triggerTurn), a live one is steered — never interrupting a + * running turn, matching the other connectors. It acks on turn completion, so a crash or + * error redelivers. + * + * Identity comes from COTAL_* env (the extension runs in the omp process and inherits it). + * No identity → inert, so a plain `omp` never joins as a stray peer. Set COTAL_NAME (and + * optionally COTAL_LINK / COTAL_AGENT_FILE) before launch to join; `cotal up --open` gives a + * loopback mesh that needs only COTAL_NAME. + */ +// oh-my-pi's published root barrel (`@oh-my-pi/pi-coding-agent`) is currently unconsumable under +// `nodenext` — its dist .d.ts use extensionless relative re-exports and re-export names pi-tui / +// pi-utils don't declare, voiding the whole barrel. Deep-import from the subpath entrypoint, which +// resolves to the specific .d.ts and typechecks clean. Revert to the root import once the upstream +// type-build fix is published (see the header comment in src/peer.ts). +import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent/extensibility/extensions/types"; +import { + configFromEnv, + hasIdentity, + MeshAgent, + cotalToolSpecs, + type CotalToolSpec, + type ToolResult, + type MeshLogger, +} from "@cotal-ai/connector-core"; +import type { PresenceStatus } from "@cotal-ai/core"; +import { runPeerLoop } from "./interactive-loop.js"; + +export default function cotalMesh(pi: ExtensionAPI): void { + // Route every connector diagnostic through OMP's FILE logger, never the shared terminal: + // a raw stderr write corrupts the live TUI, and mesh reconnect churn would otherwise flood it. + const log: MeshLogger = (msg, level = "info") => { + const line = `[cotal-mesh] ${msg}`; + if (level === "error") pi.logger.error(line); + else if (level === "warn") pi.logger.warn(line); + else pi.logger.info(line); + }; + + // No identity → a plain `omp`, not a launcher-joined session. Stay off the mesh. + if (!hasIdentity()) { + log("no COTAL_NAME / COTAL_LINK / COTAL_AGENT_FILE — staying off the mesh"); + return; + } + + const config = configFromEnv(); + config.connector = "oh-my-pi"; // advertise the host harness on our AgentCard (meta.connector) + const agent = new MeshAgent(config, log); + + // Join the mesh only from a real interactive (top-level) session. A `task`/print/RPC subagent + // inherits the parent's COTAL_* env, so hasIdentity() alone would make every subagent a stray + // same-named peer (polluting the roster + making DMs to that name ambiguous, and worse, a + // subagent could receive mesh traffic meant for the main session). `ctx.hasUI` is false in + // print/RPC/subagent mode and true for the interactive session — the available signal that + // distinguishes them (OMP's internal agentKind "main"|"sub" isn't exposed to extensions). + // Deferred to session_start because hasUI is only on the handler ctx, not the factory arg. + // NOTE: a future headless launcher (e.g. Compass spawning a real worker) is also hasUI:false and + // WOULD need to join — revisit with an explicit signal (agentKind/env opt-in) when that lands. + let started = false; + pi.on("session_start", (_event, ctx: ExtensionContext) => { + if (started) return; + started = true; + if (!ctx.hasUI) { + log("non-interactive session (subagent/print/RPC) — staying off the mesh"); + return; + } + agent.start(); // background connect with retry — never blocks + }); + + const loop = runPeerLoop({ mesh: agent, host: pi }); + + const safeStatus = async (status: PresenceStatus, activity?: string): Promise => { + try { + if (agent.connected) await agent.setStatus(status, activity); + } catch { + /* presence is best-effort — never throw into the agent loop */ + } + }; + + // ---- session event stream → presence + turn lifecycle ------------------- + pi.on("agent_start", async () => { + loop.onAgentStart(); + await safeStatus("working"); + }); + pi.on("tool_execution_start", async (event) => { + await safeStatus("working", event.toolName); + }); + pi.on("agent_end", async () => { + // agent_end is the turn-end signal (single-session process, notification-only): ack + flush. + await safeStatus("idle"); + loop.onAgentEnd(); + }); + pi.on("session_shutdown", async () => { + await safeStatus("offline"); + await loop.shutdown(); + }); + + // ---- cotal_* tools, rendered from the shared specs ---------------------- + const { z } = pi.zod; + for (const spec of cotalToolSpecs(config, "oh-my-pi")) { + registerSpec(pi, agent, config, spec, z); + } + + log( + `loaded — space="${config.space}" name="${config.name}"${config.role ? ` role="${config.role}"` : ""} (${config.servers}); joins the mesh on session_start if interactive`, + ); +} + +/** Render one shared CotalToolSpec onto `pi.registerTool`. `cotal_inbox` is forced read-only + * (peek): this extension delivers + acks each turn, so the agent's inbox tool must never drain, + * or it would race the ack. All others pass their args straight through to the spec's `run`. */ +function registerSpec( + pi: ExtensionAPI, + agent: MeshAgent, + config: ReturnType, + spec: CotalToolSpec, + z: ExtensionAPI["zod"]["z"], +): void { + const toResult = (r: ToolResult) => ({ + content: [{ type: "text" as const, text: r.isError ? `⚠ ${r.text}` : r.text }], + details: {}, + }); + + if (spec.name === "cotal_inbox") { + // Empty params (this tool takes none). The explicit `registerTool<…>` generic below pins + // `TParams` so the tool registry doesn't infer it from the literal and recurse into + // `Static` (TS2589, excessively deep) under pi-coding-agent ≥16.3.7. + const parameters = z.object({}); + pi.registerTool>({ + name: spec.name, + label: spec.title, + description: + "Show the peer messages currently waiting for you (incl. focus-mode recall). You don't normally need this — the extension delivers peer messages into your turns automatically; use it to re-check what's pending mid-task. Read-only: it never consumes them.", + parameters, + approval: "read", + async execute(_id, _params, _signal, _onUpdate, _ctx: ExtensionContext) { + return toResult(await spec.run(agent, config, { peek: true })); + }, + }); + return; + } + + // The shared spec carries a Zod raw shape from connector-core's own zod copy; rebuild it with the + // host's injected zod (pi.zod) so the schema type matches OMP's tool registry. The cast bridges the + // two structurally-identical zod copies at this single boundary (raw shapes are plain objects). + const parameters = z.object((spec.schema ?? {}) as Parameters[0]); + pi.registerTool>({ + name: spec.name, + label: spec.title, + description: spec.description, + parameters, + async execute(_id, params, _signal, _onUpdate, _ctx: ExtensionContext) { + return toResult(await spec.run(agent, config, params ?? {})); + }, + }); +} diff --git a/extensions/connector-oh-my-pi/src/index.ts b/extensions/connector-oh-my-pi/src/index.ts new file mode 100644 index 00000000..febc70ef --- /dev/null +++ b/extensions/connector-oh-my-pi/src/index.ts @@ -0,0 +1,4 @@ +export * from "./connector.js"; +export { runOmpPeer } from "./peer.js"; +// The interactive session extension (default export = the `pi --extension` factory). +export { default as cotalMeshExtension } from "./extension.js"; diff --git a/extensions/connector-oh-my-pi/src/interactive-loop.ts b/extensions/connector-oh-my-pi/src/interactive-loop.ts new file mode 100644 index 00000000..4ce86f69 --- /dev/null +++ b/extensions/connector-oh-my-pi/src/interactive-loop.ts @@ -0,0 +1,138 @@ +/** + * The delivery loop that bridges a Cotal mesh to one oh-my-pi session — extracted from the + * extension factory so it can be driven with fakes (no NATS, no real session) in the smoke. + * + * It owns three things: (1) DELIVERY — inbound mesh traffic is injected into the session, waking + * an idle one and steering a live one, never interrupting a running turn; (2) ACK-ON-SURFACE — the + * batch injected into a turn is acked only when that turn ends, matched by id so a front-eviction + * can't ack the wrong messages; (3) PRESENCE — the session's own lifecycle events map to mesh + * presence. The real `MeshAgent` satisfies {@link PeerMesh} structurally; the real `ExtensionAPI` + * satisfies {@link PeerHost}. + */ +import { formatInjection, fmtFrom, ORIENTATION_BOOTSTRAP, type InboxItem } from "@cotal-ai/connector-core"; +import type { PresenceStatus, AttentionMode, ChannelMode } from "@cotal-ai/core"; + +/** The mesh surface the loop drives. `MeshAgent` satisfies this structurally. */ +export interface PeerMesh { + readonly connected: boolean; + readonly attention: AttentionMode; + channelMode(channel?: string): ChannelMode | undefined; + peekInbox(): InboxItem[]; + drainInbox(limit?: number): InboxItem[]; + pendingWake(): number; + setStatus(status: PresenceStatus, activity?: string): Promise; + stop(): Promise; + on(event: "incoming" | "mention-wake", handler: (item: InboxItem) => void): void; + on(event: "wake", handler: () => void): void; +} + +/** The host-session surface the loop drives. The extension's `ExtensionAPI` satisfies this. */ +export interface PeerHost { + sendMessage( + message: { customType: string; content: string; display: boolean; details: unknown; attribution: "user" | "agent" }, + options: { deliverAs: "steer"; triggerTurn: true }, + ): void; +} + +/** customType tags for injected messages — namespaced like OMP's own `irc:*`. */ +export const INCOMING = "cotal:incoming"; +export const NUDGE = "cotal:nudge"; + +/** A running peer loop. The factory owns the mesh + process lifecycle around it. */ +export interface PeerLoop { + /** Call on `agent_start`: a turn began — hold delivery. */ + onAgentStart(): void; + /** Call on `agent_end`: the turn ended — ack the surfaced batch, then flush the next. */ + onAgentEnd(): void; + /** Presence: this session is offline; stop the mesh. */ + shutdown(): Promise; +} + +/** + * Wire a {@link PeerMesh}'s inbox to a {@link PeerHost} and return the turn-lifecycle hooks the + * factory calls from the session's event stream. + */ +export function runPeerLoop({ mesh, host }: { mesh: PeerMesh; host: PeerHost }): PeerLoop { + // One session, driven straight off the inbox. `busy` gates delivery so a message that arrives + // mid-turn waits for turn end (no-interrupt). `surfaced` holds the ids injected into the current + // turn, acked on completion by id (not count) so a front-eviction can't ack the wrong messages. + let busy = false; + let surfaced: string[] = []; + let primed = false; // orientation bootstrap prepended once, on the first delivered turn + + /** Inject the current inbox batch (peeked, not drained). `override` replaces the body with a bare + * nudge (focus @mention recall) and surfaces nothing to ack. Never drives into a running turn. */ + function drive(override?: string): void { + if (busy) return; + let text: string; + let ids: string[] = []; + if (override) { + text = override; + } else { + const items = mesh.peekInbox(); + if (items.length === 0) return; + ids = items.map((i) => i.id); + const inj = formatInjection(items); + if (!inj) return; + text = inj; + } + if (!primed) { + primed = true; + text = `${ORIENTATION_BOOTSTRAP}\n\n${text}`; + } + busy = true; + surfaced = ids; + // The content participates in LLM context (a CustomMessage); triggerTurn wakes an idle session, + // steer folds into a live one. Attribution "user" — a peer message is external input here. + host.sendMessage( + { customType: override ? NUDGE : INCOMING, content: text, display: true, details: {}, attribution: "user" }, + { deliverAs: "steer", triggerTurn: true }, + ); + } + + /** Ack the surfaced batch — but only the leading run STILL at the front of the inbox, matched by + * id. The mesh evicts from the FRONT at its cap, so a long turn on a chatty channel can shift our + * surfaced prefix out; matching by id (not a raw count) means we never ack the wrong, newer + * messages. Any surfaced survivor that no longer leads is left unacked → redelivered. */ + function ackSurfaced(): void { + if (surfaced.length === 0) return; + const front = mesh.peekInbox(); + let n = 0; + while (n < surfaced.length && n < front.length && front[n].id === surfaced[n]) n++; + if (n > 0) mesh.drainInbox(n); + surfaced = []; + } + + // ---- inbound mesh → delivery -------------------------------------------- + // A directed message (DM / anycast / @mention) drives when idle; ambient channel chatter drives + // only in `open` while idle (dnd/focus hold it for the next turn); a per-channel `quiet` channel + // never ambient-drives. `muted` ambient never reaches here (ack-dropped at ingest). + mesh.on("incoming", (item: InboxItem) => { + if (busy) return; // buffer; onAgentEnd drives at turn end + const directed = item.kind !== "channel" || item.mentionsMe; + const quiet = item.kind === "channel" && mesh.channelMode(item.channel) === "quiet"; + if (directed || (!quiet && mesh.attention === "open")) drive(); + }); + mesh.on("mention-wake", (item: InboxItem) => { + // Focus: the @mention body was acked-and-dropped at ingest — wake a turn to PULL it (recall). + if (!busy) drive(`📨 You were mentioned by ${fmtFrom(item)} on #${item.channel ?? "?"} — read it with cotal_inbox.`); + }); + mesh.on("wake", () => { + if (!busy) drive(); + }); + + return { + onAgentStart(): void { + busy = true; + }, + onAgentEnd(): void { + // turn-end: release the no-interrupt gate, ack the surfaced batch, flush the next. + busy = false; + ackSurfaced(); + if (mesh.pendingWake() > 0) drive(); + }, + async shutdown(): Promise { + await mesh.stop(); + }, + }; +} diff --git a/extensions/connector-oh-my-pi/src/loop.ts b/extensions/connector-oh-my-pi/src/loop.ts new file mode 100644 index 00000000..23c4849d --- /dev/null +++ b/extensions/connector-oh-my-pi/src/loop.ts @@ -0,0 +1,301 @@ +import { InboxTurn } from "@cotal-ai/connector-core"; +import type { InboxItem, InboxSource } from "@cotal-ai/connector-core"; +// Type-only: the event union is erased at build/runtime, so this module has NO oh-my-pi +// value import and loads under plain node/tsx (the oh-my-pi runtime pulls in `bun`, which is +// unloadable off-Bun). The runtime wiring lives in `peer.ts`; this is the injectable loop. +import type { AgentSessionEvent } from "@oh-my-pi/pi-coding-agent/session/agent-session"; + +export function log(e: unknown): void { + process.stderr.write(`[oh-my-pi-peer] ${e instanceof Error ? e.message : String(e)}\n`); +} + +/** + * The mesh surface {@link runPeerLoop} drives: presence, reply delivery, and a stream-backed + * inbox ({@link InboxSource}). The real `MeshAgent` satisfies this structurally; the smoke + * test passes a fake so the loop can be exercised with no NATS connection. + */ +export interface PeerMesh extends InboxSource { + readonly id: string; + setStatus(status: "idle" | "waiting" | "working", activity?: string): Promise; + send(text: string, channel?: string, mentions?: string[]): Promise; + dm(target: string, text: string): Promise; + on(event: "incoming" | "wake", listener: () => void): unknown; +} + +/** + * The slice of the oh-my-pi `AgentSession` the loop drives. The real session satisfies this + * structurally; the smoke passes a stub whose `prompt`/`steer` record and whose event stream + * is driven with the scripted `agent_start`/`agent_end` events. + */ +export interface PeerSession { + subscribe(listener: (event: AgentSessionEvent) => void): () => void; + prompt(text: string): Promise; + steer(text: string): Promise; + abort(): Promise; + dispose(): Promise; +} + +/** A running peer loop. {@link runOmpPeer} owns the mesh + process lifecycle around it. */ +export interface PeerLoop { + /** Abandon any in-flight turn (no ack → redeliver) and tear the session down. */ + shutdown(): Promise; +} + +/** 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: Pick, 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]; + if (!m || typeof m !== "object" || !("role" in m) || m.role !== "assistant") continue; + if (!("content" in m) || !Array.isArray(m.content)) continue; + const text = m.content + .map((p) => (p && typeof p === "object" && "type" in p && p.type === "text" && "text" in p ? String(p.text ?? "") : "")) + .join(""); + return text.length ? text : undefined; + } + return undefined; +} + +/** + * Wire an oh-my-pi session's event stream to a {@link PeerMesh}'s inbox and run the + * reply/scope/ack loop. Pure wiring over the two injected surfaces — no NATS, no process + * lifecycle, no session creation — so it can be driven against fakes ({@link PeerMesh} + + * {@link PeerSession}) in the smoke test. {@link runOmpPeer} owns the real instances and the + * mesh/process lifecycle around this. + * + * `prompt()` wakes an idle session on the front message, `steer()` interjects into a live one + * (true mid-turn drive), 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. + * + * oh-my-pi is a fork of Pi, so this mirrors the `@cotal-ai/pi` connector; one fork + * divergence is handled here — oh-my-pi surfaces retries as session-level `auto_retry_*` + * events rather than an `agent_end.willRetry` flag, so an `agent_end` here is always the + * turn's terminal event. + */ +export function runPeerLoop({ + mesh, + session, + // How long a terminal commit waits for a turn's unconfirmed fold steers to settle before giving + // up on them (un-surfacing → redeliver). A healthy steer settles in ≤1 microtask (its promise + // resolves at synchronous enqueue-time — no image work on the connector's string-only steers), so + // allSettled wins this race by orders of magnitude and the timeout never fires on the happy path; + // it only bounds a genuinely-stuck steer so shutdown/commit can't wedge. 5s is generous headroom + // over any realistic settle (even a future images-carrying steer's normalize/resize is ~tens of + // ms) while keeping a stuck-steer commit delay human-tolerable. Injectable so tests don't wait it. + steerSettleTimeoutMs = 5_000, +}: { + mesh: PeerMesh; + session: PeerSession; + steerSettleTimeoutMs?: number; +}): PeerLoop { + const turn = new InboxTurn(mesh); + let streaming = false; // gates steer(): only valid once the agent is actually streaming + // Set once shutdown() begins so nothing dispatched after teardown drives a disposed session + // (commit/pump/setStatus/steer on a torn-down loop). Checked at every entry point that can act + // post-shutdown: pump(), foldSameScope(), the session event handler, the prompt callback + // (onStartError), and the deferred commit. + let stopped = false; + // Monotonic turn counter, bumped on each terminal commit. A fold's steer callback captures the + // generation it was issued under, so a late settle (a steer resolving after its turn committed) + // can only mutate ITS OWN turn — never a later turn that re-surfaced a redelivered id (cubic P1). + let generation = 0; + // Folds issued in the current turn whose steer() has not settled: id → the settle-chained promise. + // agent_end awaits these (bounded) so an ACCEPTED fold stays acked and only a rejected/stranded one + // is un-surfaced. steer() resolving means the session accepted the message into its queue (the + // model got it); un-surfacing an accepted fold would redeliver a message already delivered, since + // the mesh only dedups ACKED ids — an un-acked redelivery re-surfaces to the model. + const pendingSteers = new Map>(); + + 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 or after teardown. */ + function pump(): void { + if (stopped || turn.inFlight) return; + turn.drop((i) => !actionable(mesh, i)); + const origin = turn.start(); + if (!origin) { + setStatus("idle"); + return; + } + streaming = false; + // prompt() resolves false when the session DECLINES the wake (no agent_start/agent_end will + // follow). Treat that like a pre-flight failure: complete the turn so the peer doesn't wedge + // with an in-flight-but-never-streaming origin. A true pre-flight throw routes the same way. + session.prompt(framed(origin)).then( + (accepted) => { + if (!accepted && !streaming) onStartError(new Error("session declined the prompt")); + }, + onStartError, + ); + } + + /** 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 (stopped || !turn.origin || !streaming) return; + const gen = generation; // these folds belong to the current turn; a late settle checks this + for (const item of turn.extend((i, o) => actionable(mesh, i) && scopeKey(i) === scopeKey(o))) { + // extend() surfaces synchronously so a re-entrant fold can't re-pick the item; the steer is + // async. Track the settle-chained promise so agent_end can await the real accept/reject before + // commit. The generation guard stops a steer that settles after its turn committed from + // mutating a later turn's ack set (its id long since acked or re-surfaced under a new turn). + const settle = session.steer(framed(item)).then( + () => { + if (gen === generation) pendingSteers.delete(item.id); // accepted → stays surfaced → acked + }, + (e) => { + log(e); + if (gen !== generation) return; // turn already committed → never touch a later turn + pendingSteers.delete(item.id); + turn.unsurface(item.id); // rejected → drop from the ack set → redelivers on a later turn + }, + ); + pendingSteers.set(item.id, settle); + } + } + + function onStartError(e: unknown): void { + log(e); + if (stopped) return; // torn down mid-flight → never commit/pump/status a disposed session + 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(); + } + + /** The sole terminal-commit tail for a streaming turn: un-surface any fold whose steer never + * confirmed (redeliver, not ack), ack the rest, bump generation so a late steer settle can no + * longer mutate this turn, then advance to the next scope. */ + function finishTurn(to: InboxItem | undefined, reply?: string): void { + for (const id of pendingSteers.keys()) turn.unsurface(id); // unconfirmed fold → redeliver, not acked + pendingSteers.clear(); + turn.commit(); // ack the surfaced run (origin + confirmed folds) — clean or failed both consume + generation++; // supersede: a steer settling after this can't touch the next turn's ack set + if (to && reply) deliver(to, reply); + pump(); // next scope + } + + /** agent_end path with folds still unconfirmed: wait — bounded by {@link steerSettleTimeoutMs} so + * a steer that never settles can't hang the turn — for each fold's steer to settle (its handler in + * {@link foldSameScope} acks an accepted fold by leaving it surfaced, un-surfaces a rejected one), + * then finish. The timeout is generous (a healthy steer settles in ≤1 microtask, so allSettled + * wins the race with orders of magnitude to spare — even a slow accept lands well within it, so an + * accepted fold is never falsely un-surfaced). A fold STILL pending after the timeout stranded → + * finishTurn un-surfaces it (redeliver, the safe direction). Guarded so a shutdown mid-wait or a + * superseding turn never commits a stale/disposed turn. */ + async function commitAfterSteers(gen: number, to: InboxItem | undefined, reply?: string): Promise { + // Bound the wait on a human-scale timer, but CLEAR it when allSettled wins (the common path): + // an uncleared setTimeout(steerSettleTimeoutMs) stays ref'd on the Node event loop and delays + // process/CLI exit by up to that timeout per folded turn (harmless at 0ms, not at the 5s default). + let timer: ReturnType | undefined; + try { + const timeout = new Promise((resolve) => { + timer = setTimeout(resolve, steerSettleTimeoutMs); + }); + await Promise.race([Promise.allSettled([...pendingSteers.values()]), timeout]); + } finally { + if (timer !== undefined) clearTimeout(timer); + } + if (stopped || gen !== generation) return; // torn down or superseded mid-wait → don't commit + finishTurn(to, reply); + } + + mesh.on("incoming", () => { + if (turn.inFlight) foldSameScope(); + else pump(); + }); + mesh.on("wake", () => { + if (!turn.inFlight) pump(); + }); + + session.subscribe((event: AgentSessionEvent) => { + if (stopped) return; // teardown began → don't drive a disposed session + 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": { + // oh-my-pi has no `agent_end.willRetry`; a retry is its own session `auto_retry_*` + // event and the failed turn still ends here, so `agent_end` is always terminal. + streaming = false; // done streaming — foldSameScope is now a no-op; no new folds this turn + const to = turn.origin; + const reply = turnReplyText(event.messages); + // No unconfirmed folds → commit synchronously (the common one-message turn, unchanged). With + // folds still settling, defer the commit until they do so an accepted fold is acked and only + // a rejected/stranded one redelivers (bounded, generation-guarded). + if (pendingSteers.size === 0) finishTurn(to, reply); + else void commitAfterSteers(generation, to, reply); + break; + } + } + }); + + // Drain anything already buffered before the listeners were attached. + pump(); + + return { + async shutdown(): Promise { + stopped = true; // block any in-flight prompt/steer/mesh callback from driving a disposed session + // abort() and dispose() are INDEPENDENT teardown steps: each must run even if the other fails, + // and neither may propagate out of shutdown() — peer.ts awaits this before mesh.stop(), so a + // throw here would skip mesh cleanup → ghost peer. Per-call try/catch (not one wrapping block: + // that would let an abort failure skip dispose) also covers a SYNCHRONOUS throw that a bare + // .catch() would miss (a non-conforming adapter throwing before it returns its promise). + try { + if (turn.inFlight) { + turn.abandon(); // leave the in-flight run on the stream → redeliver, no peer dropped + await session.abort(); + } + } catch (e) { + log(e); // a failed abort must not skip dispose below + } + try { + await session.dispose(); // await async cleanup before the caller stops the mesh + } catch (e) { + log(e); // a failed dispose must not skip the caller's mesh.stop() + } + }, + }; +} diff --git a/extensions/connector-oh-my-pi/src/main.ts b/extensions/connector-oh-my-pi/src/main.ts new file mode 100644 index 00000000..69c98e38 --- /dev/null +++ b/extensions/connector-oh-my-pi/src/main.ts @@ -0,0 +1,6 @@ +import { runOmpPeer } from "./peer.js"; + +runOmpPeer().catch((e) => { + process.stderr.write(`[oh-my-pi-peer] fatal: ${(e as Error).message}\n`); + process.exit(1); +}); diff --git a/extensions/connector-oh-my-pi/src/peer.ts b/extensions/connector-oh-my-pi/src/peer.ts new file mode 100644 index 00000000..7df379c3 --- /dev/null +++ b/extensions/connector-oh-my-pi/src/peer.ts @@ -0,0 +1,100 @@ +import { MeshAgent, configFromEnv } from "@cotal-ai/connector-core"; +// oh-my-pi's published root barrel (`@oh-my-pi/pi-coding-agent`) is currently +// unconsumable under `nodenext` — its dist .d.ts use extensionless relative +// re-exports (TS2834) and re-export names pi-tui/pi-utils don't declare, voiding +// the whole barrel. So we deep-import from the subpath entrypoints, which resolve +// to the specific .d.ts and typecheck clean. Revert to the root import once the +// upstream type-build fix (can1357/oh-my-pi) is published. +import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk"; +import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; +import type { ToolDefinition } from "@oh-my-pi/pi-coding-agent/extensibility/extensions/types"; +import { z } from "zod"; +import { runPeerLoop } from "./loop.js"; + +/** + * 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 runOmpPeer), 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 pi / openai-agents / vercel-ai adapters. + * + * Params are authored in zod (the SDK's canonical schema — `Static` infers `z.infer` first), + * so `execute`'s `params` is typed off the schema and the tool satisfies `ToolDefinition` + * without the retired TypeBox `defineTool` shim. + */ +function buildTools(mesh: MeshAgent): ToolDefinition[] { + const cotal_roster: ToolDefinition>> = { + name: "cotal_roster", + label: "Cotal roster", + description: "List the peers currently present on the Cotal mesh.", + parameters: z.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 }] }; + }, + }; + + const statusParams = z.object({ + status: z.enum(["idle", "waiting", "working"]), + activity: z.string().optional(), + }); + const cotal_status: ToolDefinition = { + name: "cotal_status", + label: "Cotal status", + description: "Update this peer's presence status on the mesh.", + parameters: statusParams, + execute: async (_id, params) => { + await mesh.setStatus(params.status, params.activity); + return { content: [{ type: "text", text: `status set to ${params.status}` }] }; + }, + }; + + return [cotal_roster, cotal_status]; +} + +/** + * Embed an oh-my-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; oh-my-pi's loop is driven straight off + * that inbox via {@link runPeerLoop} — `prompt()` wakes an idle session on the front + * message, `steer()` interjects into a live one (true mid-turn drive), and presence is read + * off the session's event stream. The loop owns reply routing, so the model never + * mis-routes. + */ +export async function runOmpPeer(): Promise { + const mesh = new MeshAgent(configFromEnv()); + mesh.start(); + + // oh-my-pi discovers auth + the model registry from the environment when they + // aren't supplied (a spawned peer gets provider keys via the connector's + // buildLaunch env), so we let createAgentSession default them rather than wiring + // them by hand — matches oh-my-pi's own SDK usage. + const { session } = await createAgentSession({ + cwd: process.cwd(), + sessionManager: SessionManager.inMemory(), + customTools: buildTools(mesh), + }); + + const loop = runPeerLoop({ mesh, session }); + + let shuttingDown = false; + async function shutdown(): Promise { + if (shuttingDown) return; // a second signal during teardown must not re-abort/-dispose/-stop + shuttingDown = true; + try { + await loop.shutdown(); + 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/connector-oh-my-pi/tsconfig.json b/extensions/connector-oh-my-pi/tsconfig.json new file mode 100644 index 00000000..051d08e2 --- /dev/null +++ b/extensions/connector-oh-my-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 7d81818f..45b3a29e 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,10 @@ "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:oh-my-pi": "tsx extensions/connector-oh-my-pi/oh-my-pi-peer.smoke.ts", + "smoke:oh-my-pi-extension": "tsx extensions/connector-oh-my-pi/oh-my-pi-extension.smoke.ts", + "smoke:oh-my-pi-interactive-loop": "tsx extensions/connector-oh-my-pi/interactive-loop.smoke.ts", "smoke:view": "tsx implementations/cli/smoke/view.smoke.ts", "smoke:members": "tsx packages/core/smoke/members.smoke.ts", "smoke:control-reply-bound": "tsx packages/core/smoke/control-reply-bound.smoke.ts", @@ -55,6 +59,7 @@ "smoke:presence-scrub": "tsx packages/core/smoke/presence-offline-scrub.smoke.ts", "smoke:cross-path-dedup": "tsx extensions/connector-core/smoke/cross-path-dedup.smoke.ts", "smoke:feedback": "tsx extensions/connector-core/smoke/feedback.smoke.ts", + "smoke:reconnect-log": "tsx extensions/connector-core/smoke/reconnect-log.smoke.ts", "smoke:opencode": "tsx extensions/connector-opencode/smoke/turn-wedge.smoke.ts", "smoke:opencode-coop": "tsx extensions/connector-opencode/smoke/cooperative-stop.smoke.ts", "smoke:opencode-transcript": "tsx extensions/connector-opencode/smoke/transcript-mirror.smoke.ts", @@ -104,6 +109,7 @@ "@cotal-ai/connector-hermes": "workspace:*", "@cotal-ai/connector-opencode": "workspace:*", "@cotal-ai/core": "workspace:*", + "@cotal-ai/delivery": "workspace:*", "@cotal-ai/manager": "workspace:*", "@cotal-ai/tmux": "workspace:*" }, diff --git a/packages/core/src/endpoint.ts b/packages/core/src/endpoint.ts index 5527c24e..2b55cd06 100644 --- a/packages/core/src/endpoint.ts +++ b/packages/core/src/endpoint.ts @@ -553,23 +553,25 @@ export class CotalEndpoint extends EventEmitter { } } - /** Rebuild with backoff until it sticks or we're stopped. Interruptible: a manual - * {@link reconnect} kicks the backoff so the next attempt runs immediately instead of - * awaiting the full retryMs. One loop at a time ({@link reestablishing}); concurrent - * triggers coalesce via {@link rebuild}. */ + /** Rebuild with exponential backoff until it sticks or we're stopped. The delay grows + * `retryMs · 2^attempt` capped at 30s, so a mesh that stays down is retried politely rather + * than every 3s. Interruptible: a manual {@link reconnect} kicks the backoff so the next + * attempt runs immediately (and resets the growth). One loop at a time ({@link reestablishing}); + * concurrent triggers coalesce via {@link rebuild}. */ private async reestablishLoop(): Promise { if (this.reestablishing) return; this.reestablishing = true; try { - while (!this.stopped) { + for (let attempt = 0; !this.stopped; attempt++) { try { await this.rebuild(); return; // success — re-armed; the supervisor re-triggers on the next terminal close } catch (e) { if (!this.stopped) this.emit("error", e as Error); + const delay = Math.min(30_000, this.retryMs * 2 ** attempt); await new Promise((resolve) => { this.backoffResolve = resolve; - this.backoffTimer = setTimeout(resolve, this.retryMs); + this.backoffTimer = setTimeout(resolve, delay); }); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee55efea..a682cad8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@cotal-ai/core': specifier: workspace:* version: link:packages/core + '@cotal-ai/delivery': + specifier: workspace:* + version: link:implementations/delivery '@cotal-ai/manager': specifier: workspace:* version: link:implementations/manager @@ -126,6 +129,22 @@ importers: specifier: ^2.9.0 version: 2.9.0 + examples/04-oh-my-pi: + dependencies: + '@cotal-ai/core': + specifier: workspace:* + version: link:../../packages/core + '@cotal-ai/manager': + specifier: workspace:* + version: link:../../implementations/manager + '@cotal-ai/oh-my-pi': + specifier: workspace:* + version: link:../../extensions/connector-oh-my-pi + devDependencies: + tsx: + specifier: ^4.22.4 + version: 4.22.4 + extensions/cmux: dependencies: '@cotal-ai/core': @@ -187,6 +206,28 @@ importers: specifier: ^0.28.0 version: 0.28.0 + extensions/connector-oh-my-pi: + dependencies: + '@cotal-ai/connector-core': + specifier: workspace:* + version: link:../connector-core + '@oh-my-pi/pi-coding-agent': + specifier: ^16.3.12 + version: 16.5.2 + tsx: + specifier: ^4.22.4 + version: 4.22.4 + devDependencies: + '@cotal-ai/core': + specifier: workspace:* + version: link:../../packages/core + esbuild: + specifier: ^0.28.0 + version: 0.28.1 + zod: + specifier: ^4.4.3 + version: 4.4.3 + extensions/connector-opencode: dependencies: '@cotal-ai/connector-core': @@ -346,14 +387,45 @@ importers: packages: + '@agentclientprotocol/sdk@1.2.1': + resolution: {integrity: sha512-jwYUdOQR7tc+Zfch53VL4JJyUNK/46q03uUTYb+PjECsmnNl94XFXOfYLJ8RBpMNidXd1rpOAVgb0vqD98xImA==} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + '@alcalzone/ansi-tokenize@0.2.5': resolution: {integrity: sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==} engines: {node: '>=18'} + '@ark/schema@0.56.2': + resolution: {integrity: sha512-Qx4D2JFbBWpntiHZaTv7bGG4H/M2rigiknezKg/WVyDSaLdE4YCcWAOoFB7pjjDqHbbV2OqRfntm1nnXvwMexg==} + + '@ark/util@0.56.2': + resolution: {integrity: sha512-9kU2sUE38FZEGG7l3hamYMBieLYEJh2L1mrYD2eXpT+78EnQSV1bhjxJhnxGBMSTbtwpBSDNSK+K60WvaI/DTQ==} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/runtime@7.29.7': resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@bufbuild/protobuf@2.13.0': + resolution: {integrity: sha512-acq7c49vxfm1ggJ95P70TX7ABDM0vxr1SYD3BB0o0jnBLB4OAqeHyKuN+cD3w80gXEDQ2zxHpR6CUeA+O/aU9g==} + '@changesets/apply-release-plan@7.1.1': resolution: {integrity: sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==} @@ -417,6 +489,16 @@ packages: resolution: {integrity: sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw==} engines: {node: '>= 20.12.0'} + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + + '@dabh/diagnostics@2.0.8': + resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + '@eplightning/nats-server-darwin-arm64@2.14.0': resolution: {integrity: sha512-tBCOf4anrlTnbQimh3KSz6C+0IbdJTyfTFLcol2YfGWzfmMgsXqjfrSZvnRnqMK2hWD/TJ6eIhsv3/UlfdTzEg==} engines: {bun: '>=1', deno: '>=2', node: '>=22'} @@ -771,6 +853,169 @@ packages: peerDependencies: hono: ^4 + '@huggingface/jinja@0.5.9': + resolution: {integrity: sha512-uWTG+l3VJRsl7EXxYizuL3P+cCPoc3cRqbWWRcQN0FhejRfbdq0RNhCmbY/YDtnTcz9icdLYuLDjsnz4d8JMuw==} + engines: {node: '>=18'} + + '@huggingface/tokenizers@0.1.3': + resolution: {integrity: sha512-8rF/RRT10u+kn7YuUbUg0OF30K8rjTc78aHpxT+qJ1uWSqxT1MHi8+9ltwYfkFYJzT/oS+qw3JVfHtNMGAdqyA==} + + '@huggingface/transformers@4.2.0': + resolution: {integrity: sha512-8BRCoBMH0XsWaEIamuR0LrJGAfftgHAfb2Vrffy0VKlSAE/MnUJ5/h/zTfEP3fDIft+nk7TqB8xXEyABGitBjQ==} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -780,6 +1025,25 @@ packages: '@types/node': optional: true + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@kurkle/color@0.3.4': + resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} + '@lydell/node-pty-darwin-arm64@1.2.0-beta.12': resolution: {integrity: sha512-tqaifcY9Cr41SblO1+FLzh8oxxtkNhuW9Dhl22lKme9BreYvKvxEZcdPIXTuqkJc5tagOEC4QHShKmJjLyLXLQ==} cpu: [arm64] @@ -819,6 +1083,9 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@mixmark-io/domino@2.2.0': + resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} + '@modelcontextprotocol/sdk@1.29.0': resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} engines: {node: '>=18'} @@ -829,6 +1096,10 @@ packages: '@cfworker/json-schema': optional: true + '@mozilla/readability@0.6.0': + resolution: {integrity: sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==} + engines: {node: '>=14.0.0'} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==} cpu: [arm64] @@ -887,6 +1158,9 @@ packages: resolution: {integrity: sha512-hH7u7ejIBTFEJIZ8rIcMrHJI6wl+HhpO5sVFs1+ppmXa8RuB2+Lh1+UwTzZ5xTNNm1TKcRkYy+2qCV56qp8RxA==} engines: {node: '>= 18.0.0'} + '@nodable/entities@3.0.0': + resolution: {integrity: sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -899,6 +1173,95 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@oh-my-pi/hashline@16.5.2': + resolution: {integrity: sha512-5dUF7QuERyJoGFgcO8RkFzEtMDpMmvj3OcfQgSGk3GKQscXP2NH3BQreUmCcYsHuvz+hQ30FsEw+cKVKY9homA==} + engines: {bun: '>=1.3.14'} + + '@oh-my-pi/omp-stats@16.5.2': + resolution: {integrity: sha512-olm624iqy0Epgt6AaP85SaKu/aUQNHBLXbOnqh6IXT+WtfMyq0ShUu7J/0HfhEzmFx2qlA4a+Knaf2UZBf2Ewg==} + engines: {bun: '>=1.3.14'} + hasBin: true + + '@oh-my-pi/pi-agent-core@16.5.2': + resolution: {integrity: sha512-5aYKEvhbs/6dAaxvJbjOW6L1dr21TBk6UWTOtg13YudYpPtBbKakfwnMJUMuGsivYNNCipm0Z7uW8OWsg/HTiA==} + engines: {bun: '>=1.3.14'} + + '@oh-my-pi/pi-ai@16.5.2': + resolution: {integrity: sha512-KM3dfTxNaiztckBpMbrN/p651hmIMwbm02wqylUhJmC+XcfcZt0ZjXJ7skgUZ0ljnDCQUhAucTzWhrmMlrfQgA==} + engines: {bun: '>=1.3.14'} + + '@oh-my-pi/pi-catalog@16.5.2': + resolution: {integrity: sha512-adxH47fb4xuoOvPYupTP9nOzR/zSEr0ratNWjDC0yxYUMa3f1MCtKlk5t6a4MF0r5JljgJmUDIeTZ55vxI1QOw==} + engines: {bun: '>=1.3.14'} + + '@oh-my-pi/pi-coding-agent@16.5.2': + resolution: {integrity: sha512-qvMnnZgEyx6xC0dVfYLXuX/afp/wqBzGKu0h3RNvriEBQiobLvA8IxFjyFBokLSq1Sns2bLG0vOhSLmsL1L43Q==} + engines: {bun: '>=1.3.14'} + hasBin: true + + '@oh-my-pi/pi-mnemopi@16.5.2': + resolution: {integrity: sha512-ITrBIahERF/QW/xn7BIwmRRFIGi5ESPHVBNnR2Xws2haUQQcIBLNv9WLhNYZNwzZrDRLsm/OvObIHdv5A2gtyw==} + engines: {bun: '>=1.3.14'} + hasBin: true + peerDependencies: + fastembed: 2.1.0 + onnxruntime-node: 1.21.0 + peerDependenciesMeta: + fastembed: + optional: true + onnxruntime-node: + optional: true + + '@oh-my-pi/pi-natives-darwin-arm64@16.5.2': + resolution: {integrity: sha512-NOMIrjK8NUHhM6+Wyb2t9UqNjhztMG102Dm1xlFBK7044LVYehxWZ1K+HBpluTo7Mmp7OIcVH72/y437TU3DPw==} + engines: {bun: '>=1.3.14'} + cpu: [arm64] + os: [darwin] + + '@oh-my-pi/pi-natives-darwin-x64@16.5.2': + resolution: {integrity: sha512-ZR+8RytmuTcGchFJGmZVrqf3qiXiYKcWI7YYOOpsRu5sOHyhJQt1AQg6QHnGTTUAiMWILOOywQqDfWnS7Jm1bg==} + engines: {bun: '>=1.3.14'} + cpu: [x64] + os: [darwin] + + '@oh-my-pi/pi-natives-linux-arm64@16.5.2': + resolution: {integrity: sha512-e3NmbZwu/SMvVdQV5Gvne8jMhpXE4+izTNQRtrFrNVvWaFf7JvLgOr61UFtu0en/OdS0pt8uj5KfKlwfJRC+Yw==} + engines: {bun: '>=1.3.14'} + cpu: [arm64] + os: [linux] + + '@oh-my-pi/pi-natives-linux-x64@16.5.2': + resolution: {integrity: sha512-o0g7edSynuRGzn6AB8JykQwudX6mApqXl1riqj6hllaFnz2uBsFttHmleiBogY9jWB0AhsaHy5dQ9FWMMHxzSQ==} + engines: {bun: '>=1.3.14'} + cpu: [x64] + os: [linux] + + '@oh-my-pi/pi-natives-win32-x64@16.5.2': + resolution: {integrity: sha512-gnZNte96lmdL0sSRzOaW4k4PEkK4HdirPI85Nwhd0pwPwH2z4YBG1Udz4mUfcRdosGppgUyhbhNOWzwo9QzrIQ==} + engines: {bun: '>=1.3.14'} + cpu: [x64] + os: [win32] + + '@oh-my-pi/pi-natives@16.5.2': + resolution: {integrity: sha512-H+yPlHarVyhmxfVY/2FMIpeP/Dmsmc5TCGLbEjDDoQ1DU97AMxw4rmf/99sNetz/82r0UF0ZbUsP6e+z/UO4cA==} + engines: {bun: '>=1.3.14'} + + '@oh-my-pi/pi-tui@16.5.2': + resolution: {integrity: sha512-U2kqQ4kHHwGvgsaTtO3EnVqMwphb7GEFizQA4vWuMjjt/ckdYEKlU2b/gJI86m99R5tZ3p951rTKmKMWC+RX9Q==} + engines: {bun: '>=1.3.14'} + + '@oh-my-pi/pi-utils@16.5.2': + resolution: {integrity: sha512-Ivwibl2xdec6tQv0Wca+xznf7ZrT5OaqlbW+nZwhPzmO5OTRwz3lceYSqoBdtAjmu3bRqDG1h5vhHr+cSXl4HA==} + engines: {bun: '>=1.3.14'} + + '@oh-my-pi/pi-wire@16.5.2': + resolution: {integrity: sha512-ELdcx4Wow7zTVuj8YHfUdyhWwdalXsPB2Jtnp/h+x270Mwy4B0wUTeUNhRn9SVjPRtFgmDanAhLEjzOoLYu1uw==} + engines: {bun: '>=1.3.14'} + + '@oh-my-pi/snapcompact@16.5.2': + resolution: {integrity: sha512-ev9NHl3+cikPfydI2yMbwEoFdzAQzZ4evMHG0avC9d0NJFNE8ohRadRzceTQBlckBLot1YjM9QUu4QkBJPGrIQ==} + engines: {bun: '>=1.3.14'} + '@opencode-ai/plugin@1.16.2': resolution: {integrity: sha512-FaZhVXrbz93xsdGLCtarRDTeqFt8AkLfh8B34tFBj6G4HXVmKSgBwVXmtELKKC+08xMtawBC9hshiMbXryv6cg==} peerDependencies: @@ -916,9 +1279,168 @@ packages: '@opencode-ai/sdk@1.16.2': resolution: {integrity: sha512-Z/xZ7q79dYeE0afqIk/yFEcRNGEQFcE+H8ssYivUiy+xGZ1mGwT72jpaQZKBwPn3JH4sRCu4KA2lcktBQfcOjg==} + '@opentelemetry/api-logs@0.220.0': + resolution: {integrity: sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@2.10.0': + resolution: {integrity: sha512-bvyMcgLEkozzSzpEEEo1OMoeQ97bxj6Qs2uN3mPrSdDvObMI1myffD/BPqcLlzZO9//d1SqQA/WPw7Cz2AiqhA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.10.0': + resolution: {integrity: sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.9.0': + resolution: {integrity: sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-trace-otlp-proto@0.220.0': + resolution: {integrity: sha512-voTAD8XgJxlK7zLkXh8EzMB09zrQr3tyY/BsnDTlDiQU/UdK58MZ63A3mUjdEDrxMjCVmBHU3WQJhRmQe+Dvzg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.220.0': + resolution: {integrity: sha512-CXYo8UD5Mn9YbgebO2EL4wejtA+gxLmLiu6HCk2KH2BR7XhFN6/6p1UlCb23DYCjeYkndevLHuejCCN1yx4+OQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.220.0': + resolution: {integrity: sha512-lXGrv7KXZ0gNH9SVNUaa6vv6phVYGvJxfXAlMbzbakiXru75f5MZl8Z7oqiMMQD77riVHJCFlQvbZs/VVN2/4A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@2.10.0': + resolution: {integrity: sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/resources@2.9.0': + resolution: {integrity: sha512-jyA5MBLQ+Dkl3+JsZkUoUvL7yHvU64kLsvpXKarWm6347Sl1t1bXFTFykUePNpT5WH5pm9a2Qtt03iIYQhZ1Fg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.220.0': + resolution: {integrity: sha512-WywcTkQtv2iNmt+6y5Kcd4rzvx9bLVsBa2Nwcmg01IUaBTkTow3W4d9KE5vNBpEDtb9tp21WcRBY/lANRrApYA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.9.0': + resolution: {integrity: sha512-Xx8RGS4H5XEBl01WuCreMIpiah9cCXMbSkeuIePPdD2cUpq/vUzYmj8E/MK1OsbOc93FuAD4jfn2WOacKwLn7Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.10.0': + resolution: {integrity: sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@2.10.0': + resolution: {integrity: sha512-GZK/G6oZyBLGlH1pUgeDch7D91KoHd2uotUGIkWCPi9GI5T9X0p4L7nNAMDR1BQjkRYoDqo+ddfVx9t5Uhys+Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-trace@2.10.0': + resolution: {integrity: sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace@2.9.0': + resolution: {integrity: sha512-sGA19HvtrrSKYsseHphluH6j3p6Xa3fqc7c7y8f/7mYWejc1lyDFcpSdD1kYa50HCLUeEo4zA5bW0pniaPszuw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.43.0': + resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==} + 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.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + + '@puppeteer/browsers@3.0.6': + resolution: {integrity: sha512-B/gKoqlFkzhvzsI6jo9K1cZz9o5ypviVv/xu8CwA4grZzyVwN+XfkT+tu8T1zrauuEXv6VhS2oGX+6NL95WcKA==} + engines: {node: '>=22.12.0'} + hasBin: true + peerDependencies: + proxy-agent: '>=8.0.1' + yauzl: ^2.10.0 || ^3.4.0 + peerDependenciesMeta: + proxy-agent: + optional: true + yauzl: + optional: true + + '@puppeteer/browsers@3.1.0': + resolution: {integrity: sha512-RDLpio3fH/qrj5k4DVY6eyiN8tCS0Zovd/6jW//n605oeqkWcUjn+3k+9ZtZBnbwMpsu0F7xDIiKXvVmG5c5Bw==} + engines: {node: '>=22.12.0'} + hasBin: true + peerDependencies: + proxy-agent: '>=8.0.1' + yauzl: ^2.10.0 || ^3.4.0 + peerDependenciesMeta: + proxy-agent: + optional: true + yauzl: + optional: true + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@so-ric/colorspace@1.1.6': + resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@tailwindcss/node@4.3.3': + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -934,9 +1456,19 @@ packages: '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + + '@types/turndown@5.0.6': + resolution: {integrity: sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@xmldom/xmldom@0.8.13': + resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} + engines: {node: '>=10.0.0'} + '@xterm/addon-attach@0.11.0': resolution: {integrity: sha512-JboCN0QAY6ZLY/SSB/Zl2cQ5zW1Eh4X3fH7BnuR1NB7xGRhzbqU2Npmpiw/3zFlxDaU88vtKzok44JKi2L2V2Q==} peerDependencies: @@ -955,6 +1487,9 @@ packages: '@xterm/headless@5.5.0': resolution: {integrity: sha512-5xXB7kdQlFBP82ViMJTwwEc3gKCLGKR/eoxQm4zge7GPBl86tCdI0IdPJjoKd8mUSFXz5V7i/25sfsEkP4j46g==} + '@xterm/headless@6.0.0': + resolution: {integrity: sha512-5Yj1QINYCyzrZtf8OFIHi47iQtI+0qYFPHmouEfG8dHNxbZ9Tb9YGSuLcsEwj9Z+OL75GJqPyJbyoFer80a2Hw==} + '@xterm/xterm@5.5.0': resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} @@ -962,6 +1497,14 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + adm-zip@0.5.18: + resolution: {integrity: sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==} + engines: {node: '>=12.0'} + + adm-zip@0.6.0: + resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==} + engines: {node: '>=14.0'} + ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -993,16 +1536,28 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + anynum@1.0.1: + resolution: {integrity: sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + arkregex@0.0.8: + resolution: {integrity: sha512-PJcx6G1kQTgLKPUbeYlYecDRaKq15AMSGVajlKFYWlPeJRQL+j3dKE6tyMs40HZ99djS1l9Vhl3ezAHy9JBIqQ==} + + arktype@2.2.3: + resolution: {integrity: sha512-7W+0RLTUNJiBFIIZXwOQxSR8Z273IAd6IvqBeG9+gHnQKFsIx2C0iOtGTmMrPnlX4qLXyc5+ll7A0BIj9WrbTg==} + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + auto-bind@5.0.1: resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1011,14 +1566,33 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.11.12: + resolution: {integrity: sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==} + engines: {node: '>=6.0.0'} + hasBin: true + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} + bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + body-parser@2.2.2: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} + boolbase@2.0.0: + resolution: {integrity: sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==} + engines: {node: '>=20.19.0'} + + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + brace-expansion@5.0.6: resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} @@ -1027,6 +1601,11 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.28.7: + resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -1039,6 +1618,13 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001807: + resolution: {integrity: sha512-daRXJ9EB/rdRgu7kV+TTl1YUKtlsMWblPl2sLnpg9DZae16QCegol6A1SmCE31Lm9mXC1sRWGt/krouH+/dl7Q==} + chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -1046,6 +1632,16 @@ packages: chardet@2.2.0: resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + chart.js@4.5.1: + resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==} + engines: {pnpm: '>=8'} + + chromium-bidi@16.0.1: + resolution: {integrity: sha512-J63PGu/9PpeCwLIcKYyzWP6yaVL5pxuBc0shlYCYM8BaAkmlwiQboXO1iNbOgSDbVklEyYFfNEcHD8oOAWacUA==} + engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0'} + peerDependencies: + devtools-protocol: '*' + cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} @@ -1058,16 +1654,36 @@ packages: resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} engines: {node: '>=20'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + code-excerpt@4.0.0: resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - commander@14.0.3: - resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} - engines: {node: '>=20'} + color-convert@3.1.3: + resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} + engines: {node: '>=14.6'} - content-disposition@1.1.0: - resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + color-name@2.1.1: + resolution: {integrity: sha512-p2FdgwVx1a9yWBHP2wI0VgShkDpgN4kZISkxdNipGBJWpa5G6b04OINlVWCyJj0JmfvcPrgqt95E9k8yvaOJFg==} + engines: {node: '>=12.20'} + + color-string@2.1.4: + resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==} + engines: {node: '>=18'} + + color@5.0.3: + resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} + engines: {node: '>=18'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} engines: {node: '>=18'} content-type@1.0.5: @@ -1090,6 +1706,9 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.6: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} @@ -1098,9 +1717,23 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-select@7.0.0: + resolution: {integrity: sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==} + engines: {node: '>=20.19.0'} + + css-what@8.0.0: + resolution: {integrity: sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==} + engines: {node: '>=20.19.0'} + + cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + date-fns@4.4.0: + resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1110,6 +1743,14 @@ packages: supports-color: optional: true + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1122,10 +1763,59 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + devtools-protocol@0.0.1638949: + resolution: {integrity: sha512-mXwg4Fqnv0WR4iuAT/gYUmctNkjILwXFHyZ+m7Ty1dfr0ezZt2U3gnrrJTfRobJTHoXf+IbuFvFITzLrLFjwJA==} + + diff@9.0.0: + resolution: {integrity: sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==} + engines: {node: '>=0.3.1'} + + dingbat-to-unicode@1.0.1: + resolution: {integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + dom-serializer@3.1.1: + resolution: {integrity: sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==} + engines: {node: '>=20.19.0'} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domelementtype@3.0.0: + resolution: {integrity: sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==} + engines: {node: '>=20.19.0'} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domhandler@6.0.1: + resolution: {integrity: sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==} + engines: {node: '>=20.19.0'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + domutils@4.0.2: + resolution: {integrity: sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==} + engines: {node: '>=20.19.0'} + + dot-prop@6.0.1: + resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} + engines: {node: '>=10'} + + duck@0.1.12: + resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1136,17 +1826,39 @@ packages: effect@4.0.0-beta.74: resolution: {integrity: sha512-Yx+Kh12U+i2FmjwEfKs+ePFmpMd43RPD1oGqc/VraSS9bYzvF0Ff3PojwEFEVEewp8xc92Uxu28gTspU4qyvHA==} + electron-to-chromium@1.5.402: + resolution: {integrity: sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA==} + emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + enhanced-resolve@5.24.5: + resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} + engines: {node: '>=10.13.0'} + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -1166,6 +1878,9 @@ packages: es-toolkit@1.47.0: resolution: {integrity: sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==} + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + esbuild@0.28.0: resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} engines: {node: '>=18'} @@ -1176,6 +1891,10 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -1183,6 +1902,10 @@ packages: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -1236,9 +1959,22 @@ packages: fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fast-xml-builder@1.3.0: + resolution: {integrity: sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==} + + fast-xml-parser@5.10.1: + resolution: {integrity: sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw==} + hasBin: true + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + + file-stream-rotator@0.6.1: + resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1254,6 +1990,12 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} + flatbuffers@25.9.23: + resolution: {integrity: sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==} + + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -1278,6 +2020,13 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + generative-bayesian-network@2.1.88: + resolution: {integrity: sha512-kxbW6CCsiEAVdBYPont/6ZVOa47Pyfv5ldYFIvj8wmOx9uQOZ4c8wdR0jEf2pE6DeVuIAfmolm+91bYne6/3uA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.6.0: resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} @@ -1298,6 +2047,14 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} + global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -1309,6 +2066,17 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + guid-typescript@1.0.9: + resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==} + + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1317,10 +2085,20 @@ packages: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} + header-generator@2.1.88: + resolution: {integrity: sha512-12GkTL1CDaPTQ6gkd8TPwxNtn7t3wSExnWU9qgWfEDh8IqpD1NAVcvzfHphIzULez8WP2DK9YQsDegajjDdTKQ==} + engines: {node: '>=16.0.0'} + hono@4.12.23: resolution: {integrity: sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==} engines: {node: '>=16.9.0'} + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + + htmlparser2@10.1.0: + resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -1337,6 +2115,9 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + indent-string@5.0.0: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} @@ -1390,20 +2171,38 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-subdir@1.2.0: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} engines: {node: '>=4'} + is-unsafe@2.0.0: + resolution: {integrity: sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==} + is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + 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==} @@ -1421,6 +2220,9 @@ packages: json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -1429,20 +2231,152 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + kubernetes-types@1.30.0: resolution: {integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==} + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + linkedom@0.18.13: + resolution: {integrity: sha512-ES/o9qotMpzpN2MHs+Iq/JcVoOj8Fa5wiQYrTdFpvAnwXL0g66XHHUc9WUMk6nAlBtGsFQ24ne+SYnvnaQ2FSw==} + engines: {node: '>=16'} + peerDependencies: + canvas: '>= 2' + peerDependenciesMeta: + canvas: + optional: true + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + logform@2.7.0: + resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} + engines: {node: '>= 12.0.0'} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lop@0.4.2: + resolution: {integrity: sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==} + lru-cache@11.5.1: resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} engines: {node: 20 || >=22} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + lucide-react@1.29.0: + resolution: {integrity: sha512-Xs9QFG5+9sNX04MdKVT4++umA+hJ2qsJVlRlRWHQ7qZobXgMiNHSpZ5eZm8JUoGCdNyoEdXoEwa8HVr0DNjOQg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mammoth@1.12.0: + resolution: {integrity: sha512-cwnK1RIcRdDMi2HRx2EXGYlxqIEh0Oo3bLhorgnsVJi2UkbX1+jKxuBNR9PC5+JaX7EkmJxFPmo6mjLpqShI2w==} + engines: {node: '>=12.0.0'} + hasBin: true + + marked@18.0.9: + resolution: {integrity: sha512-/Sa4qiiHZxf0/FQdBBowr9q4r10krCwMvpK48FUBdXdUXScDxiQGR9zCPrFgRVR5LU3iySOiIjy09ZQvADir1w==} + engines: {node: '>= 20'} + hasBin: true + + matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1479,10 +2413,23 @@ packages: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + modern-tar@0.7.7: + resolution: {integrity: sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ==} + engines: {node: '>=18.0.0'} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -1500,26 +2447,48 @@ packages: multipasta@0.2.7: resolution: {integrity: sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==} + mupdf@1.28.0: + resolution: {integrity: sha512-ACUnbpECaQ5JLq04pwd89lS+0IGMest5qL5tb08g9TAR7bDtfqflHEkb2Xm3o4rvC/szguLiV+WEbW9kstj8Sg==} + negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + node-gyp-build-optional-packages@5.2.2: resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} hasBin: true + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + engines: {node: '>=18'} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + nth-check@3.0.1: + resolution: {integrity: sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==} + engines: {node: '>=20.19.0'} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -1527,13 +2496,36 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + onnxruntime-common@1.24.0-dev.20251116-b39e144322: + resolution: {integrity: sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw==} + + onnxruntime-common@1.24.3: + resolution: {integrity: sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==} + + onnxruntime-node@1.24.3: + resolution: {integrity: sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==} + os: [win32, darwin, linux] + + onnxruntime-web@1.26.0-dev.20260416-b7804b056c: + resolution: {integrity: sha512-MD6Ss4GSpQBo6zqoJzyT9LRbKYs7x/JVN23FT24EcEvlqF4VuzPOeH6X38orZPKHQDbprn7K+SBpu0/mj2CQiw==} + + option@0.2.4: + resolution: {integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==} + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + ow@0.28.2: + resolution: {integrity: sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==} + engines: {node: '>=12'} + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -1557,6 +2549,9 @@ packages: package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -1569,6 +2564,14 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-expression-matcher@1.6.2: + resolution: {integrity: sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==} + engines: {node: '>=14.0.0'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1599,15 +2602,29 @@ packages: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} + platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + protobufjs@7.6.5: + resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + puppeteer-core@25.3.0: + resolution: {integrity: sha512-fm+wpUr2oigH1PXZvwgATrM2tYWHMDG8ASzTEe9uukCye4X5Ldx1K5BTHPFKITrIWvQQAQ256d1NpbEveBcKjA==} + engines: {node: '>=22.12.0'} + pure-rand@8.4.0: resolution: {integrity: sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==} @@ -1629,6 +2646,17 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} + react-chartjs-2@5.3.1: + resolution: {integrity: sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==} + peerDependencies: + chart.js: ^4.1.1 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + react-reconciler@0.33.0: resolution: {integrity: sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==} engines: {node: '>=0.10.0'} @@ -1643,6 +2671,13 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -1659,6 +2694,10 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -1666,6 +2705,12 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + 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'} @@ -1676,6 +2721,9 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} @@ -1685,13 +2733,24 @@ packages: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + serve-static@2.2.1: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1700,6 +2759,39 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + sherpa-onnx-darwin-arm64@1.13.4: + resolution: {integrity: sha512-QcYKzyrTzGSx6aKCD6hUODgRS1LetqfG57Z/+i5LCyfMlrgCvDc1lRcl9cdB+TozBsLha9QwLTlI0vmDcf5JKg==} + cpu: [arm64] + os: [darwin] + + sherpa-onnx-darwin-x64@1.13.4: + resolution: {integrity: sha512-6RGeis9K9gV/UQWOgd6Rf3iqXr2/YsBQswxHaCR4hrYkHfEIpHMfFmRWLt6nJJCOWgYW2xFxEd9yzjrafAV/Pw==} + cpu: [x64] + os: [darwin] + + sherpa-onnx-linux-arm64@1.13.4: + resolution: {integrity: sha512-RMjMRqT82BgTXypNNGmLe6ZFYhc3WEvnAGl3DdkK7qB/kuXwkL3iHhV31wAecbnWPsnEpUoD+8cFovWSBzsCuw==} + cpu: [arm64] + os: [linux] + + sherpa-onnx-linux-x64@1.13.4: + resolution: {integrity: sha512-WZh5NCkGPFHHpYSd78iN4OnmxQeSTGyt9uZskH+im/NFHQ7elQ7B0sLzCMeRpvJxiIKvd9C6WxIJ4hYaxClfsQ==} + cpu: [x64] + os: [linux] + + sherpa-onnx-node@1.13.2: + resolution: {integrity: sha512-uIH6SA5Or4pb8HlCYWB3K54XkMtzdef4/tkw1amtIf8GB1tt6hQLpur9p2jSFNfTYRyzZ8XrXofxefXQ0A7EUA==} + + sherpa-onnx-win-ia32@1.13.4: + resolution: {integrity: sha512-/JbPjldrfNv+t+uIS3MlkuhfIf5l3FHUGkRC2oRXgjRqOaVmEyP3vLlQ7dTa4J7raG5oB8c3GoPjuSWSqT9GOQ==} + cpu: [ia32] + os: [win32] + + sherpa-onnx-win-x64@1.13.4: + resolution: {integrity: sha512-R0PWby1VxC14TDZPq7GcfSyXSY6SAFO8Y4JwdCdqouFmeXkZ1L7Is9m98C9KxQ0dN7ZtDzhAmE/43FUs/elXRQ==} + cpu: [x64] + os: [win32] + side-channel-list@1.0.1: resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} @@ -1734,12 +2826,26 @@ packages: resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} engines: {node: '>=20'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + spawndamnit@3.0.1: resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -1756,6 +2862,12 @@ packages: resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} engines: {node: '>=20'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1768,10 +2880,20 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strnum@2.4.1: + resolution: {integrity: sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==} + tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} + tailwindcss@4.3.3: + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -1780,6 +2902,9 @@ packages: resolution: {integrity: sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==} engines: {node: '>=18'} + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1792,6 +2917,10 @@ packages: resolution: {integrity: sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==} engines: {node: '>=20'} + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + ts-json-schema-generator@2.9.0: resolution: {integrity: sha512-NR5ZE108uiPtBHBJNGnhwoUaUx5vWTDJzDFG9YlRoqxPU76n+5FClRh92dcGgysbe1smRmYalM9Saj97GW1J4Q==} engines: {node: '>=22.0.0'} @@ -1805,9 +2934,20 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + turndown-plugin-gfm@1.0.2: + resolution: {integrity: sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg==} + + turndown@7.2.4: + resolution: {integrity: sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ==} + engines: {node: '>=18', npm: '>=9'} + tweetnacl@1.0.3: resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + type-fest@5.7.0: resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} engines: {node: '>=20'} @@ -1816,11 +2956,25 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} + typed-query-selector@2.12.2: + resolution: {integrity: sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + uhyphen@0.2.0: + resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==} + + underscore@1.13.8: + resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -1835,14 +2989,30 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@14.0.0: resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} hasBin: true + vali-date@1.0.0: + resolution: {integrity: sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==} + engines: {node: '>=0.10.0'} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + webdriver-bidi-protocol@0.4.2: + resolution: {integrity: sha512-VSV+fzfChirL3e7jay2yUC7B4HQCGtEWEg/MSSQbK+qWbqeGlRLlXTzPpYr3XGUvbpDHumWZBJxgesg4N7dbtA==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1852,6 +3022,23 @@ packages: resolution: {integrity: sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==} engines: {node: '>=20'} + winston-daily-rotate-file@5.0.0: + resolution: {integrity: sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==} + engines: {node: '>=8'} + peerDependencies: + winston: ^3 + + winston-transport@4.9.0: + resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} + engines: {node: '>= 12.0.0'} + + winston@3.19.0: + resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} + engines: {node: '>= 12.0.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} @@ -1871,11 +3058,31 @@ packages: utf-8-validate: optional: true + xml-naming@0.3.0: + resolution: {integrity: sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==} + engines: {node: '>=16.0.0'} + + xmlbuilder@10.1.1: + resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==} + engines: {node: '>=4.0'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yaml@2.9.0: resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} hasBin: true + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@18.1.0: + resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yoga-layout@3.2.1: resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} @@ -1884,6 +3091,9 @@ packages: peerDependencies: zod: ^3.25.28 || ^4 + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.1.8: resolution: {integrity: sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==} @@ -1892,13 +3102,38 @@ packages: snapshots: + '@agentclientprotocol/sdk@1.2.1(zod@4.4.3)': + dependencies: + zod: 4.4.3 + '@alcalzone/ansi-tokenize@0.2.5': dependencies: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + '@ark/schema@0.56.2': + dependencies: + '@ark/util': 0.56.2 + + '@ark/util@0.56.2': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + '@babel/runtime@7.29.7': {} + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bufbuild/protobuf@2.13.0': {} + '@changesets/apply-release-plan@7.1.1': dependencies: '@changesets/config': 3.1.4 @@ -2054,6 +3289,19 @@ snapshots: fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 + '@colors/colors@1.6.0': {} + + '@dabh/diagnostics@2.0.8': + dependencies: + '@so-ric/colorspace': 1.1.6 + enabled: 2.0.0 + kuler: 2.0.0 + + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + optional: true + '@eplightning/nats-server-darwin-arm64@2.14.0': optional: true @@ -2232,6 +3480,118 @@ snapshots: dependencies: hono: 4.12.23 + '@huggingface/jinja@0.5.9': + optional: true + + '@huggingface/tokenizers@0.1.3': + optional: true + + '@huggingface/transformers@4.2.0': + dependencies: + '@huggingface/jinja': 0.5.9 + '@huggingface/tokenizers': 0.1.3 + onnxruntime-node: 1.24.3 + onnxruntime-web: 1.26.0-dev.20260416-b7804b056c + sharp: 0.34.5 + optional: true + + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@inquirer/external-editor@1.0.3(@types/node@22.20.0)': dependencies: chardet: 2.2.0 @@ -2239,6 +3599,27 @@ snapshots: optionalDependencies: '@types/node': 22.20.0 + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@kurkle/color@0.3.4': {} + '@lydell/node-pty-darwin-arm64@1.2.0-beta.12': optional: true @@ -2282,6 +3663,8 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 + '@mixmark-io/domino@2.2.0': {} + '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': dependencies: '@hono/node-server': 1.19.14(hono@4.12.23) @@ -2304,6 +3687,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@mozilla/readability@0.6.0': {} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': optional: true @@ -2356,6 +3741,8 @@ snapshots: '@nats-io/nkeys': 2.0.3 '@nats-io/nuid': 3.0.0 + '@nodable/entities@3.0.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2368,6 +3755,155 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@oh-my-pi/hashline@16.5.2': + dependencies: + diff: 9.0.0 + lru-cache: 11.5.2 + + '@oh-my-pi/omp-stats@16.5.2': + dependencies: + '@oh-my-pi/pi-ai': 16.5.2 + '@oh-my-pi/pi-catalog': 16.5.2 + '@oh-my-pi/pi-utils': 16.5.2 + '@tailwindcss/node': 4.3.3 + chart.js: 4.5.1 + date-fns: 4.4.0 + lucide-react: 1.29.0(react@19.2.7) + react: 19.2.7 + react-chartjs-2: 5.3.1(chart.js@4.5.1)(react@19.2.7) + react-dom: 19.2.7(react@19.2.7) + tailwindcss: 4.3.3 + + '@oh-my-pi/pi-agent-core@16.5.2': + dependencies: + '@oh-my-pi/pi-ai': 16.5.2 + '@oh-my-pi/pi-catalog': 16.5.2 + '@oh-my-pi/pi-natives': 16.5.2 + '@oh-my-pi/pi-utils': 16.5.2 + '@oh-my-pi/pi-wire': 16.5.2 + '@oh-my-pi/snapcompact': 16.5.2 + '@opentelemetry/api': 1.9.1 + + '@oh-my-pi/pi-ai@16.5.2': + dependencies: + '@bufbuild/protobuf': 2.13.0 + '@oh-my-pi/pi-catalog': 16.5.2 + '@oh-my-pi/pi-utils': 16.5.2 + '@oh-my-pi/pi-wire': 16.5.2 + arktype: 2.2.3 + zod: 4.4.3 + + '@oh-my-pi/pi-catalog@16.5.2': + dependencies: + '@bufbuild/protobuf': 2.13.0 + '@oh-my-pi/pi-utils': 16.5.2 + arktype: 2.2.3 + zod: 4.4.3 + + '@oh-my-pi/pi-coding-agent@16.5.2': + dependencies: + '@agentclientprotocol/sdk': 1.2.1(zod@4.4.3) + '@babel/parser': 7.29.8 + '@mozilla/readability': 0.6.0 + '@oh-my-pi/hashline': 16.5.2 + '@oh-my-pi/omp-stats': 16.5.2 + '@oh-my-pi/pi-agent-core': 16.5.2 + '@oh-my-pi/pi-ai': 16.5.2 + '@oh-my-pi/pi-catalog': 16.5.2 + '@oh-my-pi/pi-mnemopi': 16.5.2 + '@oh-my-pi/pi-natives': 16.5.2 + '@oh-my-pi/pi-tui': 16.5.2 + '@oh-my-pi/pi-utils': 16.5.2 + '@oh-my-pi/pi-wire': 16.5.2 + '@oh-my-pi/snapcompact': 16.5.2 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-proto': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-node': 2.10.0(@opentelemetry/api@1.9.1) + '@puppeteer/browsers': 3.1.0 + '@types/turndown': 5.0.6 + '@xterm/headless': 6.0.0 + arktype: 2.2.3 + chalk: 5.6.2 + diff: 9.0.0 + fast-xml-parser: 5.10.1 + handlebars: 4.7.9 + header-generator: 2.1.88 + linkedom: 0.18.13 + lru-cache: 11.5.2 + mammoth: 1.12.0 + mupdf: 1.28.0 + puppeteer-core: 25.3.0 + turndown: 7.2.4 + turndown-plugin-gfm: 1.0.2 + zod: 4.4.3 + optionalDependencies: + '@huggingface/transformers': 4.2.0 + sherpa-onnx-node: 1.13.2 + transitivePeerDependencies: + - bufferutil + - canvas + - fastembed + - onnxruntime-node + - proxy-agent + - utf-8-validate + - yauzl + + '@oh-my-pi/pi-mnemopi@16.5.2': + dependencies: + '@oh-my-pi/pi-ai': 16.5.2 + '@oh-my-pi/pi-catalog': 16.5.2 + '@oh-my-pi/pi-utils': 16.5.2 + lru-cache: 11.5.2 + + '@oh-my-pi/pi-natives-darwin-arm64@16.5.2': + optional: true + + '@oh-my-pi/pi-natives-darwin-x64@16.5.2': + optional: true + + '@oh-my-pi/pi-natives-linux-arm64@16.5.2': + optional: true + + '@oh-my-pi/pi-natives-linux-x64@16.5.2': + optional: true + + '@oh-my-pi/pi-natives-win32-x64@16.5.2': + optional: true + + '@oh-my-pi/pi-natives@16.5.2': + optionalDependencies: + '@oh-my-pi/pi-natives-darwin-arm64': 16.5.2 + '@oh-my-pi/pi-natives-darwin-x64': 16.5.2 + '@oh-my-pi/pi-natives-linux-arm64': 16.5.2 + '@oh-my-pi/pi-natives-linux-x64': 16.5.2 + '@oh-my-pi/pi-natives-win32-x64': 16.5.2 + + '@oh-my-pi/pi-tui@16.5.2': + dependencies: + '@oh-my-pi/pi-natives': 16.5.2 + '@oh-my-pi/pi-utils': 16.5.2 + lru-cache: 11.5.2 + marked: 18.0.9 + + '@oh-my-pi/pi-utils@16.5.2': + dependencies: + '@oh-my-pi/pi-natives': 16.5.2 + handlebars: 4.7.9 + winston: 3.19.0 + winston-daily-rotate-file: 5.0.0(winston@3.19.0) + + '@oh-my-pi/pi-wire@16.5.2': {} + + '@oh-my-pi/snapcompact@16.5.2': + dependencies: + '@oh-my-pi/pi-ai': 16.5.2 + '@oh-my-pi/pi-natives': 16.5.2 + '@oh-my-pi/pi-utils': 16.5.2 + '@oh-my-pi/pi-wire': 16.5.2 + '@opencode-ai/plugin@1.16.2': dependencies: '@opencode-ai/sdk': 1.16.2 @@ -2378,8 +3914,166 @@ snapshots: dependencies: cross-spawn: 7.0.6 + '@opentelemetry/api-logs@0.220.0': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api@1.9.1': {} + + '@opentelemetry/context-async-hooks@2.10.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.220.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-exporter-base@0.220.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-transformer@0.220.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.220.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/resources@2.10.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/resources@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/sdk-logs@0.220.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.220.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/sdk-metrics@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/sdk-trace-node@2.10.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.10.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-trace@2.10.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/sdk-trace@2.9.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/semantic-conventions@1.43.0': {} + + '@protobufjs/aspromise@1.1.2': + optional: true + + '@protobufjs/base64@1.1.2': + optional: true + + '@protobufjs/codegen@2.0.5': + optional: true + + '@protobufjs/eventemitter@1.1.1': + optional: true + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + optional: true + + '@protobufjs/float@1.0.2': + optional: true + + '@protobufjs/path@1.1.2': + optional: true + + '@protobufjs/pool@1.1.0': + optional: true + + '@protobufjs/utf8@1.1.2': + optional: true + + '@puppeteer/browsers@3.0.6': + dependencies: + modern-tar: 0.7.7 + yargs: 18.1.0 + + '@puppeteer/browsers@3.1.0': + dependencies: + modern-tar: 0.7.7 + yargs: 18.1.0 + + '@sindresorhus/is@4.6.0': {} + + '@so-ric/colorspace@1.1.6': + dependencies: + color: 5.0.3 + text-hex: 1.0.0 + '@standard-schema/spec@1.1.0': {} + '@tailwindcss/node@4.3.3': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.24.5 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.3 + '@types/json-schema@7.0.15': {} '@types/node@12.20.55': {} @@ -2396,10 +4090,16 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/triple-beam@1.3.5': {} + + '@types/turndown@5.0.6': {} + '@types/ws@8.18.1': dependencies: '@types/node': 26.0.0 + '@xmldom/xmldom@0.8.13': {} + '@xterm/addon-attach@0.11.0(@xterm/xterm@5.5.0)': dependencies: '@xterm/xterm': 5.5.0 @@ -2414,6 +4114,8 @@ snapshots: '@xterm/headless@5.5.0': {} + '@xterm/headless@6.0.0': {} + '@xterm/xterm@5.5.0': {} accepts@2.0.0: @@ -2421,6 +4123,11 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 + adm-zip@0.5.18: + optional: true + + adm-zip@0.6.0: {} + ajv-formats@3.0.1(ajv@8.20.0): optionalDependencies: ajv: 8.20.0 @@ -2444,22 +4151,42 @@ snapshots: ansi-styles@6.2.3: {} + anynum@1.0.1: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 argparse@2.0.1: {} + arkregex@0.0.8: + dependencies: + '@ark/util': 0.56.2 + + arktype@2.2.3: + dependencies: + '@ark/schema': 0.56.2 + '@ark/util': 0.56.2 + arkregex: 0.0.8 + array-union@2.1.0: {} + async@3.2.6: {} + auto-bind@5.0.1: {} balanced-match@4.0.4: {} + base64-js@1.5.1: {} + + baseline-browser-mapping@2.11.12: {} + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 + bluebird@3.4.7: {} + body-parser@2.2.2: dependencies: bytes: 3.1.2 @@ -2474,6 +4201,11 @@ snapshots: transitivePeerDependencies: - supports-color + boolbase@2.0.0: {} + + boolean@3.2.0: + optional: true + brace-expansion@5.0.6: dependencies: balanced-match: 4.0.4 @@ -2482,6 +4214,14 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.28.7: + dependencies: + baseline-browser-mapping: 2.11.12 + caniuse-lite: 1.0.30001807 + electron-to-chromium: 1.5.402 + node-releases: 2.0.53 + update-browserslist-db: 1.2.3(browserslist@4.28.7) + bytes@3.1.2: {} call-bind-apply-helpers@1.0.2: @@ -2494,10 +4234,24 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + callsites@3.1.0: {} + + caniuse-lite@1.0.30001807: {} + chalk@5.6.2: {} chardet@2.2.0: {} + chart.js@4.5.1: + dependencies: + '@kurkle/color': 0.3.4 + + chromium-bidi@16.0.1(devtools-protocol@0.0.1638949): + dependencies: + devtools-protocol: 0.0.1638949 + mitt: 3.0.1 + zod: 3.25.76 + cli-boxes@3.0.0: {} cli-cursor@4.0.0: @@ -2509,10 +4263,31 @@ snapshots: slice-ansi: 8.0.0 string-width: 8.2.1 + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + code-excerpt@4.0.0: dependencies: convert-to-spaces: 2.0.1 + color-convert@3.1.3: + dependencies: + color-name: 2.1.1 + + color-name@2.1.1: {} + + color-string@2.1.4: + dependencies: + color-name: 2.1.1 + + color@5.0.3: + dependencies: + color-convert: 3.1.3 + color-string: 2.1.4 + commander@14.0.3: {} content-disposition@1.1.0: {} @@ -2527,6 +4302,8 @@ snapshots: cookie@0.7.2: {} + core-util-is@1.0.3: {} + cors@2.8.6: dependencies: object-assign: 4.1.1 @@ -2538,23 +4315,103 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-select@7.0.0: + dependencies: + boolbase: 2.0.0 + css-what: 8.0.0 + domhandler: 6.0.1 + domutils: 4.0.2 + nth-check: 3.0.1 + + css-what@8.0.0: {} + + cssom@0.5.0: {} + csstype@3.2.3: {} + date-fns@4.4.0: {} + debug@4.4.3: dependencies: ms: 2.1.3 + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + optional: true + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + optional: true + depd@2.0.0: {} detect-indent@6.1.0: {} - detect-libc@2.1.2: + detect-libc@2.1.2: {} + + detect-node@2.1.0: optional: true + devtools-protocol@0.0.1638949: {} + + diff@9.0.0: {} + + dingbat-to-unicode@1.0.1: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + dom-serializer@3.1.1: + dependencies: + domelementtype: 3.0.0 + domhandler: 6.0.1 + entities: 8.0.0 + + domelementtype@2.3.0: {} + + domelementtype@3.0.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domhandler@6.0.1: + dependencies: + domelementtype: 3.0.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + domutils@4.0.2: + dependencies: + dom-serializer: 3.1.1 + domelementtype: 3.0.0 + domhandler: 6.0.1 + + dot-prop@6.0.1: + dependencies: + is-obj: 2.0.0 + + duck@0.1.12: + dependencies: + underscore: 1.13.8 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -2576,15 +4433,30 @@ snapshots: uuid: 14.0.0 yaml: 2.9.0 + electron-to-chromium@1.5.402: {} + emoji-regex@10.6.0: {} + enabled@2.0.0: {} + encodeurl@2.0.0: {} + enhanced-resolve@5.24.5: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + entities@4.5.0: {} + + entities@7.0.1: {} + + entities@8.0.0: {} + environment@1.1.0: {} es-define-property@1.0.1: {} @@ -2597,6 +4469,9 @@ snapshots: es-toolkit@1.47.0: {} + es6-error@4.1.1: + optional: true + esbuild@0.28.0: optionalDependencies: '@esbuild/aix-ppc64': 0.28.0 @@ -2655,10 +4530,15 @@ snapshots: '@esbuild/win32-ia32': 0.28.1 '@esbuild/win32-x64': 0.28.1 + escalade@3.2.0: {} + escape-html@1.0.3: {} escape-string-regexp@2.0.0: {} + escape-string-regexp@4.0.0: + optional: true + esprima@4.0.1: {} etag@1.8.1: {} @@ -2735,10 +4615,30 @@ snapshots: dependencies: fast-string-width: 3.0.2 + fast-xml-builder@1.3.0: + dependencies: + path-expression-matcher: 1.6.2 + xml-naming: 0.3.0 + + fast-xml-parser@5.10.1: + dependencies: + '@nodable/entities': 3.0.0 + fast-xml-builder: 1.3.0 + is-unsafe: 2.0.0 + path-expression-matcher: 1.6.2 + strnum: 2.4.1 + xml-naming: 0.3.0 + fastq@1.20.1: dependencies: reusify: 1.1.0 + fecha@4.2.3: {} + + file-stream-rotator@0.6.1: + dependencies: + moment: 2.30.1 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -2761,6 +4661,11 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 + flatbuffers@25.9.23: + optional: true + + fn.name@1.1.0: {} + forwarded@0.2.0: {} fresh@2.0.0: {} @@ -2782,6 +4687,13 @@ snapshots: function-bind@1.1.2: {} + generative-bayesian-network@2.1.88: + dependencies: + adm-zip: 0.6.0 + tslib: 2.8.1 + + get-caller-file@2.0.5: {} + get-east-asian-width@1.6.0: {} get-intrinsic@1.3.0: @@ -2812,6 +4724,22 @@ snapshots: minipass: 7.1.3 path-scurry: 2.0.2 + global-agent@3.0.0: + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.8.5 + serialize-error: 7.0.1 + optional: true + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + optional: true + globby@11.1.0: dependencies: array-union: 2.1.0 @@ -2825,14 +4753,47 @@ snapshots: graceful-fs@4.2.11: {} + guid-typescript@1.0.9: + optional: true + + handlebars@4.7.9: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + optional: true + has-symbols@1.1.0: {} hasown@2.0.4: dependencies: function-bind: 1.1.2 + header-generator@2.1.88: + dependencies: + browserslist: 4.28.7 + generative-bayesian-network: 2.1.88 + ow: 0.28.2 + tslib: 2.8.1 + hono@4.12.23: {} + html-escaper@3.0.3: {} + + htmlparser2@10.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 7.0.1 + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -2849,6 +4810,8 @@ snapshots: ignore@5.3.2: {} + immediate@3.0.6: {} + indent-string@5.0.0: {} inherits@2.0.4: {} @@ -2907,16 +4870,26 @@ snapshots: is-number@7.0.0: {} + is-obj@2.0.0: {} + is-promise@4.0.0: {} + is-stream@2.0.1: {} + is-subdir@1.2.0: dependencies: better-path-resolve: 1.0.0 + is-unsafe@2.0.0: {} + is-windows@1.0.2: {} + isarray@1.0.0: {} + isexe@2.0.0: {} + jiti@2.7.0: {} + jose@6.2.3: {} js-yaml@3.14.2: @@ -2932,22 +4905,145 @@ snapshots: json-schema-typed@8.0.2: {} + json-stringify-safe@5.0.1: + optional: true + json5@2.2.3: {} jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + kubernetes-types@1.30.0: {} + kuler@2.0.0: {} + + lie@3.3.0: + dependencies: + immediate: 3.0.6 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + linkedom@0.18.13: + dependencies: + css-select: 7.0.0 + cssom: 0.5.0 + html-escaper: 3.0.3 + htmlparser2: 10.1.0 + uhyphen: 0.2.0 + locate-path@5.0.0: dependencies: p-locate: 4.1.0 + lodash.isequal@4.5.0: {} + lodash.startcase@4.4.0: {} + logform@2.7.0: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.5.0 + triple-beam: 1.4.1 + + long@5.3.2: + optional: true + + lop@0.4.2: + dependencies: + duck: 0.1.12 + option: 0.2.4 + underscore: 1.13.8 + lru-cache@11.5.1: {} + lru-cache@11.5.2: {} + + lucide-react@1.29.0(react@19.2.7): + dependencies: + react: 19.2.7 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mammoth@1.12.0: + dependencies: + '@xmldom/xmldom': 0.8.13 + argparse: 1.0.10 + base64-js: 1.5.1 + bluebird: 3.4.7 + dingbat-to-unicode: 1.0.1 + jszip: 3.10.1 + lop: 0.4.2 + path-is-absolute: 1.0.1 + underscore: 1.13.8 + xmlbuilder: 10.1.1 + + marked@18.0.9: {} + + matcher@3.0.0: + dependencies: + escape-string-regexp: 4.0.0 + optional: true + math-intrinsics@1.1.0: {} media-typer@1.1.0: {} @@ -2973,8 +5069,16 @@ snapshots: dependencies: brace-expansion: 5.0.6 + minimist@1.2.8: {} + minipass@7.1.3: {} + mitt@3.0.1: {} + + modern-tar@0.7.7: {} + + moment@2.30.1: {} + mri@1.2.0: {} ms@2.1.3: {} @@ -2997,19 +5101,34 @@ snapshots: multipasta@0.2.7: {} + mupdf@1.28.0: {} + negotiator@1.0.0: {} + neo-async@2.6.2: {} + node-gyp-build-optional-packages@5.2.2: dependencies: detect-libc: 2.1.2 optional: true + node-releases@2.0.53: {} + normalize-path@3.0.0: {} + nth-check@3.0.1: + dependencies: + boolbase: 2.0.0 + object-assign@4.1.1: {} + object-hash@3.0.0: {} + object-inspect@1.13.4: {} + object-keys@1.1.1: + optional: true + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -3018,12 +5137,49 @@ snapshots: dependencies: wrappy: 1.0.2 + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 + onnxruntime-common@1.24.0-dev.20251116-b39e144322: + optional: true + + onnxruntime-common@1.24.3: + optional: true + + onnxruntime-node@1.24.3: + dependencies: + adm-zip: 0.5.18 + global-agent: 3.0.0 + onnxruntime-common: 1.24.3 + optional: true + + onnxruntime-web@1.26.0-dev.20260416-b7804b056c: + dependencies: + flatbuffers: 25.9.23 + guid-typescript: 1.0.9 + long: 5.3.2 + onnxruntime-common: 1.24.0-dev.20251116-b39e144322 + platform: 1.3.6 + protobufjs: 7.6.5 + optional: true + + option@0.2.4: {} + outdent@0.5.0: {} + ow@0.28.2: + dependencies: + '@sindresorhus/is': 4.6.0 + callsites: 3.1.0 + dot-prop: 6.0.1 + lodash.isequal: 4.5.0 + vali-date: 1.0.0 + p-filter@2.1.0: dependencies: p-map: 2.1.0 @@ -3044,12 +5200,18 @@ snapshots: dependencies: quansync: 0.2.11 + pako@1.0.11: {} + parseurl@1.3.3: {} patch-console@2.0.0: {} path-exists@4.0.0: {} + path-expression-matcher@1.6.2: {} + + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-scurry@2.0.2: @@ -3069,13 +5231,47 @@ snapshots: pkce-challenge@5.0.1: {} + platform@1.3.6: + optional: true + prettier@2.8.8: {} + process-nextick-args@2.0.1: {} + + protobufjs@7.6.5: + 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.2 + '@types/node': 22.20.0 + long: 5.3.2 + optional: true + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 + puppeteer-core@25.3.0: + dependencies: + '@puppeteer/browsers': 3.0.6 + chromium-bidi: 16.0.1(devtools-protocol@0.0.1638949) + devtools-protocol: 0.0.1638949 + typed-query-selector: 2.12.2 + webdriver-bidi-protocol: 0.4.2 + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - proxy-agent + - utf-8-validate + - yauzl + pure-rand@8.4.0: {} qs@6.15.2: @@ -3095,6 +5291,16 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 + react-chartjs-2@5.3.1(chart.js@4.5.1)(react@19.2.7): + dependencies: + chart.js: 4.5.1 + react: 19.2.7 + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + react-reconciler@0.33.0(react@19.2.7): dependencies: react: 19.2.7 @@ -3109,6 +5315,22 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + require-from-string@2.0.2: {} resolve-from@5.0.0: {} @@ -3120,6 +5342,16 @@ snapshots: reusify@1.1.0: {} + roarr@2.15.4: + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.4 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + optional: true + router@2.2.0: dependencies: debug: 4.4.3 @@ -3134,12 +5366,19 @@ snapshots: dependencies: queue-microtask: 1.2.3 + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + safe-stable-stringify@2.5.0: {} safer-buffer@2.1.2: {} scheduler@0.27.0: {} + semver-compare@1.0.0: + optional: true + semver@7.8.5: {} send@1.2.1: @@ -3158,6 +5397,11 @@ snapshots: transitivePeerDependencies: - supports-color + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + optional: true + serve-static@2.2.1: dependencies: encodeurl: 2.0.0 @@ -3167,14 +5411,76 @@ snapshots: transitivePeerDependencies: - supports-color + setimmediate@1.0.5: {} + setprototypeof@1.2.0: {} + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + sherpa-onnx-darwin-arm64@1.13.4: + optional: true + + sherpa-onnx-darwin-x64@1.13.4: + optional: true + + sherpa-onnx-linux-arm64@1.13.4: + optional: true + + sherpa-onnx-linux-x64@1.13.4: + optional: true + + sherpa-onnx-node@1.13.2: + optionalDependencies: + sherpa-onnx-darwin-arm64: 1.13.4 + sherpa-onnx-darwin-x64: 1.13.4 + sherpa-onnx-linux-arm64: 1.13.4 + sherpa-onnx-linux-x64: 1.13.4 + sherpa-onnx-win-ia32: 1.13.4 + sherpa-onnx-win-x64: 1.13.4 + optional: true + + sherpa-onnx-win-ia32@1.13.4: + optional: true + + sherpa-onnx-win-x64@1.13.4: + optional: true + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 @@ -3216,6 +5522,10 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + spawndamnit@3.0.1: dependencies: cross-spawn: 7.0.6 @@ -3223,6 +5533,11 @@ snapshots: sprintf-js@1.0.3: {} + sprintf-js@1.1.3: + optional: true + + stack-trace@0.0.10: {} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -3240,6 +5555,14 @@ snapshots: get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -3250,12 +5573,22 @@ snapshots: strip-bom@3.0.0: {} + strnum@2.4.1: + dependencies: + anynum: 1.0.1 + tagged-tag@1.0.0: {} + tailwindcss@4.3.3: {} + + tapable@2.3.3: {} + term-size@2.2.1: {} terminal-size@4.0.1: {} + text-hex@1.0.0: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -3264,6 +5597,8 @@ snapshots: toml@4.1.1: {} + triple-beam@1.4.1: {} + ts-json-schema-generator@2.9.0: dependencies: '@types/json-schema': 7.0.15 @@ -3283,8 +5618,17 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + turndown-plugin-gfm@1.0.2: {} + + turndown@7.2.4: + dependencies: + '@mixmark-io/domino': 2.2.0 + tweetnacl@1.0.3: {} + type-fest@0.13.1: + optional: true + type-fest@5.7.0: dependencies: tagged-tag: 1.0.0 @@ -3295,8 +5639,17 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 + typed-query-selector@2.12.2: {} + typescript@5.9.3: {} + uglify-js@3.19.3: + optional: true + + uhyphen@0.2.0: {} + + underscore@1.13.8: {} + undici-types@6.21.0: {} undici-types@8.3.0: {} @@ -3305,10 +5658,22 @@ snapshots: unpipe@1.0.0: {} + update-browserslist-db@1.2.3(browserslist@4.28.7): + dependencies: + browserslist: 4.28.7 + escalade: 3.2.0 + picocolors: 1.1.1 + + util-deprecate@1.0.2: {} + uuid@14.0.0: {} + vali-date@1.0.0: {} + vary@1.1.2: {} + webdriver-bidi-protocol@0.4.2: {} + which@2.0.2: dependencies: isexe: 2.0.0 @@ -3317,6 +5682,36 @@ snapshots: dependencies: string-width: 8.2.1 + winston-daily-rotate-file@5.0.0(winston@3.19.0): + dependencies: + file-stream-rotator: 0.6.1 + object-hash: 3.0.0 + triple-beam: 1.4.1 + winston: 3.19.0 + winston-transport: 4.9.0 + + winston-transport@4.9.0: + dependencies: + logform: 2.7.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.19.0: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.8 + async: 3.2.6 + is-stream: 2.0.1 + logform: 2.7.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.5.0 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.9.0 + + wordwrap@1.0.0: {} + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 @@ -3327,14 +5722,33 @@ snapshots: ws@8.21.0: {} + xml-naming@0.3.0: {} + + xmlbuilder@10.1.1: {} + + y18n@5.0.8: {} + yaml@2.9.0: {} + yargs-parser@22.0.0: {} + + yargs@18.1.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 8.2.1 + y18n: 5.0.8 + yargs-parser: 22.0.0 + yoga-layout@3.2.1: {} zod-to-json-schema@3.25.2(zod@4.4.3): dependencies: zod: 4.4.3 + zod@3.25.76: {} + zod@4.1.8: {} zod@4.4.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0774c3ad..160bd541 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,3 +8,6 @@ packages: allowBuilds: esbuild: true msgpackr-extract: false + onnxruntime-node: false + protobufjs: false + sharp: false