From d80e6d6e0da0995febab86847254d16b41f2d713 Mon Sep 17 00:00:00 2001 From: Sam Heutmaker Date: Sat, 26 Sep 2026 17:06:57 -0700 Subject: [PATCH 1/6] Pin whip v1.0.0 and vendor its SDK Loupe now drives `whipcode`, the binary whip v1.0.0 ships: the pinned self-install downloads `whipcode--` from that tag into ~/.loupe/bin/whipcode, `whipcode` on PATH is used as is (every v1 binary is whipcode, so the v0 identity check goes), and LOUPE_WHIP_BIN overrides both. The workflows install the same tag explicitly. The SDK that talks to that daemon is vendored under vendor/@whip from the v1.0.0 tag (protocol 6.8), refreshed with `task vendor:whip`; VERSION records the tag, commit, and wire protocol. Nothing uses it yet; the next commit moves the harness onto it. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/loupe-chat.yml | 20 +- .github/workflows/loupe-review.yml | 22 +- .gitignore | 3 + .oxfmtrc.json | 2 +- .oxlintrc.json | 2 +- Taskfile.yml | 4 + bun.lock | 16 + package.json | 2 +- packages/harness/package.json | 3 +- packages/harness/src/index.ts | 9 +- packages/harness/src/whip-binary.ts | 93 +- packages/harness/tests/vendor.test.ts | 25 + packages/harness/tests/whip-binary.test.ts | 24 + scripts/vendor-whip.ts | 95 + vendor/@whip/VERSION | 4 + vendor/@whip/protocol/generated/index.d.ts | 4521 + vendor/@whip/protocol/generated/index.js | 1313 + .../protocol/generated/request-validators.js | 73016 ++++++++++++++++ .../protocol/generated/response-validators.js | 66731 ++++++++++++++ vendor/@whip/protocol/package.json | 14 + vendor/@whip/sdk/README.md | 774 + vendor/@whip/sdk/dist/agents.d.ts | 242 + vendor/@whip/sdk/dist/agents.js | 381 + vendor/@whip/sdk/dist/agents.js.map | 1 + vendor/@whip/sdk/dist/browser.d.ts | 42 + vendor/@whip/sdk/dist/browser.js | 295 + vendor/@whip/sdk/dist/browser.js.map | 1 + vendor/@whip/sdk/dist/client.d.ts | 164 + vendor/@whip/sdk/dist/client.js | 532 + vendor/@whip/sdk/dist/client.js.map | 1 + vendor/@whip/sdk/dist/command.d.ts | 52 + vendor/@whip/sdk/dist/command.js | 154 + vendor/@whip/sdk/dist/command.js.map | 1 + vendor/@whip/sdk/dist/content.d.ts | 27 + vendor/@whip/sdk/dist/content.js | 169 + vendor/@whip/sdk/dist/content.js.map | 1 + vendor/@whip/sdk/dist/errors.d.ts | 17 + vendor/@whip/sdk/dist/errors.js | 34 + vendor/@whip/sdk/dist/errors.js.map | 1 + vendor/@whip/sdk/dist/executions.d.ts | 91 + vendor/@whip/sdk/dist/executions.js | 511 + vendor/@whip/sdk/dist/executions.js.map | 1 + vendor/@whip/sdk/dist/index.d.ts | 23 + vendor/@whip/sdk/dist/index.js | 12 + vendor/@whip/sdk/dist/index.js.map | 1 + vendor/@whip/sdk/dist/node.d.ts | 4 + vendor/@whip/sdk/dist/node.js | 73 + vendor/@whip/sdk/dist/node.js.map | 1 + vendor/@whip/sdk/dist/react.d.ts | 2385 + vendor/@whip/sdk/dist/react.js | 12 + vendor/@whip/sdk/dist/react.js.map | 1 + vendor/@whip/sdk/dist/schema.d.ts | 86 + vendor/@whip/sdk/dist/schema.js | 60 + vendor/@whip/sdk/dist/schema.js.map | 1 + vendor/@whip/sdk/dist/services.d.ts | 101 + vendor/@whip/sdk/dist/services.js | 134 + vendor/@whip/sdk/dist/services.js.map | 1 + vendor/@whip/sdk/dist/session.d.ts | 76 + vendor/@whip/sdk/dist/session.js | 108 + vendor/@whip/sdk/dist/session.js.map | 1 + vendor/@whip/sdk/dist/state.d.ts | 175 + vendor/@whip/sdk/dist/state.js | 1114 + vendor/@whip/sdk/dist/state.js.map | 1 + vendor/@whip/sdk/dist/subscription.d.ts | 30 + vendor/@whip/sdk/dist/subscription.js | 143 + vendor/@whip/sdk/dist/subscription.js.map | 1 + vendor/@whip/sdk/dist/terminals.d.ts | 50 + vendor/@whip/sdk/dist/terminals.js | 66 + vendor/@whip/sdk/dist/terminals.js.map | 1 + vendor/@whip/sdk/dist/testing-node.d.ts | 49 + vendor/@whip/sdk/dist/testing-node.js | 174 + vendor/@whip/sdk/dist/testing-node.js.map | 1 + vendor/@whip/sdk/dist/testing.d.ts | 112 + vendor/@whip/sdk/dist/testing.js | 183 + vendor/@whip/sdk/dist/testing.js.map | 1 + vendor/@whip/sdk/dist/trace.d.ts | 66 + vendor/@whip/sdk/dist/trace.js | 123 + vendor/@whip/sdk/dist/trace.js.map | 1 + vendor/@whip/sdk/dist/transport.d.ts | 14 + vendor/@whip/sdk/dist/transport.js | 72 + vendor/@whip/sdk/dist/transport.js.map | 1 + vendor/@whip/sdk/dist/turn.d.ts | 184 + vendor/@whip/sdk/dist/turn.js | 291 + vendor/@whip/sdk/dist/turn.js.map | 1 + vendor/@whip/sdk/dist/util.d.ts | 11 + vendor/@whip/sdk/dist/util.js | 67 + vendor/@whip/sdk/dist/util.js.map | 1 + vendor/@whip/sdk/package.json | 42 + 88 files changed, 155380 insertions(+), 86 deletions(-) create mode 100644 packages/harness/tests/vendor.test.ts create mode 100644 packages/harness/tests/whip-binary.test.ts create mode 100644 scripts/vendor-whip.ts create mode 100644 vendor/@whip/VERSION create mode 100644 vendor/@whip/protocol/generated/index.d.ts create mode 100644 vendor/@whip/protocol/generated/index.js create mode 100644 vendor/@whip/protocol/generated/request-validators.js create mode 100644 vendor/@whip/protocol/generated/response-validators.js create mode 100644 vendor/@whip/protocol/package.json create mode 100644 vendor/@whip/sdk/README.md create mode 100644 vendor/@whip/sdk/dist/agents.d.ts create mode 100644 vendor/@whip/sdk/dist/agents.js create mode 100644 vendor/@whip/sdk/dist/agents.js.map create mode 100644 vendor/@whip/sdk/dist/browser.d.ts create mode 100644 vendor/@whip/sdk/dist/browser.js create mode 100644 vendor/@whip/sdk/dist/browser.js.map create mode 100644 vendor/@whip/sdk/dist/client.d.ts create mode 100644 vendor/@whip/sdk/dist/client.js create mode 100644 vendor/@whip/sdk/dist/client.js.map create mode 100644 vendor/@whip/sdk/dist/command.d.ts create mode 100644 vendor/@whip/sdk/dist/command.js create mode 100644 vendor/@whip/sdk/dist/command.js.map create mode 100644 vendor/@whip/sdk/dist/content.d.ts create mode 100644 vendor/@whip/sdk/dist/content.js create mode 100644 vendor/@whip/sdk/dist/content.js.map create mode 100644 vendor/@whip/sdk/dist/errors.d.ts create mode 100644 vendor/@whip/sdk/dist/errors.js create mode 100644 vendor/@whip/sdk/dist/errors.js.map create mode 100644 vendor/@whip/sdk/dist/executions.d.ts create mode 100644 vendor/@whip/sdk/dist/executions.js create mode 100644 vendor/@whip/sdk/dist/executions.js.map create mode 100644 vendor/@whip/sdk/dist/index.d.ts create mode 100644 vendor/@whip/sdk/dist/index.js create mode 100644 vendor/@whip/sdk/dist/index.js.map create mode 100644 vendor/@whip/sdk/dist/node.d.ts create mode 100644 vendor/@whip/sdk/dist/node.js create mode 100644 vendor/@whip/sdk/dist/node.js.map create mode 100644 vendor/@whip/sdk/dist/react.d.ts create mode 100644 vendor/@whip/sdk/dist/react.js create mode 100644 vendor/@whip/sdk/dist/react.js.map create mode 100644 vendor/@whip/sdk/dist/schema.d.ts create mode 100644 vendor/@whip/sdk/dist/schema.js create mode 100644 vendor/@whip/sdk/dist/schema.js.map create mode 100644 vendor/@whip/sdk/dist/services.d.ts create mode 100644 vendor/@whip/sdk/dist/services.js create mode 100644 vendor/@whip/sdk/dist/services.js.map create mode 100644 vendor/@whip/sdk/dist/session.d.ts create mode 100644 vendor/@whip/sdk/dist/session.js create mode 100644 vendor/@whip/sdk/dist/session.js.map create mode 100644 vendor/@whip/sdk/dist/state.d.ts create mode 100644 vendor/@whip/sdk/dist/state.js create mode 100644 vendor/@whip/sdk/dist/state.js.map create mode 100644 vendor/@whip/sdk/dist/subscription.d.ts create mode 100644 vendor/@whip/sdk/dist/subscription.js create mode 100644 vendor/@whip/sdk/dist/subscription.js.map create mode 100644 vendor/@whip/sdk/dist/terminals.d.ts create mode 100644 vendor/@whip/sdk/dist/terminals.js create mode 100644 vendor/@whip/sdk/dist/terminals.js.map create mode 100644 vendor/@whip/sdk/dist/testing-node.d.ts create mode 100644 vendor/@whip/sdk/dist/testing-node.js create mode 100644 vendor/@whip/sdk/dist/testing-node.js.map create mode 100644 vendor/@whip/sdk/dist/testing.d.ts create mode 100644 vendor/@whip/sdk/dist/testing.js create mode 100644 vendor/@whip/sdk/dist/testing.js.map create mode 100644 vendor/@whip/sdk/dist/trace.d.ts create mode 100644 vendor/@whip/sdk/dist/trace.js create mode 100644 vendor/@whip/sdk/dist/trace.js.map create mode 100644 vendor/@whip/sdk/dist/transport.d.ts create mode 100644 vendor/@whip/sdk/dist/transport.js create mode 100644 vendor/@whip/sdk/dist/transport.js.map create mode 100644 vendor/@whip/sdk/dist/turn.d.ts create mode 100644 vendor/@whip/sdk/dist/turn.js create mode 100644 vendor/@whip/sdk/dist/turn.js.map create mode 100644 vendor/@whip/sdk/dist/util.d.ts create mode 100644 vendor/@whip/sdk/dist/util.js create mode 100644 vendor/@whip/sdk/dist/util.js.map create mode 100644 vendor/@whip/sdk/package.json diff --git a/.github/workflows/loupe-chat.yml b/.github/workflows/loupe-chat.yml index 58be282..00dc7f8 100644 --- a/.github/workflows/loupe-chat.yml +++ b/.github/workflows/loupe-chat.yml @@ -37,20 +37,20 @@ jobs: contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) steps: - uses: actions/checkout@v5 - - name: Install whip (pinned v0.6.5) + - name: Install whipcode (pinned v1.0.0) continue-on-error: true env: GH_TOKEN: ${{ github.token }} run: | - # Pin to v0.6.5 — the last release whose binary installs as `whip`. - # (v1.0.0 renamed the binary to `whipcode`.) The harness resolves - # either name, but CI stays on the pinned `whip` for stability. - tag="v0.6.5" - echo "Installing whip $tag" - gh release download "$tag" --repo context-labs/whip --pattern "*-linux-x64" --output whip --clobber - chmod +x whip - sudo mv whip /usr/local/bin/whip - whip --version || true + # Pin to the release loupe's vendored SDK came from (vendor/@whip/VERSION); + # the SDK refuses a daemon from another protocol major. If this step + # fails, loupe downloads the same tag itself at review time. + tag="v1.0.0" + echo "Installing whipcode $tag" + gh release download "$tag" --repo context-labs/whip --pattern "whipcode-linux-x64" --output whipcode --clobber + chmod +x whipcode + sudo mv whipcode /usr/local/bin/whipcode + whipcode --version || true - name: Load loupe secrets from Infisical continue-on-error: true uses: Infisical/secrets-action@v1.0.15 diff --git a/.github/workflows/loupe-review.yml b/.github/workflows/loupe-review.yml index 472c50a..907e081 100644 --- a/.github/workflows/loupe-review.yml +++ b/.github/workflows/loupe-review.yml @@ -1,7 +1,7 @@ name: "loupe self-review" # loupe dogfooding loupe: reviews its own PRs with the config in .loupe/config.json -# (whip, kimi-k3 + panel, maxTurns 5). Advisory and non-blocking — the required +# (whipcode v1.0.0, glm-5.3, maxTurns 15). Advisory and non-blocking — the required # check is CI (ci.yml); this never gates a merge. @loupe chat lives in # loupe-chat.yml. # @@ -34,20 +34,20 @@ jobs: github.event.pull_request.head.repo.full_name == github.repository steps: - uses: actions/checkout@v5 - - name: Install whip (pinned v0.6.5) + - name: Install whipcode (pinned v1.0.0) continue-on-error: true env: GH_TOKEN: ${{ github.token }} run: | - # Pin to v0.6.5 — the last release whose binary installs as `whip`. - # (v1.0.0 renamed the binary to `whipcode`.) The harness resolves - # either name, but CI stays on the pinned `whip` for stability. - tag="v0.6.5" - echo "Installing whip $tag" - gh release download "$tag" --repo context-labs/whip --pattern "*-linux-x64" --output whip --clobber - chmod +x whip - sudo mv whip /usr/local/bin/whip - whip --version || true + # Pin to the release loupe's vendored SDK came from (vendor/@whip/VERSION); + # the SDK refuses a daemon from another protocol major. If this step + # fails, loupe downloads the same tag itself at review time. + tag="v1.0.0" + echo "Installing whipcode $tag" + gh release download "$tag" --repo context-labs/whip --pattern "whipcode-linux-x64" --output whipcode --clobber + chmod +x whipcode + sudo mv whipcode /usr/local/bin/whipcode + whipcode --version || true - name: Load loupe secrets from Infisical continue-on-error: true uses: Infisical/secrets-action@v1.0.15 diff --git a/.gitignore b/.gitignore index a8c453f..9d13bb3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ packages/*/src/**/*.js packages/*/src/**/*.js.map packages/*/src/**/*.d.ts packages/*/src/**/*.d.ts.map + +# vendored prebuilt packages (see vendor/@whip/VERSION) +!vendor/@whip/sdk/dist/ diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 3b96e9b..e72efac 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -19,5 +19,5 @@ "^[./]" ], "sortPackageJson": false, - "ignorePatterns": ["**/dist", "**/build"] + "ignorePatterns": ["**/dist", "**/build", "**/vendor/**"] } diff --git a/.oxlintrc.json b/.oxlintrc.json index 5583923..3b4c629 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -4,7 +4,7 @@ "categories": { "correctness": "error" }, "options": { "typeAware": true }, "env": { "builtin": true }, - "ignorePatterns": ["**/dist/**", "**/build/**", "**/coverage/**"], + "ignorePatterns": ["**/dist/**", "**/build/**", "**/coverage/**", "**/vendor/**"], "rules": { "typescript/no-explicit-any": "warn", "typescript/no-floating-promises": "error", diff --git a/Taskfile.yml b/Taskfile.yml index 0497a35..445d04b 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -36,3 +36,7 @@ tasks: cmds: - task: format:fix - task: lint:fix + vendor:whip: + desc: Refresh vendor/@whip from a whip checkout (WHIP=../whip) + cmds: + - bun run scripts/vendor-whip.ts {{.WHIP | default "../whip"}} diff --git a/bun.lock b/bun.lock index 158db88..8875aae 100644 --- a/bun.lock +++ b/bun.lock @@ -66,6 +66,7 @@ "version": "0.1.0", "dependencies": { "@loupe/logger": "workspace:*", + "@whip/sdk": "workspace:*", }, "devDependencies": { "bun-types": "1.3.14", @@ -90,6 +91,17 @@ "vitest": "4.1.10", }, }, + "vendor/@whip/protocol": { + "name": "@whip/protocol", + "version": "3.0.0", + }, + "vendor/@whip/sdk": { + "name": "@whip/sdk", + "version": "0.1.0", + "dependencies": { + "@whip/protocol": "workspace:*", + }, + }, }, "packages": { "@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="], @@ -368,6 +380,10 @@ "@vitest/utils": ["@vitest/utils@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA=="], + "@whip/protocol": ["@whip/protocol@workspace:vendor/@whip/protocol"], + + "@whip/sdk": ["@whip/sdk@workspace:vendor/@whip/sdk"], + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], diff --git a/package.json b/package.json index cc66830..5ae00af 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "packageManager": "bun@1.3.14", - "workspaces": ["packages/*"], + "workspaces": ["packages/*", "vendor/@whip/*"], "scripts": { "format": "oxfmt --check \"**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}\"", "format:fix": "oxfmt --write \"**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}\"", diff --git a/packages/harness/package.json b/packages/harness/package.json index bbd6b50..0249560 100644 --- a/packages/harness/package.json +++ b/packages/harness/package.json @@ -9,7 +9,8 @@ "test": "vitest run" }, "dependencies": { - "@loupe/logger": "workspace:*" + "@loupe/logger": "workspace:*", + "@whip/sdk": "workspace:*" }, "devDependencies": { "bun-types": "1.3.14", diff --git a/packages/harness/src/index.ts b/packages/harness/src/index.ts index 2c4c253..aecf7b5 100644 --- a/packages/harness/src/index.ts +++ b/packages/harness/src/index.ts @@ -236,7 +236,7 @@ export function codexHarness(): Harness { function runWhipStreaming( args: readonly string[], ctx: HarnessContext, - binary = "whip", + binary = "whipcode", ): Promise { const log = ctx.logger.child("whip"); // Known secrets (resolved credential values handed to the subprocess via env) @@ -488,9 +488,8 @@ export function shouldRetryWithoutCacheKey( * the provider + model panel instead. `-max-turns` caps the tool loop as a * safety net in case the model ignores the headless directive. * - * Binary resolution: the `whip` on PATH wins; if it is absent (e.g. the - * upstream v1.0.0 rename to `whipcode` removed it), loupe falls back to a - * pinned release download so harness "whip" keeps working with no user action. + * Binary resolution: LOUPE_WHIP_BIN, else `whipcode` on PATH, else the pinned + * release (see whip-binary.ts) downloaded on demand. */ export function whipHarness(): Harness { return { @@ -498,7 +497,7 @@ export function whipHarness(): Harness { credentialKeys: [], available: () => resolveWhipBinary(null).then((b) => b !== null), review: async (ctx) => { - const binary = (await resolveWhipBinary(ctx.logger)) ?? "whip"; + const binary = (await resolveWhipBinary(ctx.logger)) ?? "whipcode"; // Agentic reviews need room to explore the checkout with tools; headless // diff-only reviews should answer in one turn, capped as a safety net. // The agentic cap is configurable (config.json maxTurns / --max-turns). diff --git a/packages/harness/src/whip-binary.ts b/packages/harness/src/whip-binary.ts index c77d68a..655a556 100644 --- a/packages/harness/src/whip-binary.ts +++ b/packages/harness/src/whip-binary.ts @@ -1,17 +1,9 @@ /** - * Pinned-whip installer - one home for the "whip binary may be missing or - * renamed" patch logic. Everything loupe needs to run harness "whip" lives - * here; the harness in index.ts only calls resolveWhipBinary(). - * - * Background: the upstream whip CLI renamed its binary to `whipcode` at v1.0.0 - * (and its config dir to ~/.whipcode/WHIPCODE_HOME). Environments that install - * "latest" now have a renamed or missing `whip`, so loupe: - * 1. identity-checks any `whip` on PATH (`whip --version` must report whip), - * rejecting renamings like whipcode masquerading as `whip`; - * 2. falls back to a pinned release (PINNED_WHIP_TAG), downloaded on demand - * into a private cache dir; and - * 3. single-flights the download so concurrent reviewers inside one loupe - * process share a single install instead of racing one another. + * Pinned-whip installer: which `whipcode` binary loupe drives. The harness in + * index.ts only calls resolveWhipBinary(), which prefers LOUPE_WHIP_BIN, then a + * `whipcode` on PATH, then a pinned release (PINNED_WHIP_TAG) downloaded on + * demand into a private cache dir. The download is single-flighted so + * concurrent reviewers inside one loupe process share one install. */ import { spawn } from "node:child_process"; @@ -29,13 +21,11 @@ import { join } from "node:path"; import type { Logger } from "@loupe/logger"; /** - * whip v0.6.5 - the pinned release loupe installs on demand. The latest whip - * release (v1.0.0+) renamed the binary to `whipcode` and broke the harness, so - * when a user selects harness "whip" and no genuine `whip` is on PATH, loupe - * downloads this exact tag instead of failing with "CLI is not installed." - * Bump deliberately when adopting the v1+ whipcode generation. + * The whip release loupe installs on demand when no `whipcode` is on PATH. + * Pinned so the vendored SDK (vendor/@whip/VERSION) and the daemon it talks to + * come from the same release; bump both together. */ -export const PINNED_WHIP_TAG = "v0.6.5"; +export const PINNED_WHIP_TAG = "v1.0.0"; /** Minimal logging surface so this module works without a real logger. */ type WhipLogger = Pick; @@ -45,30 +35,19 @@ const noLogger: WhipLogger = { warn: () => {}, }; -/** - * Run `whip --version` and check the binary identifies itself as genuine whip - * (output starting with "whip v"). The v1.0.0 release renamed the CLI to - * whipcode, but many CI install steps still download "latest" and save it as - * `whip` - so a PATH hit may actually be whipcode, which reads a different - * config dir and rejects loupe's model panel: - * whipcode: unknown model "glm-5.3" (models: ...) - */ -export function isGenuineWhipOnPath(): Promise { - return new Promise((resolve) => { - const p = spawn("whip", ["--version"]); - let stdout = ""; - p.on("error", () => resolve(false)); - p.on("close", () => resolve(/^\s*whip v/i.test(stdout))); - p.stdout?.on("data", (chunk: Buffer) => { - stdout += chunk.toString(); - }); - }); -} - /** The cache path where the pinned binary lives once installed. */ export function pinnedWhipPath(): string { const home = process.env.HOME ?? tmpdir(); - return join(home, ".loupe", "bin", "whip"); + return join(home, ".loupe", "bin", "whipcode"); +} + +/** The release asset for this platform (`whipcode-linux-x64` and friends). */ +export function pinnedWhipAsset( + platform = process.platform, + arch = process.arch, +): string { + const os = platform === "darwin" ? "darwin" : "linux"; + return `whipcode-${os}-${arch === "arm64" ? "arm64" : "x64"}`; } /** True when a complete, executable binary is already at the cache path. */ @@ -120,19 +99,11 @@ function downloadPinnedWhip(log: WhipLogger): Promise { const binDir = join(dest, ".."); mkdirSync(binDir, { recursive: true }); if (isInstalled(dest)) return Promise.resolve(dest); - const mode = - process.platform === "darwin" && process.arch === "arm64" - ? "darwin-arm64" - : process.platform === "darwin" - ? "darwin-x64" - : process.platform === "linux" && process.arch === "arm64" - ? "linux-arm64" - : "linux-x64"; - const url = `https://github.com/context-labs/whip/releases/download/${PINNED_WHIP_TAG}/whip-${mode}`; + const url = `https://github.com/context-labs/whip/releases/download/${PINNED_WHIP_TAG}/${pinnedWhipAsset()}`; // Unique staging dir per attempt: safe against same-process concurrency and // a same-machine second process. Inside binDir so rename stays on one fs. const stagingDir = mkdtempSync(join(binDir, ".download-")); - const staging = join(stagingDir, "whip"); + const staging = join(stagingDir, "whipcode"); return new Promise((resolve) => { const cleanup = (): void => { try { @@ -175,20 +146,30 @@ function downloadPinnedWhip(log: WhipLogger): Promise { }); } +function onPath(cmd: string): Promise { + return new Promise((resolve) => { + const p = spawn("which", [cmd], { stdio: "ignore" }); + p.on("error", () => resolve(false)); + p.on("close", (code) => resolve(code === 0)); + }); +} + /** * The one entry point the whip harness uses: resolve which binary to spawn. - * Prefers a genuine `whip` on PATH (identity-checked), else installs/returns - * the pinned release from the private cache. Returns null only when neither - * is possible (no genuine PATH binary and the download failed). + * LOUPE_WHIP_BIN wins, then a `whipcode` on PATH, else the pinned release from + * the private cache (installed on demand). Returns null only when nothing is + * on PATH and the download failed. */ export async function resolveWhipBinary( logger: WhipLogger | null, ): Promise { - if (await isGenuineWhipOnPath()) return "whip"; + const override = process.env.LOUPE_WHIP_BIN; + if (override) return override; + if (await onPath("whipcode")) return "whipcode"; const installed = await installPinnedWhip(logger); - if (installed && installed !== "whip") { + if (installed) { logger?.info( - `no genuine whip on PATH; using pinned release ${PINNED_WHIP_TAG} from ${installed}`, + `no whipcode on PATH; using pinned release ${PINNED_WHIP_TAG} from ${installed}`, ); } return installed; diff --git a/packages/harness/tests/vendor.test.ts b/packages/harness/tests/vendor.test.ts new file mode 100644 index 0000000..c1be28c --- /dev/null +++ b/packages/harness/tests/vendor.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "vitest"; + +import { defineAgent, tool } from "@whip/sdk/agents"; +import { createWhipClient, unixSocket } from "@whip/sdk/node"; + +// The vendored SDK resolves under Bun and exposes the entry points loupe uses. +describe("vendored @whip/sdk", () => { + it("exposes the node transport and the agents API", () => { + expect(typeof unixSocket("/tmp/x.sock")).toBe("function"); + expect(typeof createWhipClient).toBe("function"); + const ping = tool({ + name: "ping", + description: "Ping", + input: { type: "object" }, + execute: () => "pong", + }); + const agent = defineAgent({ + id: "loupe-vendor-test", + modules: ["context"], + tools: [ping], + }); + expect(agent.document.id).toBe("loupe-vendor-test"); + expect(agent.document.tools?.[0]?.name).toBe("ping"); + }); +}); diff --git a/packages/harness/tests/whip-binary.test.ts b/packages/harness/tests/whip-binary.test.ts new file mode 100644 index 0000000..13b82b0 --- /dev/null +++ b/packages/harness/tests/whip-binary.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; + +import { + PINNED_WHIP_TAG, + pinnedWhipAsset, + pinnedWhipPath, +} from "../src/whip-binary"; + +describe("pinned whipcode release", () => { + it("is the release the vendored SDK came from", () => { + expect(PINNED_WHIP_TAG).toBe("v1.0.0"); + }); + + it("names the v1 release assets per platform", () => { + expect(pinnedWhipAsset("linux", "x64")).toBe("whipcode-linux-x64"); + expect(pinnedWhipAsset("linux", "arm64")).toBe("whipcode-linux-arm64"); + expect(pinnedWhipAsset("darwin", "arm64")).toBe("whipcode-darwin-arm64"); + expect(pinnedWhipAsset("darwin", "x64")).toBe("whipcode-darwin-x64"); + }); + + it("caches the binary under its v1 name", () => { + expect(pinnedWhipPath()).toMatch(/\/\.loupe\/bin\/whipcode$/); + }); +}); diff --git a/scripts/vendor-whip.ts b/scripts/vendor-whip.ts new file mode 100644 index 0000000..1862efe --- /dev/null +++ b/scripts/vendor-whip.ts @@ -0,0 +1,95 @@ +#!/usr/bin/env bun +/** + * Refresh vendor/@whip from a whip checkout: build the SDK there, copy the + * built protocol and SDK packages here, and rewrite their manifests as plain + * workspace packages. Usage: bun run scripts/vendor-whip.ts [path-to-whip] + */ +import { spawnSync } from "node:child_process"; +import { + cpSync, + mkdirSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { join, resolve } from "node:path"; + +const whip = resolve(process.argv[2] ?? "../whip"); +const out = resolve(import.meta.dir, "..", "vendor", "@whip"); + +function run(cmd: string, args: readonly string[], cwd: string): string { + const res = spawnSync(cmd, args, { cwd, encoding: "utf8" }); + if (res.status !== 0) { + throw new Error(`${cmd} ${args.join(" ")} failed:\n${res.stderr}`); + } + return res.stdout.trim(); +} + +type Manifest = Record; + +function manifest(path: string, edit: (m: Manifest) => void): string { + const m = JSON.parse(readFileSync(path, "utf8")) as Manifest; + for (const k of ["private", "scripts", "engines", "devDependencies"]) { + delete m[k]; + } + edit(m); + return JSON.stringify(m, null, 2) + "\n"; +} + +run("npm", ["run", "build"], join(whip, "packages", "sdk")); + +const protocol = join(out, "protocol"); +const sdk = join(out, "sdk"); +rmSync(join(protocol, "generated"), { recursive: true, force: true }); +rmSync(join(sdk, "dist"), { recursive: true, force: true }); +mkdirSync(protocol, { recursive: true }); +mkdirSync(sdk, { recursive: true }); +cpSync(join(whip, "packages/protocol/generated"), join(protocol, "generated"), { + recursive: true, +}); +cpSync(join(whip, "packages/sdk/dist"), join(sdk, "dist"), { + recursive: true, +}); +cpSync(join(whip, "packages/sdk/README.md"), join(sdk, "README.md")); +writeFileSync( + join(protocol, "package.json"), + manifest(join(whip, "packages/protocol/package.json"), () => {}), +); +writeFileSync( + join(sdk, "package.json"), + manifest(join(whip, "packages/sdk/package.json"), (m) => { + m["dependencies"] = { "@whip/protocol": "workspace:*" }; + delete m["peerDependencies"]; + delete m["peerDependenciesMeta"]; + }), +); + +// The wire protocol version the daemon reports (protocol_major.protocol_minor); +// the npm package version says nothing about daemon compatibility. +const wire = /Major\s*=\s*(\d+)[\s\S]*?Minor\s*=\s*(\d+)/.exec( + readFileSync(join(whip, "internal/protocol/types.go"), "utf8"), +); +const version = wire ? `${wire[1]}.${wire[2]}` : "unknown"; +// A tag when HEAD sits on one (a release), else the branch name. +const ref = spawnSync("git", ["describe", "--tags", "--exact-match"], { + cwd: whip, + encoding: "utf8", +}); +const source = + ref.status === 0 + ? ref.stdout.trim() + : run("git", ["rev-parse", "--abbrev-ref", "HEAD"], whip); +const commit = run("git", ["rev-parse", "--short", "HEAD"], whip); +writeFileSync( + join(out, "VERSION"), + [ + `source: context-labs/whip ${source} @ ${commit}`, + `protocol: ${version}`, + `built: ${new Date().toISOString().slice(0, 16)}Z`, + "refresh: task vendor:whip WHIP=", + "", + ].join("\n"), +); +console.log( + `vendored @whip/sdk (protocol ${version}) from ${source} @ ${commit}`, +); diff --git a/vendor/@whip/VERSION b/vendor/@whip/VERSION new file mode 100644 index 0000000..4c05bab --- /dev/null +++ b/vendor/@whip/VERSION @@ -0,0 +1,4 @@ +source: context-labs/whip v1.0.0 @ 7e8bb75c5 +protocol: 6.8 +built: 2026-09-27T00:06Z +refresh: task vendor:whip WHIP= diff --git a/vendor/@whip/protocol/generated/index.d.ts b/vendor/@whip/protocol/generated/index.d.ts new file mode 100644 index 0000000..367b2f5 --- /dev/null +++ b/vendor/@whip/protocol/generated/index.d.ts @@ -0,0 +1,4521 @@ +// Generated from Go wire types. Run npm run generate. +export interface Accepted { + accepted: boolean; +} + +export interface AgentCancelParams { + id: string; + turn_id: string; +} + +export interface AgentInputParams { + design_context?: null | { + context_attachment_id: string; + screenshot_attachment_id?: string; + elements: + | null + | { + label: string; + selector?: string; + }[]; + element_count: number; + page_url?: string; + page_title?: string; + }; + id: string; + text: string; + delivery?: string; + parts?: + | null + | { + type: string; + text?: string; + image_url?: null | { + url: string; + }; + w?: number; + h?: number; + }[]; + attachments?: + | null + | { + kind: "image" | "text"; + content: { + reference_id: string; + digest: string; + size: string; + media_type?: string; + source?: string; + }; + name?: string; + }[]; +} + +export type AgentListResult = + | null + | { + execution_engine: string; + last_turn?: null | { + turn_id?: string; + status: string; + started_at?: string; + finished_at?: string; + event_seq: string; + error?: string; + error_truncated?: boolean; + error_details?: null | { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + model_calls?: number; + compactions?: number; + last_activity_at?: string; + }; + id: string; + root_id: string; + parent_id: string; + name: string; + model: string; + provider: string; + effort: string; + cwd: string; + report: string; + status: string; + pending_mail: number; + lifecycle_phase: string; + blocking_reason: string; + terminal_cause: string; + allowed_controls: null | string[]; + }[]; + +export interface AgentSubmitResult { + agent_id: string; + inbox_seq: string; + kind?: string; + status: string; +} + +export interface AgentTranscriptResult { + accounting?: { + root_id: string; + agent_id: string; + scope: string; + revision: string; + reported_cost_micros: string; + estimated_cost_micros: string; + reported_cost_calls: string; + estimated_cost_calls: string; + unknown_cost_calls: string; + reported_calls: string; + estimated_calls: string; + pending_calls: string; + }; + cursor: string; + agent: { + execution_engine: string; + last_turn?: null | { + turn_id?: string; + status: string; + started_at?: string; + finished_at?: string; + event_seq: string; + error?: string; + error_truncated?: boolean; + error_details?: null | { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + model_calls?: number; + compactions?: number; + last_activity_at?: string; + }; + id: string; + root_id: string; + parent_id: string; + name: string; + model: string; + provider: string; + effort: string; + cwd: string; + report: string; + status: string; + pending_mail: number; + lifecycle_phase: string; + blocking_reason: string; + terminal_cause: string; + allowed_controls: null | string[]; + }; + page: { + history_revision: string; + through_seq: number; + next_seq: number; + has_more: boolean; + messages: + | null + | { + presentation?: null | { + design_context?: null | { + context_attachment_id: string; + screenshot_attachment_id?: string; + elements: + | null + | { + label: string; + selector?: string; + }[]; + element_count: number; + page_url?: string; + page_title?: string; + context_part_index: number; + screenshot_part_index?: null | number; + }; + version: number; + turn_id?: string; + parts?: + | null + | { + id: string; + kind: string; + start?: number; + end?: number; + text?: string; + tool_name?: string; + status?: string; + call_id?: string; + hosts?: + | null + | { + invocation_id: string; + name: string; + summary?: string; + status: string; + duration?: string; + error?: string; + display?: null | { + target?: string; + command?: string; + query?: string; + child_id?: string; + label?: string; + }; + }[]; + omitted?: number; + }[]; + omitted?: number; + }; + role?: string; + authored?: boolean; + sent_at?: null | string; + seq: number; + message?: null | { + presentation?: null | { + design_context?: null | { + context_attachment_id: string; + screenshot_attachment_id?: string; + elements: + | null + | { + label: string; + selector?: string; + }[]; + element_count: number; + page_url?: string; + page_title?: string; + context_part_index: number; + screenshot_part_index?: null | number; + }; + version: number; + turn_id?: string; + parts?: + | null + | { + id: string; + kind: string; + start?: number; + end?: number; + text?: string; + tool_name?: string; + status?: string; + call_id?: string; + hosts?: + | null + | { + invocation_id: string; + name: string; + summary?: string; + status: string; + duration?: string; + error?: string; + display?: null | { + target?: string; + command?: string; + query?: string; + child_id?: string; + label?: string; + }; + }[]; + omitted?: number; + }[]; + omitted?: number; + }; + role: string; + content: + | string + | { + type: string; + text?: string; + image_url?: null | { + url: string; + }; + w?: number; + h?: number; + }[]; + tool_calls?: + | null + | { + id: string; + type: string; + function: { + name: string; + arguments: string; + }; + duration_ms?: number; + exit_code?: number; + }[]; + tool_call_id?: string; + name?: string; + authored?: boolean; + sent_at?: null | string; + usage?: null | { + reported?: boolean; + cost?: null | number; + prompt_tokens: number; + completion_tokens: number; + prompt_tokens_details?: null | { + cached_tokens: number; + }; + completion_tokens_details?: null | { + reasoning_tokens: number; + }; + }; + model?: string; + rewound_from?: string; + call_id?: string; + }; + body?: null | { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }[]; + }; + presentation?: + | null + | { + seq: string; + kind: string; + payload: unknown; + }[]; + inbox?: + | null + | { + delivery_seq?: string; + origin?: string; + command_client_id?: string; + command_id?: string; + steer_turn_id?: string; + preview?: null | { + design_context?: null | { + context_attachment_id: string; + screenshot_attachment_id?: string; + elements: + | null + | { + label: string; + selector?: string; + }[]; + element_count: number; + page_url?: string; + page_title?: string; + }; + text: string; + truncated?: boolean; + attachments?: + | null + | { + kind: string; + name?: string; + content: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }[]; + attachment_count?: number; + }; + root_id: string; + agent_id: string; + seq: string; + kind: string; + status: string; + payload: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }[]; +} + +export interface ArchiveParams { + archived: boolean; +} + +export interface ArchiveResult { + archived: boolean; +} + +export interface BoundedTranscriptPage { + history_revision: string; + through_seq: number; + next_seq: number; + has_more: boolean; + messages: + | null + | { + presentation?: null | { + design_context?: null | { + context_attachment_id: string; + screenshot_attachment_id?: string; + elements: + | null + | { + label: string; + selector?: string; + }[]; + element_count: number; + page_url?: string; + page_title?: string; + context_part_index: number; + screenshot_part_index?: null | number; + }; + version: number; + turn_id?: string; + parts?: + | null + | { + id: string; + kind: string; + start?: number; + end?: number; + text?: string; + tool_name?: string; + status?: string; + call_id?: string; + hosts?: + | null + | { + invocation_id: string; + name: string; + summary?: string; + status: string; + duration?: string; + error?: string; + display?: null | { + target?: string; + command?: string; + query?: string; + child_id?: string; + label?: string; + }; + }[]; + omitted?: number; + }[]; + omitted?: number; + }; + role?: string; + authored?: boolean; + sent_at?: null | string; + seq: number; + message?: null | { + presentation?: null | { + design_context?: null | { + context_attachment_id: string; + screenshot_attachment_id?: string; + elements: + | null + | { + label: string; + selector?: string; + }[]; + element_count: number; + page_url?: string; + page_title?: string; + context_part_index: number; + screenshot_part_index?: null | number; + }; + version: number; + turn_id?: string; + parts?: + | null + | { + id: string; + kind: string; + start?: number; + end?: number; + text?: string; + tool_name?: string; + status?: string; + call_id?: string; + hosts?: + | null + | { + invocation_id: string; + name: string; + summary?: string; + status: string; + duration?: string; + error?: string; + display?: null | { + target?: string; + command?: string; + query?: string; + child_id?: string; + label?: string; + }; + }[]; + omitted?: number; + }[]; + omitted?: number; + }; + role: string; + content: + | string + | { + type: string; + text?: string; + image_url?: null | { + url: string; + }; + w?: number; + h?: number; + }[]; + tool_calls?: + | null + | { + id: string; + type: string; + function: { + name: string; + arguments: string; + }; + duration_ms?: number; + exit_code?: number; + }[]; + tool_call_id?: string; + name?: string; + authored?: boolean; + sent_at?: null | string; + usage?: null | { + reported?: boolean; + cost?: null | number; + prompt_tokens: number; + completion_tokens: number; + prompt_tokens_details?: null | { + cached_tokens: number; + }; + completion_tokens_details?: null | { + reasoning_tokens: number; + }; + }; + model?: string; + rewound_from?: string; + call_id?: string; + }; + body?: null | { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }[]; +} + +export interface BrowserCommand { + command_id: string; + operation_id: string; + root_id: string; + agent_id: string; + provider_epoch: string; + scope: { + provider_id: string; + provider_epoch: string; + tab_id: string; + tab_generation: string; + profile_id: string; + attachment_id?: string; + attachment_generation?: string; + rights: null | string[]; + preview?: null | { + host_id: string; + host_identity: string; + connection_generation: string; + environment_id: string; + loopback: string; + ports: null | number[]; + }; + }; + expected_document?: string; + deadline_millis: string; + kind: string; + arguments: unknown; +} + +export interface BrowserCommandCancel { + command_id: string; + root_id: string; + provider_epoch: string; + attachment_generation: string; + reason: string; +} + +export interface BrowserCommandResultParams { + command_id: string; + root_id: string; + provider_epoch: string; + attachment_generation: string; + document_revision: string; + result?: unknown; + error?: null | { + kind: string; + message: string; + }; + screenshot?: null | { + reference_id: string; + digest: string; + size: string; + media_type?: string; + source?: string; + }; +} + +export interface BrowserDriverParams { + driver: string; +} + +export interface BrowserInventoryRequest { + request_id: string; + root_id: string; + agent_id: string; + provider_id: string; + provider_epoch: string; + tabs: + | null + | { + tab_id: string; + tab_generation: string; + }[]; +} + +export interface BrowserInventoryResultParams { + request_id: string; + root_id: string; + provider_epoch: string; + tabs: + | null + | { + tab_id: string; + tab_generation: string; + document_revision: string; + url: string; + title: string; + state: string; + requestable: boolean; + attachment_id?: string; + }[]; + error?: null | { + kind: string; + message: string; + }; +} + +export interface BrowserProviderBindParams { + availability?: boolean; + root_id: string; + version: number; + desktop_id: string; + window_id: string; + offer_revision: string; + create_profile_id: string; + expected_provider_epoch?: string; + offered_tabs: + | null + | { + tab_id: string; + tab_generation: string; + profile_id: string; + document_revision?: string; + url?: string; + title?: string; + preview?: null | { + host_id: string; + host_identity: string; + connection_generation: string; + environment_id: string; + loopback: string; + ports: null | number[]; + }; + }[]; + offered_preview_hosts: + | null + | { + host_id: string; + host_identity: string; + connection_generation: string; + environment_id: string; + loopback: string; + ports: null | number[]; + }[]; +} + +export interface BrowserProviderBindResult { + version: number; + provider_id: string; + provider_epoch: string; +} + +export interface BrowserProviderEventParams { + root_id: string; + provider_epoch: string; + tab_id: string; + tab_generation: string; + attachment_id: string; + attachment_generation: string; + sequence: string; + operation_id?: string; + document_revision: string; + kind: string; + method?: string; + params?: unknown; + url?: string; + title?: string; +} + +export interface BrowserProviderRevoked { + root_id: string; + provider_id: string; + provider_epoch: string; + reason: string; +} + +export interface BrowserProviderUnbindParams { + root_id: string; + provider_epoch: string; +} + +export interface BrowserStatusResult { + enabled: boolean; + driver?: string; +} + +export interface BudgetCapParams { + id: string; + kind: string; + limit: string; +} + +export interface BudgetState { + kind: string; + limit: string | null; + used: string; + reserved: string; + remaining: string | null; + uncertain: string; + incomplete: boolean; +} + +export interface CancelParams { + turn_id?: string; + target_command_id?: string; +} + +export interface CapabilityRecord { + id: string; + root_id: string; + agent_id: string; + issuer_agent_id: string; + operations: null | string[]; + scopes: null | string[]; + file_scope?: string; + file_issuer_id?: string; + file_issuer_generation?: string; + mcp: + | null + | { + server: string; + tool: string; + definition: string; + }[]; + mcp_all: boolean; + browser?: null | { + provider_id: string; + provider_epoch: string; + tab_id: string; + tab_generation: string; + profile_id: string; + attachment_id?: string; + attachment_generation?: string; + rights: null | string[]; + preview?: null | { + host_id: string; + host_identity: string; + connection_generation: string; + environment_id: string; + loopback: string; + ports: null | number[]; + }; + }; + browser_issuer_id?: string; + browser_issuer_generation?: number; + browser_delegation_only?: boolean; + generation: string; + status: string; + expires_at: string; + created_at: string; + updated_at: string; +} + +export interface CatalogResult { + themes: + | null + | { + id: string; + name: string; + dark: boolean; + source: string; + }[]; + errors: + | null + | { + file: string; + message: string; + }[]; + truncated: boolean; +} + +export interface CatalogRevision { + revision: string; +} + +export interface CheckpointParams { + reason?: string; +} + +export interface ClearHistoryParams { + expected_revision?: string | null; +} + +export interface CommandParams { + command_id: string; + scope: string; + root_id?: string; + operation: string; + payload?: unknown; +} + +export interface CommandResult { + content?: null | { + reference_id: string; + digest: string; + size: string; + media_type?: string; + source?: string; + }; + operation: string; + result?: unknown; + failure?: null | { + data?: null | { + kind: string; + }; + code: number; + message: string; + }; + command_id: string; + ingress_seq: string; + status: string; +} + +export interface CommandStatusParams { + command_id: string; +} + +export type CompactionListResult = + | null + | { + seq: number; + cutoff: number; + summary: string; + }[]; + +export interface CompactionParams { + model?: string; + provider?: string; +} + +export interface CompactionResult { + cutoff: number; + model?: string; + usage: { + reported?: boolean; + cost?: null | number; + prompt_tokens: number; + completion_tokens: number; + prompt_tokens_details?: null | { + cached_tokens: number; + }; + completion_tokens_details?: null | { + reasoning_tokens: number; + }; + }; +} + +export interface CompactionRetryResult { + undone: boolean; + sequence?: number; +} + +export interface CompactionSettingsResult { + model?: string; + provider?: string; + builtin_default: boolean; +} + +export interface CompletionParams { + root_id: string; + agent_id?: string; + kind: string; + prefix: string; + limit: number; +} + +export interface CompletionResult { + warnings?: null | string[]; + candidates: + | null + | { + text: string; + description: string; + }[]; + truncated: boolean; +} + +export interface ComputerAppParams { + app: string; +} + +export interface ComputerStatusResult { + enabled: boolean; + default_deny: boolean; + allowed: null | string[]; + denied: null | string[]; + session_allowed: null | string[]; + session_denied: null | string[]; +} + +export interface ConfigurationUpdate { + default_execution_engine?: null | string; + disabled_providers?: null | string[]; + remote_hosts?: + | null + | { + id: string; + name: string; + url: string; + runtime_id: string; + connect_on_launch: boolean; + }[]; + import_claude?: null | boolean; + import_codex?: null | boolean; + brand_icons?: null | boolean; + revision: string; + default_model?: null | string; + default_provider?: null | string; + default_effort?: null | string; + default_permission_mode?: null | string; + compact_model?: null | string; + compact_provider?: null | string; + compact_percent?: null | number; + goal_max_rounds?: null | number; + max_retries?: null | number; +} + +export interface ContentEventPayload { + part_id?: string; + display?: null | { + target?: string; + command?: string; + query?: string; + child_id?: string; + label?: string; + }; + accounting?: null | { + root_id: string; + agent_id: string; + scope: string; + revision: string; + reported_cost_micros: string; + estimated_cost_micros: string; + reported_cost_calls: string; + estimated_cost_calls: string; + unknown_cost_calls: string; + reported_calls: string; + estimated_calls: string; + pending_calls: string; + }; + usage?: null | { + used: number; + size: number; + usage: { + reported?: boolean; + cost?: null | number; + prompt_tokens: number; + completion_tokens: number; + prompt_tokens_details?: null | { + cached_tokens: number; + }; + completion_tokens_details?: null | { + reasoning_tokens: number; + }; + }; + }; + agent_id?: string; + turn_id?: string; + invocation_id?: string; + operation_id?: string; + host_status?: string; + id?: string; + name?: string; + text?: string; + args?: string; + result?: string; + content: { + reference_id: string; + digest: string; + size: string; + media_type?: string; + source?: string; + }; + truncated: true; +} + +export interface ContentHandle { + reference_id: string; + digest: string; + size: string; + media_type?: string; + source?: string; +} + +export interface ContentReadParams { + root_id: string; + agent_id?: string; + reference_id: string; + offset: string; + limit: number; +} + +export interface ContentReadResult { + data: string | null; + content: { + reference_id: string; + digest: string; + size: string; + media_type?: string; + source?: string; + }; +} + +export interface ContextAuditResult { + working_directory: string; + rows: + | null + | { + label: string; + bytes?: number; + note?: string; + }[]; +} + +export interface CreateSessionParams { + execution_engine?: string; + definition?: string; + kind: string; + cwd: string; + model: string; + provider: string; + permission_mode?: string; +} + +export interface DefinitionList { + items: + | null + | { + id: string; + revision: string; + built_in: boolean; + registered_by: string; + created_at: string; + }[]; +} + +export interface DefinitionParams { + id: string; + revision?: string; +} + +export interface DefinitionRecord { + definition: { + id: string; + instructions: { + persona: string; + rules: string; + project_files: null | string[]; + skill_discovery: boolean; + standing_instructions: boolean; + }; + modules: null | string[]; + capabilities: null | string[]; + model: { + model: string; + provider: string; + effort: string; + }; + compaction: { + model: string; + provider: string; + threshold: number; + }; + mcp: { + servers: null | string[]; + }; + tools: + | null + | { + name: string; + description: string; + input_schema: unknown; + output_schema: unknown; + timeout_millis: number; + }[]; + output: unknown; + children: { + [k: string]: { + instructions: null | { + persona: string; + rules: string; + project_files: null | string[]; + skill_discovery: boolean; + standing_instructions: boolean; + }; + modules: null | string[]; + capabilities: null | string[]; + tools: null | string[]; + model: { + model: string; + provider: string; + effort: string; + }; + budgets: { + [k: string]: number; + }; + report: string; + output: unknown; + }; + }; + surface: { + auto_title: boolean; + goal_loop: boolean; + }; + hooks: null | { + before_tool: null | { + operations: null | string[]; + optional: boolean; + timeout_millis: number; + }; + before_spawn: null | { + operations: null | string[]; + optional: boolean; + timeout_millis: number; + }; + turn_start: null | { + operations: null | string[]; + optional: boolean; + timeout_millis: number; + }; + }; + }; + revision: string; + built_in: boolean; + registered_by: string; + created_at: string; +} + +export interface DefinitionRegisterParams { + definition: { + id: string; + instructions: { + persona: string; + rules: string; + project_files: null | string[]; + skill_discovery: boolean; + standing_instructions: boolean; + }; + modules: null | string[]; + capabilities: null | string[]; + model: { + model: string; + provider: string; + effort: string; + }; + compaction: { + model: string; + provider: string; + threshold: number; + }; + mcp: { + servers: null | string[]; + }; + tools: + | null + | { + name: string; + description: string; + input_schema: unknown; + output_schema: unknown; + timeout_millis: number; + }[]; + output: unknown; + children: { + [k: string]: { + instructions: null | { + persona: string; + rules: string; + project_files: null | string[]; + skill_discovery: boolean; + standing_instructions: boolean; + }; + modules: null | string[]; + capabilities: null | string[]; + tools: null | string[]; + model: { + model: string; + provider: string; + effort: string; + }; + budgets: { + [k: string]: number; + }; + report: string; + output: unknown; + }; + }; + surface: { + auto_title: boolean; + goal_loop: boolean; + }; + hooks: null | { + before_tool: null | { + operations: null | string[]; + optional: boolean; + timeout_millis: number; + }; + before_spawn: null | { + operations: null | string[]; + optional: boolean; + timeout_millis: number; + }; + turn_start: null | { + operations: null | string[]; + optional: boolean; + timeout_millis: number; + }; + }; + }; +} + +export interface DefinitionRegisterResult { + id: string; + revision: string; + created: boolean; +} + +export interface EffortParams { + effort: string; + persist_default: boolean; +} + +export interface EffortResult { + effort: string; +} + +export interface Empty {} + +export interface EmptyParams {} + +export interface EventNotification { + event: { + subscription_id?: string; + root_id: string; + seq: string; + kind: string; + payload?: unknown; + }; +} + +export interface ExecutorBindParams { + definition: string; + revision: string; + tools: null | string[]; + hooks?: null | string[]; +} + +export interface ExecutorBindResult { + generation: string; + tools: null | string[]; + hooks?: null | string[]; +} + +export interface ExecutorPendingParams { + definition: string; + revision: string; + generation: string; +} + +export interface ExecutorPendingResult { + invocations: + | null + | { + invocation_id: string; + definition: string; + revision: string; + generation: string; + root_id: string; + agent_id: string; + turn_id: string; + tool: string; + input: unknown; + deadline_millis: string; + }[]; + hooks?: + | null + | { + invocation_id: string; + definition: string; + revision: string; + generation: string; + root_id: string; + agent_id: string; + turn_id: string; + hook: string; + operation?: string; + arguments?: unknown; + spawn?: null | { + request: { + prompt: string; + name: string; + definition: string; + capabilities: null | string[]; + tools: null | string[]; + budgets: { + [k: string]: number; + }; + report: string; + model: string; + provider: string; + effort: string; + mcp_tools?: unknown; + browser_attachments?: null | string[]; + }; + resolved: { + browser_attachments?: null | string[]; + definition: string; + modules: null | string[]; + capabilities: null | string[]; + tools: null | string[]; + budgets: { + [k: string]: number; + }; + report: string; + }; + }; + input?: string; + permission_mode: string; + deadline_millis: string; + }[]; +} + +export interface ForkParams { + expected_revision: string | null; + title?: string; + cut?: number; +} + +export interface GatewayStatus { + state: string; + endpoint?: string; + error?: string; +} + +export interface GoalContextParams { + window?: number; +} + +export interface GoalResult { + goal: string; +} + +export interface HistoryPageParams { + root_id: string; + agent_id: string; + after_seq?: number; + before_seq?: number; + through_seq: number; + revision?: string | null; + limit: number; + max_bytes: number; + recent?: boolean; +} + +export interface HookInvokeParams { + invocation_id: string; + definition: string; + revision: string; + generation: string; + root_id: string; + agent_id: string; + turn_id: string; + hook: string; + operation?: string; + arguments?: unknown; + spawn?: null | { + request: { + prompt: string; + name: string; + definition: string; + capabilities: null | string[]; + tools: null | string[]; + budgets: { + [k: string]: number; + }; + report: string; + model: string; + provider: string; + effort: string; + mcp_tools?: unknown; + browser_attachments?: null | string[]; + }; + resolved: { + browser_attachments?: null | string[]; + definition: string; + modules: null | string[]; + capabilities: null | string[]; + tools: null | string[]; + budgets: { + [k: string]: number; + }; + report: string; + }; + }; + input?: string; + permission_mode: string; + deadline_millis: string; +} + +export interface HookResultParams { + invocation_id: string; + generation: string; + decision?: string; + reason?: string; + arguments?: unknown; + spawn?: null | { + prompt: string; + name: string; + definition: string; + capabilities: null | string[]; + tools: null | string[]; + budgets: { + [k: string]: number; + }; + report: string; + model: string; + provider: string; + effort: string; + mcp_tools?: unknown; + browser_attachments?: null | string[]; + }; + context?: string; + error?: string; +} + +export interface HostAttentionParams { + after_id?: string; + limit: number; + max_bytes: number; +} + +export interface HostAttentionResult { + items: + | null + | { + root_id: string; + title: string; + active_agents: string; + pending_permissions: string; + questions: + | null + | { + turn_id?: string; + root_id?: string; + agent_id?: string; + sender_agent_id?: string; + inbox_seq?: string; + inbox_kind?: string; + delivery?: string; + message_id?: string; + phase?: string; + status?: string; + terminal_cause?: string; + command_client_id?: string; + command_id?: string; + operation_id?: string; + trace_id?: string; + schedule_id?: number; + slot?: string; + error?: string; + model_call?: null | { + id: string; + logical_id: string; + number: number; + purpose: string; + usage_source: string; + cost_source: string; + tokens: string; + cost_micros: string; + elapsed_millis: string; + exhausted: boolean; + }; + acknowledged_inbox?: string[]; + subscription_id?: string; + key?: string; + version?: string; + expected_version?: string; + restored?: null | string[]; + not_restored?: + | null + | { + name: string; + reason: string; + }[]; + attempt?: string; + budget_kind?: string; + amount?: string; + limit?: string; + used?: string; + reserved?: string; + capability_id?: string; + generation?: string; + permission_id?: string; + operation?: string; + canonical_path?: string; + request_digest?: string; + command?: string; + rule?: string; + rule_source?: string; + question_id?: string; + question?: string; + options?: + | null + | { + label: string; + description?: string; + recommended?: boolean; + }[]; + multiple?: boolean; + questions?: + | null + | { + question: string; + options?: + | null + | { + label: string; + description?: string; + recommended?: boolean; + }[]; + multiple?: boolean; + }[]; + answer?: null | string[]; + dismissed?: boolean; + answers?: + | null + | { + answer?: null | string[]; + dismissed?: boolean; + }[]; + }[]; + truncated: boolean; + }[]; + next_after_id?: string; + has_more: boolean; + truncated: boolean; +} + +export interface HostDirectoryParams { + path?: string; + after?: string; + prefix?: string; + show_hidden?: boolean; + limit: number; +} + +export interface HostDirectoryPickParams { + start?: string; +} + +export interface HostDirectoryPickResult { + path?: string; + cancelled: boolean; +} + +export interface HostDirectoryResult { + path: string; + parent: string; + entries: + | null + | { + name: string; + path: string; + }[]; + next_after?: string; + has_more: boolean; + truncated: boolean; +} + +export type HostSkillCompletionParams = { + scope?: string; + cwd?: string; + definition?: string; + permission_mode?: string; + prefix: string; + limit: number; +} & ( + | { + cwd: string; + scope?: ""; + [k: string]: unknown; + } + | { + cwd?: ""; + scope: "global"; + [k: string]: unknown; + } +); + +export interface HostThemeResolveParams { + name?: string; + json?: string; +} + +export interface IDParams { + id: string; +} + +export interface InboxControlResult { + agent_id: string; + inbox_seq: string; + status: string; +} + +export interface InboxRemoveParams { + id: string; + inbox_seq: string; +} + +export interface InboxSteerParams { + id: string; + inbox_seq: string; + turn_id: string; +} + +export interface InitializeParams { + protocol_major: number; + build_id: string; + client_kind: string; + client_id: string; + capabilities?: null | string[]; +} + +export interface InitializeResult { + execution_engines: + | null + | { + bridge_sha256?: string; + build?: string; + abi?: string; + profile?: string; + fidelity?: string; + guide_sha256?: string; + features?: null | string[]; + default_limits?: { + [k: string]: number; + }; + id: string; + language: string; + label: string; + }[]; + default_execution_engine: string; + operations: + | null + | { + name: string; + surface: string; + execution: string; + permission: string; + sensitive?: boolean; + }[]; + limits: { + frame_bytes: number; + connections: number; + in_flight_requests: number; + outbound_messages: number; + outbound_bytes: string; + root_subscriptions: number; + content_chunk_bytes: number; + upload_bytes: string; + }; + negotiated_capabilities: null | string[]; + protocol_minor: number; + runtime_id: string; + connection_id: string; + host_platform: string; + host_architecture: string; + network_endpoint?: string; + protocol_major: number; + build_id: string; + generation: string; + pid?: number; + started_at?: string; + capabilities: null | string[]; +} + +export type LSPListResult = + | null + | { + name: string; + root?: string; + state: string; + error?: string; + }[]; + +export interface LifecycleEvent { + turn_id?: string; + root_id?: string; + agent_id?: string; + sender_agent_id?: string; + inbox_seq?: string; + inbox_kind?: string; + delivery?: string; + message_id?: string; + phase?: string; + status?: string; + terminal_cause?: string; + command_client_id?: string; + command_id?: string; + operation_id?: string; + trace_id?: string; + schedule_id?: number; + slot?: string; + error?: string; + model_call?: null | { + id: string; + logical_id: string; + number: number; + purpose: string; + usage_source: string; + cost_source: string; + tokens: string; + cost_micros: string; + elapsed_millis: string; + exhausted: boolean; + }; + acknowledged_inbox?: string[]; + subscription_id?: string; + key?: string; + version?: string; + expected_version?: string; + restored?: null | string[]; + not_restored?: + | null + | { + name: string; + reason: string; + }[]; + attempt?: string; + budget_kind?: string; + amount?: string; + limit?: string; + used?: string; + reserved?: string; + capability_id?: string; + generation?: string; + permission_id?: string; + operation?: string; + canonical_path?: string; + request_digest?: string; + command?: string; + rule?: string; + rule_source?: string; + question_id?: string; + question?: string; + options?: + | null + | { + label: string; + description?: string; + recommended?: boolean; + }[]; + multiple?: boolean; + questions?: + | null + | { + question: string; + options?: + | null + | { + label: string; + description?: string; + recommended?: boolean; + }[]; + multiple?: boolean; + }[]; + answer?: null | string[]; + dismissed?: boolean; + answers?: + | null + | { + answer?: null | string[]; + dismissed?: boolean; + }[]; +} + +export interface ListParams { + limit?: number; +} + +export interface MCPAttachParams { + servers: { + [k: string]: { + command?: null | string[]; + env?: { + [k: string]: string; + }; + cwd?: string; + url?: string; + headers?: { + [k: string]: string; + }; + enabled?: null | boolean; + note?: string; + startup_timeout?: number; + tool_timeout?: number; + source?: string; + origin?: string; + }; + }; +} + +export interface MCPBrandIconsParams { + keys: null | string[]; +} + +export interface MCPBrandIconsResult { + icons: { + [k: string]: string; + }; +} + +export interface MCPImportApplyParams { + cwd?: string; + names: null | string[]; +} + +export interface MCPImportApplyResult { + imported: null | string[]; + skipped?: { + [k: string]: string; + }; +} + +export interface MCPImportCandidatesParams { + cwd?: string; +} + +export interface MCPImportCandidatesResult { + candidates: + | null + | { + name: string; + source: string; + state: string; + note?: string; + brand_hint?: string; + brand_key?: string; + }[]; + offered: boolean; + config_path: string; + errors?: { + [k: string]: string; + }; +} + +export interface MCPImportParams { + source: string; + enabled: boolean; +} + +export interface MCPImportStatusResult { + claude: boolean; + codex: boolean; + project: boolean; + opencode: boolean; +} + +export type MCPListResult = + | null + | { + name: string; + status: string; + note?: string; + error?: string; + tools?: number; + source?: string; + }[]; + +export interface MCPRefreshResult { + added: null | string[]; + existing: null | string[]; + changed: null | string[]; + servers: + | null + | { + name: string; + status: string; + note?: string; + error?: string; + tools?: number; + source?: string; + }[]; + blocked: + | null + | { + name: string; + status: string; + note?: string; + error?: string; + tools?: number; + source?: string; + }[]; + source_errors: + | null + | { + name: string; + status: string; + note?: string; + error?: string; + tools?: number; + source?: string; + }[]; +} + +export interface MCPServerParams { + name: string; +} + +export interface MailboxInspection { + id: string; + revision: string; + sender: string; + recipient: string; + kind: string; + delivery: string; + subject: string; + excerpt: string; + body: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + evidence_handle?: string; + status: string; + available_at: string; + created_at: string; + delivered_at: string; + delivered_turn_id?: string; + done_at: string; +} + +export interface MailboxPage { + revision: string; + items: + | null + | { + id: string; + revision: string; + sender: string; + recipient: string; + kind: string; + delivery: string; + subject: string; + excerpt: string; + body: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + evidence_handle?: string; + status: string; + available_at: string; + created_at: string; + delivered_at: string; + delivered_turn_id?: string; + done_at: string; + }[]; + next_cursor?: null | { + root_id: string; + agent_id: string; + status: string; + revision: string; + offset: string; + }; + has_more: boolean; +} + +export interface MailboxPageParams { + root_id: string; + agent_id: string; + status?: string; + cursor?: null | { + root_id: string; + agent_id: string; + status: string; + revision: string; + offset: string; + }; + limit: number; + max_bytes: number; +} + +export interface MailboxReadParams { + root_id: string; + agent_id: string; + id: string; +} + +export interface ModelParams { + effort?: string; + model: string; + provider?: string; + persist_default: boolean; +} + +export interface ModelResult { + reload_pending?: boolean; + model: string; + provider: string; +} + +export interface PathParams { + path: string; +} + +export interface PathResult { + path: string; +} + +export interface PermissionConfigureParams { + external_permissions: boolean; +} + +export interface PermissionDecision { + command_id: string; + root_id: string; + permission_id: string; + allow: boolean; + reason?: string; + remember?: string; +} + +export interface PermissionDecisionParams { + decision: { + command_id: string; + root_id: string; + permission_id: string; + allow: boolean; + reason?: string; + remember?: string; + }; +} + +export interface PermissionDecisionResult { + operation_id: string; + lease_id: string; +} + +export interface PermissionRulesResult { + rules: + | null + | { + id: string; + root_id: string; + operation: string; + rule: string; + principal_id: string; + created_at: string; + }[]; + global: null | string[]; +} + +export interface PingResult { + generation: string; + build_id: string; +} + +export interface ProviderCatalogParams { + refresh?: boolean; + provider?: string; +} + +export interface ProviderCatalogsResult { + models: { + [k: string]: { + name?: string; + id?: string; + providers: null | string[]; + context?: number; + vision?: boolean; + }; + }; + providers: { + [k: string]: { + base_url: string; + available?: null | boolean; + }; + }; + catalogs: { + [k: string]: { + fetched_at: string; + base_url: string; + models: + | null + | { + pricing?: null | { + prompt: string; + completion: string; + input_cache_read?: string; + }; + pricing_known?: boolean; + cache_read_price_known?: boolean; + id: string; + context_length?: number; + max_completion_tokens?: number; + reasoning_efforts?: null | string[]; + in_price?: number; + out_price?: number; + cache_read_price?: number; + input_modalities?: null | string[]; + }[]; + }; + }; + errors?: { + [k: string]: string; + }; +} + +export interface ProviderConfiguration { + revision: string; + provider: string; + definition: { + name: string; + base_url: string; + api: string; + }; + custom: boolean; + credential: { + mode: string; + configured: boolean; + environment_variable?: string; + credential_path?: string; + available?: null | boolean; + }; + models: + | null + | { + alias: string; + id: string; + context?: number; + max_output?: number; + }[]; + removal_blockers: null | string[]; + discovery?: null | { + status: string; + message?: string; + model_count?: number; + }; +} + +export interface ProviderCreateParams { + revision: string; + provider: string; + definition: { + name: string; + base_url: string; + api: string; + }; + credential: { + mode: string; + key?: string; + environment_variable?: string; + }; + manual_model?: null | { + alias: string; + id: string; + context?: number; + max_output?: number; + }; + allow_unverified?: boolean; +} + +export interface ProviderDisconnectParams { + provider: string; + revision: string; +} + +export interface ProviderKeySetup { + revision: string; + provider: string; + key?: string; + environment: boolean; +} + +export interface ProviderList { + revision: string; + discovery_error?: string; + default_provider?: string; + providers: + | null + | { + id: string; + name: string; + custom: boolean; + methods: null | string[]; + status: { + provider: string; + configured: boolean; + key_source: string; + available?: null | boolean; + disabled?: boolean; + environment_variable?: string; + credential_path?: string; + auth_method?: string; + auth_state?: string; + account_id?: string; + plan?: string; + email?: string; + team_name?: string; + project_id?: string; + project_name?: string; + machine_key_name?: string; + warnings: null | string[]; + }; + recommended?: boolean; + suggested_model?: string; + category?: string; + family?: string; + key_url?: string; + }[]; + selection?: null | { + ready: boolean; + model: string; + provider: string; + reason: string; + }; +} + +export interface ProviderListParams { + model?: string; + provider?: string; +} + +export interface ProviderLoginBeginParams { + provider?: string; +} + +export interface ProviderLoginCreateParams { + flow_id: string; + name: string; +} + +export interface ProviderLoginList { + flows: + | null + | { + flow_id: string; + provider?: string; + state: string; + verification_url?: string; + user_code?: string; + email?: string; + teams: + | null + | { + id: string; + name: string; + }[]; + projects: + | null + | { + id: string; + name: string; + }[]; + team_id?: string; + project_id?: string; + expires_at: string; + error?: string; + }[]; +} + +export interface ProviderLoginParams { + flow_id: string; +} + +export interface ProviderLoginProjectParams { + flow_id: string; + project_id: string; +} + +export interface ProviderLoginStatus { + flow_id: string; + provider?: string; + state: string; + verification_url?: string; + user_code?: string; + email?: string; + teams: + | null + | { + id: string; + name: string; + }[]; + projects: + | null + | { + id: string; + name: string; + }[]; + team_id?: string; + project_id?: string; + expires_at: string; + error?: string; +} + +export interface ProviderLoginTeamParams { + flow_id: string; + team_id: string; +} + +export interface ProviderNameParams { + provider: string; +} + +export interface ProviderRemoveParams { + revision: string; + provider: string; +} + +export interface ProviderRemoveResult { + revision: string; + warning?: string; +} + +export interface ProviderStatus { + provider: string; + configured: boolean; + key_source: string; + available?: null | boolean; + disabled?: boolean; + environment_variable?: string; + credential_path?: string; + auth_method?: string; + auth_state?: string; + account_id?: string; + plan?: string; + email?: string; + team_name?: string; + project_id?: string; + project_name?: string; + machine_key_name?: string; + warnings: null | string[]; +} + +export interface ProviderUpdateParams { + revision: string; + provider: string; + name?: null | string; + base_url?: null | string; + credential?: null | { + mode: string; + key?: string; + environment_variable?: string; + }; + manual_model?: null | { + alias: string; + id: string; + context?: number; + max_output?: number; + }; + allow_unverified?: boolean; +} + +export interface ProviderValidateParams { + name: string; + base_url: string; + key: string; +} + +export interface ProviderValidateResult { + models: + | null + | { + id: string; + context_length?: number; + max_completion_tokens?: number; + reasoning_efforts?: null | string[]; + pricing?: null | { + prompt: string; + completion: string; + input_cache_read?: string; + }; + input_modalities?: null | string[]; + output_modalities?: null | string[]; + supports_tools?: null | boolean; + type?: string; + }[]; +} + +export interface QueryParams { + root_id?: string; + operation: string; + payload?: unknown; +} + +export interface QueryResult { + result?: unknown; + content?: null | { + reference_id: string; + digest: string; + size: string; + media_type?: string; + source?: string; + }; + root_id?: string; +} + +export interface QuestionAnswerParams { + id: string; + answer: null | string[]; + dismissed: boolean; + answers?: + | null + | { + answer?: null | string[]; + dismissed?: boolean; + }[]; +} + +export interface RPCError { + data?: null | { + kind: string; + }; + code: number; + message: string; +} + +export interface ReplayParams { + root_id: string; + cursor: string; + limit?: number; +} + +export interface ReplayResult { + events: + | null + | { + subscription_id?: string; + root_id: string; + seq: string; + kind: string; + payload?: unknown; + }[]; + latest: string; + expired?: boolean; +} + +export interface Resolved { + id: string; + name: string; + dark: boolean; + colors: { + background: string; + foreground: string; + muted: string; + faint: string; + primary: string; + on_primary: string; + accent: string; + success: string; + warning: string; + error: string; + info: string; + link: string; + emphasis: string; + border: string; + border_focus: string; + diff_add: string; + diff_del: string; + panel: string; + element: string; + hover: string; + }; + syntax: { + keyword: string; + string: string; + number: string; + comment: string; + function: string; + type: string; + operator: string; + punctuation: string; + }; + markdown: { + heading: string; + strong: string; + code: string; + quote: string; + }; + code: { + foreground: string; + background: string; + tokens: { + [k: string]: { + color: string; + background: string; + bold: boolean; + italic: boolean; + underline: boolean; + }; + }; + }; + web?: null | { + navigation?: string; + quiet_border?: string; + code_background?: string; + inline_code_background?: string; + }; +} + +export interface RestartNotice { + generation: string; + cursors: { + [k: string]: string; + }; +} + +export interface RestartParams { + generation: string; +} + +export interface RewindParams { + expected_revision: string | null; + cut: number; +} + +export interface RewindResult { + cut: number; + restored_files: number; +} + +export interface RootCollectionPage { + root_id: string; + collection: string; + revision: string; + event_cursor: string; + items: + | null + | ({ + agent?: null | { + execution_engine: string; + last_turn?: null | { + turn_id?: string; + status: string; + started_at?: string; + finished_at?: string; + event_seq: string; + error?: string; + error_truncated?: boolean; + error_details?: null | { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + model_calls?: number; + compactions?: number; + last_activity_at?: string; + }; + id: string; + root_id: string; + parent_id: string; + name: string; + model: string; + provider: string; + effort: string; + cwd: string; + report: string; + status: string; + pending_mail: number; + lifecycle_phase: string; + blocking_reason: string; + terminal_cause: string; + allowed_controls: null | string[]; + }; + inbox?: null | { + delivery_seq?: string; + origin?: string; + command_client_id?: string; + command_id?: string; + steer_turn_id?: string; + preview?: null | { + design_context?: null | { + context_attachment_id: string; + screenshot_attachment_id?: string; + elements: + | null + | { + label: string; + selector?: string; + }[]; + element_count: number; + page_url?: string; + page_title?: string; + }; + text: string; + truncated?: boolean; + attachments?: + | null + | { + kind: string; + name?: string; + content: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }[]; + attachment_count?: number; + }; + root_id: string; + agent_id: string; + seq: string; + kind: string; + status: string; + payload: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }; + blackboard?: null | { + key: string; + version: string; + author_agent_id: string; + payload: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }; + budget?: null | { + agent_id: string; + state: { + kind: string; + limit: string | null; + used: string; + reserved: string; + remaining: string | null; + uncertain: string; + incomplete: boolean; + }; + }; + capability?: null | { + id: string; + root_id: string; + agent_id: string; + issuer_agent_id: string; + operations: null | string[]; + scopes: null | string[]; + file_scope?: string; + file_issuer_id?: string; + file_issuer_generation?: string; + mcp: + | null + | { + server: string; + tool: string; + definition: string; + }[]; + mcp_all: boolean; + browser?: null | { + provider_id: string; + provider_epoch: string; + tab_id: string; + tab_generation: string; + profile_id: string; + attachment_id?: string; + attachment_generation?: string; + rights: null | string[]; + preview?: null | { + host_id: string; + host_identity: string; + connection_generation: string; + environment_id: string; + loopback: string; + ports: null | number[]; + }; + }; + browser_issuer_id?: string; + browser_issuer_generation?: number; + browser_delegation_only?: boolean; + generation: string; + status: string; + expires_at: string; + created_at: string; + updated_at: string; + }; + schedule?: null | { + id: number; + schedule: string; + prompt: string; + anchor: string; + last_fire: string; + }; + permission?: null | { + id: string; + agent_id: string; + operation_id: string; + operation: string; + canonical_path: string; + request_digest: string; + capability_id: string; + capability_generation: string; + status: string; + command: string; + rule: string; + }; + body?: null | { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + } & ( + | { + agent: null | { + execution_engine: string; + last_turn?: null | { + turn_id?: string; + status: string; + started_at?: string; + finished_at?: string; + event_seq: string; + error?: string; + error_truncated?: boolean; + error_details?: null | { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + model_calls?: number; + compactions?: number; + last_activity_at?: string; + }; + id: string; + root_id: string; + parent_id: string; + name: string; + model: string; + provider: string; + effort: string; + cwd: string; + report: string; + status: string; + pending_mail: number; + lifecycle_phase: string; + blocking_reason: string; + terminal_cause: string; + allowed_controls: null | string[]; + }; + } + | { + inbox: null | { + delivery_seq?: string; + origin?: string; + command_client_id?: string; + command_id?: string; + steer_turn_id?: string; + preview?: null | { + design_context?: null | { + context_attachment_id: string; + screenshot_attachment_id?: string; + elements: + | null + | { + label: string; + selector?: string; + }[]; + element_count: number; + page_url?: string; + page_title?: string; + }; + text: string; + truncated?: boolean; + attachments?: + | null + | { + kind: string; + name?: string; + content: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }[]; + attachment_count?: number; + }; + root_id: string; + agent_id: string; + seq: string; + kind: string; + status: string; + payload: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }; + } + | { + blackboard: null | { + key: string; + version: string; + author_agent_id: string; + payload: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }; + } + | { + budget: null | { + agent_id: string; + state: { + kind: string; + limit: string | null; + used: string; + reserved: string; + remaining: string | null; + uncertain: string; + incomplete: boolean; + }; + }; + } + | { + capability: null | { + id: string; + root_id: string; + agent_id: string; + issuer_agent_id: string; + operations: null | string[]; + scopes: null | string[]; + file_scope?: string; + file_issuer_id?: string; + file_issuer_generation?: string; + mcp: + | null + | { + server: string; + tool: string; + definition: string; + }[]; + mcp_all: boolean; + browser?: null | { + provider_id: string; + provider_epoch: string; + tab_id: string; + tab_generation: string; + profile_id: string; + attachment_id?: string; + attachment_generation?: string; + rights: null | string[]; + preview?: null | { + host_id: string; + host_identity: string; + connection_generation: string; + environment_id: string; + loopback: string; + ports: null | number[]; + }; + }; + browser_issuer_id?: string; + browser_issuer_generation?: number; + browser_delegation_only?: boolean; + generation: string; + status: string; + expires_at: string; + created_at: string; + updated_at: string; + }; + } + | { + schedule: null | { + id: number; + schedule: string; + prompt: string; + anchor: string; + last_fire: string; + }; + } + | { + permission: null | { + id: string; + agent_id: string; + operation_id: string; + operation: string; + canonical_path: string; + request_digest: string; + capability_id: string; + capability_generation: string; + status: string; + command: string; + rule: string; + }; + } + | { + body: null | { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + } + ))[]; + next_cursor?: null | { + root_id: string; + collection: string; + revision: string; + offset: string; + }; + has_more: boolean; +} + +export interface RootCollectionParams { + root_id: string; + collection: string; + cursor?: null | { + root_id: string; + collection: string; + revision: string; + offset: string; + }; + limit: number; + max_bytes: number; +} + +export interface RootIDResult { + root_id: string; +} + +export interface RootParams { + root_id: string; +} + +export interface RootSnapshot { + collection_revision?: string; + active_turns: { + [k: string]: string; + }; + omitted?: { + [k: string]: boolean; + }; + message_seqs: null | number[]; + first_message_seq?: number; + history_revision: string; + root_id: string; + cursor: string; + meta: { + execution_engine: string; + definition: string; + definition_revision: string; + id: string; + kind: string; + title: string; + model: string; + provider: string; + cwd: string; + goal: string; + forked_from: string; + fork_seq: number; + tags: null | string[]; + archived: boolean; + pinned: boolean; + effort: string; + usage_in: number; + usage_cached: number; + usage_out: number; + updated_at: string; + }; + messages: + | null + | { + presentation?: null | { + design_context?: null | { + context_attachment_id: string; + screenshot_attachment_id?: string; + elements: + | null + | { + label: string; + selector?: string; + }[]; + element_count: number; + page_url?: string; + page_title?: string; + context_part_index: number; + screenshot_part_index?: null | number; + }; + version: number; + turn_id?: string; + parts?: + | null + | { + id: string; + kind: string; + start?: number; + end?: number; + text?: string; + tool_name?: string; + status?: string; + call_id?: string; + hosts?: + | null + | { + invocation_id: string; + name: string; + summary?: string; + status: string; + duration?: string; + error?: string; + display?: null | { + target?: string; + command?: string; + query?: string; + child_id?: string; + label?: string; + }; + }[]; + omitted?: number; + }[]; + omitted?: number; + }; + role: string; + content: + | string + | { + type: string; + text?: string; + image_url?: null | { + url: string; + }; + w?: number; + h?: number; + }[]; + tool_calls?: + | null + | { + id: string; + type: string; + function: { + name: string; + arguments: string; + }; + duration_ms?: number; + exit_code?: number; + }[]; + tool_call_id?: string; + name?: string; + authored?: boolean; + sent_at?: null | string; + usage?: null | { + reported?: boolean; + cost?: null | number; + prompt_tokens: number; + completion_tokens: number; + prompt_tokens_details?: null | { + cached_tokens: number; + }; + completion_tokens_details?: null | { + reasoning_tokens: number; + }; + }; + model?: string; + rewound_from?: string; + call_id?: string; + }[]; + presentation: + | null + | { + seq: string; + kind: string; + payload: unknown; + }[]; + agent_presentations: { + [k: string]: + | null + | { + seq: string; + kind: string; + payload: unknown; + }[]; + }; + agents: + | null + | { + execution_engine: string; + last_turn?: null | { + turn_id?: string; + status: string; + started_at?: string; + finished_at?: string; + event_seq: string; + error?: string; + error_truncated?: boolean; + error_details?: null | { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + model_calls?: number; + compactions?: number; + last_activity_at?: string; + }; + id: string; + root_id: string; + parent_id: string; + name: string; + model: string; + provider: string; + effort: string; + cwd: string; + report: string; + status: string; + pending_mail: number; + lifecycle_phase: string; + blocking_reason: string; + terminal_cause: string; + allowed_controls: null | string[]; + }[]; + inbox: + | null + | { + delivery_seq?: string; + origin?: string; + command_client_id?: string; + command_id?: string; + steer_turn_id?: string; + preview?: null | { + design_context?: null | { + context_attachment_id: string; + screenshot_attachment_id?: string; + elements: + | null + | { + label: string; + selector?: string; + }[]; + element_count: number; + page_url?: string; + page_title?: string; + }; + text: string; + truncated?: boolean; + attachments?: + | null + | { + kind: string; + name?: string; + content: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }[]; + attachment_count?: number; + }; + root_id: string; + agent_id: string; + seq: string; + kind: string; + status: string; + payload: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }[]; + blackboard: + | null + | { + key: string; + version: string; + author_agent_id: string; + payload: { + inline?: unknown; + text?: null | string; + binary?: string | null; + reference_id: string; + digest: string; + size: string; + media_type: string; + source: string; + }; + }[]; + budgets: + | null + | { + agent_id: string; + state: { + kind: string; + limit: string | null; + used: string; + reserved: string; + remaining: string | null; + uncertain: string; + incomplete: boolean; + }; + }[]; + accounting?: { + root_id: string; + agent_id: string; + scope: string; + revision: string; + reported_cost_micros: string; + estimated_cost_micros: string; + reported_cost_calls: string; + estimated_cost_calls: string; + unknown_cost_calls: string; + reported_calls: string; + estimated_calls: string; + pending_calls: string; + }; + capabilities: + | null + | { + id: string; + root_id: string; + agent_id: string; + issuer_agent_id: string; + operations: null | string[]; + scopes: null | string[]; + file_scope?: string; + file_issuer_id?: string; + file_issuer_generation?: string; + mcp: + | null + | { + server: string; + tool: string; + definition: string; + }[]; + mcp_all: boolean; + browser?: null | { + provider_id: string; + provider_epoch: string; + tab_id: string; + tab_generation: string; + profile_id: string; + attachment_id?: string; + attachment_generation?: string; + rights: null | string[]; + preview?: null | { + host_id: string; + host_identity: string; + connection_generation: string; + environment_id: string; + loopback: string; + ports: null | number[]; + }; + }; + browser_issuer_id?: string; + browser_issuer_generation?: number; + browser_delegation_only?: boolean; + generation: string; + status: string; + expires_at: string; + created_at: string; + updated_at: string; + }[]; + schedules: + | null + | { + id: number; + schedule: string; + prompt: string; + anchor: string; + last_fire: string; + }[]; + upcoming_schedules?: + | null + | { + id: number; + next_fire: string; + prompt: string; + prompt_truncated?: boolean; + }[]; + upcoming_schedule_count?: null | number; + permissions: + | null + | { + id: string; + agent_id: string; + operation_id: string; + operation: string; + canonical_path: string; + request_digest: string; + capability_id: string; + capability_generation: string; + status: string; + command: string; + rule: string; + }[]; + questions: + | null + | { + turn_id?: string; + root_id?: string; + agent_id?: string; + sender_agent_id?: string; + inbox_seq?: string; + inbox_kind?: string; + delivery?: string; + message_id?: string; + phase?: string; + status?: string; + terminal_cause?: string; + command_client_id?: string; + command_id?: string; + operation_id?: string; + trace_id?: string; + schedule_id?: number; + slot?: string; + error?: string; + model_call?: null | { + id: string; + logical_id: string; + number: number; + purpose: string; + usage_source: string; + cost_source: string; + tokens: string; + cost_micros: string; + elapsed_millis: string; + exhausted: boolean; + }; + acknowledged_inbox?: string[]; + subscription_id?: string; + key?: string; + version?: string; + expected_version?: string; + restored?: null | string[]; + not_restored?: + | null + | { + name: string; + reason: string; + }[]; + attempt?: string; + budget_kind?: string; + amount?: string; + limit?: string; + used?: string; + reserved?: string; + capability_id?: string; + generation?: string; + permission_id?: string; + operation?: string; + canonical_path?: string; + request_digest?: string; + command?: string; + rule?: string; + rule_source?: string; + question_id?: string; + question?: string; + options?: + | null + | { + label: string; + description?: string; + recommended?: boolean; + }[]; + multiple?: boolean; + questions?: + | null + | { + question: string; + options?: + | null + | { + label: string; + description?: string; + recommended?: boolean; + }[]; + multiple?: boolean; + }[]; + answer?: null | string[]; + dismissed?: boolean; + answers?: + | null + | { + answer?: null | string[]; + dismissed?: boolean; + }[]; + }[]; + permission_mode?: string; +} + +export interface RunConfigureParams { + system?: string; + max_turns?: number; + headless?: boolean; + cache_key?: string; +} + +export interface RuntimeConfiguration { + default_execution_engine: string; + disabled_providers?: null | string[]; + discovery?: null | { + status: string; + message?: string; + model_count?: number; + }; + remote_hosts?: + | null + | { + id: string; + name: string; + url: string; + runtime_id: string; + connect_on_launch: boolean; + }[]; + import_claude: boolean; + import_codex: boolean; + mcp_import_offered: boolean; + brand_icons: boolean; + revision: string; + default_model: string; + default_provider: string; + default_effort: string; + default_permission_mode?: string; + compact_model: string; + compact_provider: string; + compact_percent: number; + goal_max_rounds: number; + max_retries: number; +} + +export interface ScheduleCreateParams { + schedule: string; + prompt: string; +} + +export interface ScheduleDeleteParams { + schedule_id: number; +} + +export type ScheduleListResult = + | null + | { + id: number; + schedule: string; + prompt: string; + anchor: string; + last_fire: string; + }[]; + +export interface ScheduleResult { + schedule_id: number; +} + +export interface SessionCatalogPage { + revision: string; + items: + | null + | { + id: string; + kind: string; + title: string; + model: string; + provider: string; + cwd: string; + workspace_id?: string; + pinned: boolean; + archived: boolean; + updated_at: string; + truncated: boolean; + }[]; + next_cursor?: null | { + revision: string; + offset: string; + search?: string; + status: "active" | "archived" | "all"; + }; + has_more: boolean; +} + +export interface SessionCatalogParams { + search?: string; + status?: "active" | "archived" | "all"; + cursor?: null | { + revision: string; + offset: string; + search?: string; + status: "active" | "archived" | "all"; + }; + limit: number; + max_bytes: number; +} + +export type SessionListResult = + | null + | { + execution_engine: string; + definition: string; + definition_revision: string; + id: string; + kind: string; + title: string; + model: string; + provider: string; + cwd: string; + goal: string; + forked_from: string; + fork_seq: number; + tags: null | string[]; + archived: boolean; + pinned: boolean; + effort: string; + usage_in: number; + usage_cached: number; + usage_out: number; + updated_at: string; + }[]; + +export interface SessionMetadata { + root_id: string; + title: string; + cwd: string; + history_revision: string; + archived: boolean; +} + +export interface SessionPreviewResult { + root_id: string; + user: string; + assistant: string; +} + +export interface SessionSummariesParams { + /** + * @maxItems 32 + */ + root_ids: string[]; +} + +export interface SessionSummariesResult { + /** + * @maxItems 32 + */ + items: { + root_id: string; + missing: boolean; + archived: boolean; + title: string; + cwd: string; + workspace_id?: string; + running_agents: string; + queued_agents: string; + pending_permissions: string; + pending_questions: string; + truncated: boolean; + }[]; +} + +export interface SessionUpdateEvent { + archived?: null | boolean; + title?: string; + model?: string; + provider?: string; + effort?: string; + effort_changed?: boolean; + working_directory?: string; + permission_mode?: null | string; +} + +export interface ShellParams { + command: string; +} + +export interface SnapshotParams { + root_id: string; +} + +export interface SpanPage { + root_id: string; + spans: + | null + | { + id: string; + trace_id: string; + parent_id?: string; + root_id: string; + agent_id: string; + turn_id?: string; + kind: string; + name: string; + status: string; + start_ns: string; + end_ns: string; + attrs?: unknown; + links?: unknown; + updated_seq: string; + }[]; + next_seq: string; + has_more: boolean; + server_time_ns: string; +} + +export interface SpanRecord { + id: string; + trace_id: string; + parent_id?: string; + root_id: string; + agent_id: string; + turn_id?: string; + kind: string; + name: string; + status: string; + start_ns: string; + end_ns: string; + attrs?: unknown; + links?: unknown; + updated_seq: string; +} + +export interface StreamEvent { + part_id?: string; + display?: null | { + target?: string; + command?: string; + query?: string; + child_id?: string; + label?: string; + }; + accounting?: null | { + root_id: string; + agent_id: string; + scope: string; + revision: string; + reported_cost_micros: string; + estimated_cost_micros: string; + reported_cost_calls: string; + estimated_cost_calls: string; + unknown_cost_calls: string; + reported_calls: string; + estimated_calls: string; + pending_calls: string; + }; + usage?: null | { + used: number; + size: number; + usage: { + reported?: boolean; + cost?: null | number; + prompt_tokens: number; + completion_tokens: number; + prompt_tokens_details?: null | { + cached_tokens: number; + }; + completion_tokens_details?: null | { + reasoning_tokens: number; + }; + }; + }; + agent_id?: string; + turn_id?: string; + invocation_id?: string; + operation_id?: string; + host_status?: string; + id?: string; + name?: string; + text?: string; + args?: string; + result?: string; +} + +export interface SubmitPayload { + design_context?: null | { + context_attachment_id: string; + screenshot_attachment_id?: string; + elements: + | null + | { + label: string; + selector?: string; + }[]; + element_count: number; + page_url?: string; + page_title?: string; + }; + text: string; + parts?: + | null + | { + type: string; + text?: string; + image_url?: null | { + url: string; + }; + w?: number; + h?: number; + }[]; + attachments?: + | null + | { + kind: "image" | "text"; + content: { + reference_id: string; + digest: string; + size: string; + media_type?: string; + source?: string; + }; + name?: string; + }[]; +} + +export interface SubscribeParams { + root_id: string; + subscription_id: string; + cursor: string; +} + +export interface SubscribeResult { + subscription_id: string; + cursor: string; +} + +export interface SubscriptionFailure { + subscription_id: string; + root_id: string; + error: null | { + data?: null | { + kind: string; + }; + code: number; + message: string; + }; +} + +export interface TerminalAttachParams { + id: string; + cursor: string; +} + +export interface TerminalAttachResult { + cursor: string; + cwd: string; + cols: number; + rows: number; + exited: boolean; + exit_code?: number; + signal?: string; +} + +export interface TerminalDetachedParams { + id: string; +} + +export interface TerminalExitedParams { + id: string; + exit_code: number; + signal?: string; +} + +export interface TerminalIDParams { + id: string; +} + +export interface TerminalInputParams { + id: string; + bytes: string | null; +} + +export interface TerminalOpenParams { + cwd?: string; + root_id?: string; + cols: number; + rows: number; +} + +export interface TerminalOpenResult { + id: string; + shell: string; + cwd: string; +} + +export interface TerminalOutputParams { + id: string; + cursor: string; + bytes: string | null; +} + +export interface TerminalResizeParams { + id: string; + cols: number; + rows: number; +} + +export interface TerminalWriteParams { + id: string; + bytes: string | null; +} + +export interface TextParams { + text: string; +} + +export interface TextResult { + text: string; + output?: unknown; +} + +export interface TitleParams { + title: string; +} + +export interface TitleResult { + title: string; +} + +export interface ToolCallParams { + tool: string; + arguments: unknown; +} + +export interface ToolCancelParams { + invocation_id: string; + generation: string; + reason: string; +} + +export interface ToolConfigureParams { + deny_permissions: boolean; +} + +export interface ToolInvokeParams { + invocation_id: string; + definition: string; + revision: string; + generation: string; + root_id: string; + agent_id: string; + turn_id: string; + tool: string; + input: unknown; + deadline_millis: string; +} + +export interface ToolProgressParams { + invocation_id: string; + generation: string; + text: string; +} + +export interface ToolResultParams { + invocation_id: string; + generation: string; + output?: unknown; + error?: string; +} + +export type ToolSchemaResult = + | null + | { + type: string; + function: { + name: string; + description: string; + parameters: unknown; + }; + }[]; + +export interface TraceExportParams { + root_id: string; + trace_id?: string; +} + +export interface TraceExportResult { + content: { + reference_id: string; + digest: string; + size: string; + media_type?: string; + source?: string; + }; + spans: number; + traces: number; +} + +export interface TracePageParams { + root_id: string; + trace_id?: string; + after_seq?: string; + limit?: number; + roots_only?: boolean; +} + +export interface UnsubscribeParams { + subscription_id: string; +} + +export interface UploadBeginParams { + upload_id: string; + root_id: string; + agent_id?: string; + expected_digest: string; + size: string; + media_type?: string; + source?: string; +} + +export interface UploadChunkParams { + upload_id: string; + offset: string; + data: string | null; +} + +export interface UploadFinishParams { + upload_id: string; +} + +export type UserHistoryResult = null | string[]; + +export interface ContractTypes { + Accepted: Accepted; + AgentCancelParams: AgentCancelParams; + AgentInputParams: AgentInputParams; + AgentListResult: AgentListResult; + AgentSubmitResult: AgentSubmitResult; + AgentTranscriptResult: AgentTranscriptResult; + ArchiveParams: ArchiveParams; + ArchiveResult: ArchiveResult; + BoundedTranscriptPage: BoundedTranscriptPage; + BrowserCommand: BrowserCommand; + BrowserCommandCancel: BrowserCommandCancel; + BrowserCommandResultParams: BrowserCommandResultParams; + BrowserDriverParams: BrowserDriverParams; + BrowserInventoryRequest: BrowserInventoryRequest; + BrowserInventoryResultParams: BrowserInventoryResultParams; + BrowserProviderBindParams: BrowserProviderBindParams; + BrowserProviderBindResult: BrowserProviderBindResult; + BrowserProviderEventParams: BrowserProviderEventParams; + BrowserProviderRevoked: BrowserProviderRevoked; + BrowserProviderUnbindParams: BrowserProviderUnbindParams; + BrowserStatusResult: BrowserStatusResult; + BudgetCapParams: BudgetCapParams; + BudgetState: BudgetState; + CancelParams: CancelParams; + CapabilityRecord: CapabilityRecord; + CatalogResult: CatalogResult; + CatalogRevision: CatalogRevision; + CheckpointParams: CheckpointParams; + ClearHistoryParams: ClearHistoryParams; + CommandParams: CommandParams; + CommandResult: CommandResult; + CommandStatusParams: CommandStatusParams; + CompactionListResult: CompactionListResult; + CompactionParams: CompactionParams; + CompactionResult: CompactionResult; + CompactionRetryResult: CompactionRetryResult; + CompactionSettingsResult: CompactionSettingsResult; + CompletionParams: CompletionParams; + CompletionResult: CompletionResult; + ComputerAppParams: ComputerAppParams; + ComputerStatusResult: ComputerStatusResult; + ConfigurationUpdate: ConfigurationUpdate; + ContentEventPayload: ContentEventPayload; + ContentHandle: ContentHandle; + ContentReadParams: ContentReadParams; + ContentReadResult: ContentReadResult; + ContextAuditResult: ContextAuditResult; + CreateSessionParams: CreateSessionParams; + DefinitionList: DefinitionList; + DefinitionParams: DefinitionParams; + DefinitionRecord: DefinitionRecord; + DefinitionRegisterParams: DefinitionRegisterParams; + DefinitionRegisterResult: DefinitionRegisterResult; + EffortParams: EffortParams; + EffortResult: EffortResult; + Empty: Empty; + EmptyParams: EmptyParams; + EventNotification: EventNotification; + ExecutorBindParams: ExecutorBindParams; + ExecutorBindResult: ExecutorBindResult; + ExecutorPendingParams: ExecutorPendingParams; + ExecutorPendingResult: ExecutorPendingResult; + ForkParams: ForkParams; + GatewayStatus: GatewayStatus; + GoalContextParams: GoalContextParams; + GoalResult: GoalResult; + HistoryPageParams: HistoryPageParams; + HookInvokeParams: HookInvokeParams; + HookResultParams: HookResultParams; + HostAttentionParams: HostAttentionParams; + HostAttentionResult: HostAttentionResult; + HostDirectoryParams: HostDirectoryParams; + HostDirectoryPickParams: HostDirectoryPickParams; + HostDirectoryPickResult: HostDirectoryPickResult; + HostDirectoryResult: HostDirectoryResult; + HostSkillCompletionParams: HostSkillCompletionParams; + HostThemeResolveParams: HostThemeResolveParams; + IDParams: IDParams; + InboxControlResult: InboxControlResult; + InboxRemoveParams: InboxRemoveParams; + InboxSteerParams: InboxSteerParams; + InitializeParams: InitializeParams; + InitializeResult: InitializeResult; + LSPListResult: LSPListResult; + LifecycleEvent: LifecycleEvent; + ListParams: ListParams; + MCPAttachParams: MCPAttachParams; + MCPBrandIconsParams: MCPBrandIconsParams; + MCPBrandIconsResult: MCPBrandIconsResult; + MCPImportApplyParams: MCPImportApplyParams; + MCPImportApplyResult: MCPImportApplyResult; + MCPImportCandidatesParams: MCPImportCandidatesParams; + MCPImportCandidatesResult: MCPImportCandidatesResult; + MCPImportParams: MCPImportParams; + MCPImportStatusResult: MCPImportStatusResult; + MCPListResult: MCPListResult; + MCPRefreshResult: MCPRefreshResult; + MCPServerParams: MCPServerParams; + MailboxInspection: MailboxInspection; + MailboxPage: MailboxPage; + MailboxPageParams: MailboxPageParams; + MailboxReadParams: MailboxReadParams; + ModelParams: ModelParams; + ModelResult: ModelResult; + PathParams: PathParams; + PathResult: PathResult; + PermissionConfigureParams: PermissionConfigureParams; + PermissionDecision: PermissionDecision; + PermissionDecisionParams: PermissionDecisionParams; + PermissionDecisionResult: PermissionDecisionResult; + PermissionRulesResult: PermissionRulesResult; + PingResult: PingResult; + ProviderCatalogParams: ProviderCatalogParams; + ProviderCatalogsResult: ProviderCatalogsResult; + ProviderConfiguration: ProviderConfiguration; + ProviderCreateParams: ProviderCreateParams; + ProviderDisconnectParams: ProviderDisconnectParams; + ProviderKeySetup: ProviderKeySetup; + ProviderList: ProviderList; + ProviderListParams: ProviderListParams; + ProviderLoginBeginParams: ProviderLoginBeginParams; + ProviderLoginCreateParams: ProviderLoginCreateParams; + ProviderLoginList: ProviderLoginList; + ProviderLoginParams: ProviderLoginParams; + ProviderLoginProjectParams: ProviderLoginProjectParams; + ProviderLoginStatus: ProviderLoginStatus; + ProviderLoginTeamParams: ProviderLoginTeamParams; + ProviderNameParams: ProviderNameParams; + ProviderRemoveParams: ProviderRemoveParams; + ProviderRemoveResult: ProviderRemoveResult; + ProviderStatus: ProviderStatus; + ProviderUpdateParams: ProviderUpdateParams; + ProviderValidateParams: ProviderValidateParams; + ProviderValidateResult: ProviderValidateResult; + QueryParams: QueryParams; + QueryResult: QueryResult; + QuestionAnswerParams: QuestionAnswerParams; + RPCError: RPCError; + ReplayParams: ReplayParams; + ReplayResult: ReplayResult; + Resolved: Resolved; + RestartNotice: RestartNotice; + RestartParams: RestartParams; + RewindParams: RewindParams; + RewindResult: RewindResult; + RootCollectionPage: RootCollectionPage; + RootCollectionParams: RootCollectionParams; + RootIDResult: RootIDResult; + RootParams: RootParams; + RootSnapshot: RootSnapshot; + RunConfigureParams: RunConfigureParams; + RuntimeConfiguration: RuntimeConfiguration; + ScheduleCreateParams: ScheduleCreateParams; + ScheduleDeleteParams: ScheduleDeleteParams; + ScheduleListResult: ScheduleListResult; + ScheduleResult: ScheduleResult; + SessionCatalogPage: SessionCatalogPage; + SessionCatalogParams: SessionCatalogParams; + SessionListResult: SessionListResult; + SessionMetadata: SessionMetadata; + SessionPreviewResult: SessionPreviewResult; + SessionSummariesParams: SessionSummariesParams; + SessionSummariesResult: SessionSummariesResult; + SessionUpdateEvent: SessionUpdateEvent; + ShellParams: ShellParams; + SnapshotParams: SnapshotParams; + SpanPage: SpanPage; + SpanRecord: SpanRecord; + StreamEvent: StreamEvent; + SubmitPayload: SubmitPayload; + SubscribeParams: SubscribeParams; + SubscribeResult: SubscribeResult; + SubscriptionFailure: SubscriptionFailure; + TerminalAttachParams: TerminalAttachParams; + TerminalAttachResult: TerminalAttachResult; + TerminalDetachedParams: TerminalDetachedParams; + TerminalExitedParams: TerminalExitedParams; + TerminalIDParams: TerminalIDParams; + TerminalInputParams: TerminalInputParams; + TerminalOpenParams: TerminalOpenParams; + TerminalOpenResult: TerminalOpenResult; + TerminalOutputParams: TerminalOutputParams; + TerminalResizeParams: TerminalResizeParams; + TerminalWriteParams: TerminalWriteParams; + TextParams: TextParams; + TextResult: TextResult; + TitleParams: TitleParams; + TitleResult: TitleResult; + ToolCallParams: ToolCallParams; + ToolCancelParams: ToolCancelParams; + ToolConfigureParams: ToolConfigureParams; + ToolInvokeParams: ToolInvokeParams; + ToolProgressParams: ToolProgressParams; + ToolResultParams: ToolResultParams; + ToolSchemaResult: ToolSchemaResult; + TraceExportParams: TraceExportParams; + TraceExportResult: TraceExportResult; + TracePageParams: TracePageParams; + UnsubscribeParams: UnsubscribeParams; + UploadBeginParams: UploadBeginParams; + UploadChunkParams: UploadChunkParams; + UploadFinishParams: UploadFinishParams; + UserHistoryResult: UserHistoryResult; +} +export interface EventPayloadTypes { + "agent.admitted": LifecycleEvent | ContentEventPayload; + "agent.prompt.queued": LifecycleEvent | ContentEventPayload; + "agent.subtree.deleted": LifecycleEvent | ContentEventPayload; + "agent.subtree.stopped": LifecycleEvent | ContentEventPayload; + "agent.turn.cancelled": LifecycleEvent | ContentEventPayload; + "agent.turn.failed": LifecycleEvent | ContentEventPayload; + "agent.turn.interrupted": LifecycleEvent | ContentEventPayload; + "agent.turn.started": LifecycleEvent | ContentEventPayload; + "agent.turn.succeeded": LifecycleEvent | ContentEventPayload; + "blackboard.append": LifecycleEvent | ContentEventPayload; + "blackboard.cas": LifecycleEvent | ContentEventPayload; + "blackboard.set": LifecycleEvent | ContentEventPayload; + "budget.active_child.reserved": LifecycleEvent | ContentEventPayload; + "budget.capped": LifecycleEvent | ContentEventPayload; + "capability.delegated": LifecycleEvent | ContentEventPayload; + "capability.revoked": LifecycleEvent | ContentEventPayload; + "command.cancelled": LifecycleEvent | ContentEventPayload; + "command.control.queued": LifecycleEvent | ContentEventPayload; + "command.failed": LifecycleEvent | ContentEventPayload; + "command.interrupted": LifecycleEvent | ContentEventPayload; + "command.queued": LifecycleEvent | ContentEventPayload; + "command.running": LifecycleEvent | ContentEventPayload; + "command.succeeded": LifecycleEvent | ContentEventPayload; + "command.waiting": LifecycleEvent | ContentEventPayload; + "goal.continued": LifecycleEvent | ContentEventPayload; + "inbox.consumed": LifecycleEvent | ContentEventPayload; + "inbox.failed": LifecycleEvent | ContentEventPayload; + "inbox.queued": LifecycleEvent | ContentEventPayload; + "inbox.removed": LifecycleEvent | ContentEventPayload; + "inbox.running": LifecycleEvent | ContentEventPayload; + "inbox.steering": LifecycleEvent | ContentEventPayload; + "message.deferred": LifecycleEvent | ContentEventPayload; + "message.delivered": LifecycleEvent | ContentEventPayload; + "message.done": LifecycleEvent | ContentEventPayload; + "message.queued": LifecycleEvent | ContentEventPayload; + "message.updated": LifecycleEvent | ContentEventPayload; + "model.call.corrected": LifecycleEvent | ContentEventPayload; + "model.call.interrupted": LifecycleEvent | ContentEventPayload; + "model.call.settled": LifecycleEvent | ContentEventPayload; + "model.call.started": LifecycleEvent | ContentEventPayload; + "permission.auto_approved": LifecycleEvent | ContentEventPayload; + "permission.pending": LifecycleEvent | ContentEventPayload; + "question.answered": LifecycleEvent | ContentEventPayload; + "question.closed": LifecycleEvent | ContentEventPayload; + "question.pending": LifecycleEvent | ContentEventPayload; + "root.failed": LifecycleEvent | ContentEventPayload; + "root.interrupted": LifecycleEvent | ContentEventPayload; + "root.stopped": LifecycleEvent | ContentEventPayload; + "schedule.fired": LifecycleEvent | ContentEventPayload; + "scratch.restored": LifecycleEvent | ContentEventPayload; + "session.archived.updated": SessionUpdateEvent | ContentEventPayload; + "session.cwd.updated": SessionUpdateEvent | ContentEventPayload; + "session.effort.updated": SessionUpdateEvent | ContentEventPayload; + "session.model.updated": SessionUpdateEvent | ContentEventPayload; + "session.permission_mode.updated": SessionUpdateEvent | ContentEventPayload; + "session.reload.failed": LifecycleEvent | ContentEventPayload; + "session.title.updated": SessionUpdateEvent | ContentEventPayload; + "span.ended": SpanRecord | ContentEventPayload; + "span.started": SpanRecord | ContentEventPayload; + "state.private.append": LifecycleEvent | ContentEventPayload; + "state.private.cas": LifecycleEvent | ContentEventPayload; + "state.private.set": LifecycleEvent | ContentEventPayload; + "stream.accounting": StreamEvent | ContentEventPayload; + "stream.cell.host": StreamEvent | ContentEventPayload; + "stream.cell.host.started": StreamEvent | ContentEventPayload; + "stream.discard": StreamEvent | ContentEventPayload; + "stream.hook.decision": StreamEvent | ContentEventPayload; + "stream.notice": StreamEvent | ContentEventPayload; + "stream.reasoning": StreamEvent | ContentEventPayload; + "stream.terminal.awaiting": StreamEvent | ContentEventPayload; + "stream.terminal.completed": StreamEvent | ContentEventPayload; + "stream.terminal.output": StreamEvent | ContentEventPayload; + "stream.terminal.started": StreamEvent | ContentEventPayload; + "stream.text": StreamEvent | ContentEventPayload; + "stream.tool.call": StreamEvent | ContentEventPayload; + "stream.tool.completed": StreamEvent | ContentEventPayload; + "stream.tool.output": StreamEvent | ContentEventPayload; + "stream.tool.progress": StreamEvent | ContentEventPayload; + "stream.tool.started": StreamEvent | ContentEventPayload; + "stream.usage": StreamEvent | ContentEventPayload; + "subscription.cancelled": LifecycleEvent | ContentEventPayload; + "subscription.created": LifecycleEvent | ContentEventPayload; + "turn.cancelled": LifecycleEvent | ContentEventPayload; + "turn.failed": LifecycleEvent | ContentEventPayload; + "turn.interrupted": LifecycleEvent | ContentEventPayload; + "turn.started": LifecycleEvent | ContentEventPayload; + "turn.succeeded": LifecycleEvent | ContentEventPayload; +} +export interface RpcMethods { + "browser.command.result": { params: BrowserCommandResultParams; result: Accepted; execution: "ephemeral"; permission: "browser-provider"; sensitive: false }; + "browser.inventory.result": { params: BrowserInventoryResultParams; result: Accepted; execution: "ephemeral"; permission: "browser-provider"; sensitive: false }; + "browser.provider.bind": { params: BrowserProviderBindParams; result: BrowserProviderBindResult; execution: "ephemeral"; permission: "browser-provider"; sensitive: false }; + "browser.provider.event": { params: BrowserProviderEventParams; result: Accepted; execution: "ephemeral"; permission: "browser-provider"; sensitive: false }; + "browser.provider.unbind": { params: BrowserProviderUnbindParams; result: Accepted; execution: "ephemeral"; permission: "browser-provider"; sensitive: false }; + "command.status": { params: CommandStatusParams; result: CommandResult; execution: "query"; permission: "client-command-namespace"; sensitive: false }; + "command.submit": { params: CommandParams; result: CommandResult; execution: "command"; permission: "operation-specific"; sensitive: false }; + "config.get": { params: Empty; result: RuntimeConfiguration; execution: "query"; permission: "none"; sensitive: false }; + "config.update": { params: ConfigurationUpdate; result: RuntimeConfiguration; execution: "ephemeral"; permission: "configuration-revision"; sensitive: false }; + "content.read": { params: ContentReadParams; result: ContentReadResult; execution: "query"; permission: "root-agent-content-grant"; sensitive: false }; + "daemon.ping": { params: Empty; result: PingResult; execution: "query"; permission: "none"; sensitive: false }; + "daemon.restart": { params: RestartParams; result: Empty; execution: "lifecycle"; permission: "armed-generation"; sensitive: false }; + "daemon.stop": { params: RestartParams; result: Empty; execution: "lifecycle"; permission: "armed-generation"; sensitive: false }; + "definitions.get": { params: DefinitionParams; result: DefinitionRecord; execution: "query"; permission: "host-runtime"; sensitive: false }; + "definitions.list": { params: Empty; result: DefinitionList; execution: "query"; permission: "host-runtime"; sensitive: false }; + "definitions.register": { params: DefinitionRegisterParams; result: DefinitionRegisterResult; execution: "ephemeral"; permission: "host-runtime"; sensitive: false }; + "events.replay": { params: ReplayParams; result: ReplayResult; execution: "query"; permission: "root-association"; sensitive: false }; + "events.subscribe": { params: SubscribeParams; result: SubscribeResult; execution: "subscription"; permission: "root-association"; sensitive: false }; + "events.unsubscribe": { params: UnsubscribeParams; result: Empty; execution: "subscription"; permission: "connection-subscription"; sensitive: false }; + "executor.bind": { params: ExecutorBindParams; result: ExecutorBindResult; execution: "ephemeral"; permission: "host-runtime"; sensitive: false }; + "executor.pending": { params: ExecutorPendingParams; result: ExecutorPendingResult; execution: "query"; permission: "executor-lease"; sensitive: false }; + "gateway.status": { params: Empty; result: GatewayStatus; execution: "query"; permission: "none"; sensitive: false }; + "history.page": { params: HistoryPageParams; result: BoundedTranscriptPage; execution: "query"; permission: "root-agent-association"; sensitive: false }; + "hook.result": { params: HookResultParams; result: Accepted; execution: "ephemeral"; permission: "executor-lease"; sensitive: false }; + "host.attention": { params: HostAttentionParams; result: HostAttentionResult; execution: "query"; permission: "host-runtime"; sensitive: false }; + "host.directories.list": { params: HostDirectoryParams; result: HostDirectoryResult; execution: "query"; permission: "host-runtime"; sensitive: false }; + "host.directory.pick": { params: HostDirectoryPickParams; result: HostDirectoryPickResult; execution: "query"; permission: "host-runtime"; sensitive: false }; + "host.skills.complete": { params: HostSkillCompletionParams; result: CompletionResult; execution: "query"; permission: "host-runtime"; sensitive: false }; + "host.themes.list": { params: EmptyParams; result: CatalogResult; execution: "query"; permission: "host-runtime"; sensitive: false }; + "host.themes.resolve": { params: HostThemeResolveParams; result: Resolved; execution: "query"; permission: "host-runtime"; sensitive: false }; + "initialize": { params: InitializeParams; result: InitializeResult; execution: "query"; permission: "none"; sensitive: false }; + "mailbox.list": { params: MailboxPageParams; result: MailboxPage; execution: "query"; permission: "root-agent-association"; sensitive: false }; + "mailbox.read": { params: MailboxReadParams; result: MailboxInspection; execution: "query"; permission: "root-agent-association"; sensitive: false }; + "mcp.brand.icons": { params: MCPBrandIconsParams; result: MCPBrandIconsResult; execution: "query"; permission: "host-configuration"; sensitive: false }; + "mcp.import.apply": { params: MCPImportApplyParams; result: MCPImportApplyResult; execution: "ephemeral"; permission: "host-configuration"; sensitive: false }; + "mcp.import.candidates": { params: MCPImportCandidatesParams; result: MCPImportCandidatesResult; execution: "query"; permission: "host-configuration"; sensitive: false }; + "operation.invoke": { params: QueryParams; result: QueryResult; execution: "ephemeral"; permission: "operation-specific"; sensitive: true }; + "permission.decide": { params: PermissionDecisionParams; result: PermissionDecisionResult; execution: "ephemeral"; permission: "trusted-client-decision"; sensitive: false }; + "provider.create": { params: ProviderCreateParams; result: ProviderConfiguration; execution: "ephemeral"; permission: "configuration-revision"; sensitive: true }; + "provider.disconnect": { params: ProviderDisconnectParams; result: ProviderStatus; execution: "ephemeral"; permission: "configuration-revision"; sensitive: false }; + "provider.discover": { params: ProviderListParams; result: ProviderList; execution: "ephemeral"; permission: "host-configuration"; sensitive: false }; + "provider.get": { params: ProviderNameParams; result: ProviderConfiguration; execution: "query"; permission: "host-configuration"; sensitive: false }; + "provider.key.rotate": { params: ProviderNameParams; result: ProviderStatus; execution: "ephemeral"; permission: "host-configuration"; sensitive: true }; + "provider.key.set": { params: ProviderKeySetup; result: RuntimeConfiguration; execution: "ephemeral"; permission: "configuration-revision"; sensitive: true }; + "provider.list": { params: ProviderListParams; result: ProviderList; execution: "query"; permission: "host-configuration"; sensitive: false }; + "provider.login.begin": { params: ProviderLoginBeginParams; result: ProviderLoginStatus; execution: "ephemeral"; permission: "host-configuration"; sensitive: false }; + "provider.login.cancel": { params: ProviderLoginParams; result: ProviderLoginStatus; execution: "ephemeral"; permission: "host-configuration"; sensitive: false }; + "provider.login.list": { params: Empty; result: ProviderLoginList; execution: "query"; permission: "host-configuration"; sensitive: false }; + "provider.login.project.create": { params: ProviderLoginCreateParams; result: ProviderLoginStatus; execution: "ephemeral"; permission: "host-configuration"; sensitive: false }; + "provider.login.project.select": { params: ProviderLoginProjectParams; result: ProviderLoginStatus; execution: "ephemeral"; permission: "host-configuration"; sensitive: false }; + "provider.login.status": { params: ProviderLoginParams; result: ProviderLoginStatus; execution: "query"; permission: "none"; sensitive: false }; + "provider.login.team.select": { params: ProviderLoginTeamParams; result: ProviderLoginStatus; execution: "ephemeral"; permission: "host-configuration"; sensitive: false }; + "provider.logout": { params: ProviderNameParams; result: ProviderStatus; execution: "ephemeral"; permission: "host-configuration"; sensitive: false }; + "provider.remove": { params: ProviderRemoveParams; result: ProviderRemoveResult; execution: "ephemeral"; permission: "configuration-revision"; sensitive: false }; + "provider.status": { params: ProviderNameParams; result: ProviderStatus; execution: "query"; permission: "host-configuration"; sensitive: false }; + "provider.update": { params: ProviderUpdateParams; result: ProviderConfiguration; execution: "ephemeral"; permission: "configuration-revision"; sensitive: true }; + "provider.validate": { params: ProviderValidateParams; result: ProviderValidateResult; execution: "ephemeral"; permission: "host-configuration"; sensitive: true }; + "query": { params: QueryParams; result: QueryResult; execution: "query"; permission: "operation-specific"; sensitive: false }; + "root.collection": { params: RootCollectionParams; result: RootCollectionPage; execution: "query"; permission: "root-association"; sensitive: false }; + "root.snapshot": { params: SnapshotParams; result: RootSnapshot; execution: "query"; permission: "root-association"; sensitive: false }; + "sessions.get": { params: RootParams; result: SessionMetadata; execution: "query"; permission: "root-association"; sensitive: false }; + "sessions.list": { params: SessionCatalogParams; result: SessionCatalogPage; execution: "query"; permission: "host-runtime"; sensitive: false }; + "sessions.revision": { params: EmptyParams; result: CatalogRevision; execution: "query"; permission: "host-runtime"; sensitive: false }; + "sessions.summaries": { params: SessionSummariesParams; result: SessionSummariesResult; execution: "query"; permission: "host-runtime"; sensitive: false }; + "terminal.attach": { params: TerminalAttachParams; result: TerminalAttachResult; execution: "ephemeral"; permission: "host-terminal"; sensitive: false }; + "terminal.close": { params: TerminalIDParams; result: Accepted; execution: "ephemeral"; permission: "host-terminal"; sensitive: false }; + "terminal.open": { params: TerminalOpenParams; result: TerminalOpenResult; execution: "ephemeral"; permission: "host-terminal"; sensitive: false }; + "terminal.resize": { params: TerminalResizeParams; result: Accepted; execution: "ephemeral"; permission: "host-terminal"; sensitive: false }; + "terminal.write": { params: TerminalWriteParams; result: Accepted; execution: "ephemeral"; permission: "host-terminal"; sensitive: true }; + "tool.progress": { params: ToolProgressParams; result: Accepted; execution: "ephemeral"; permission: "executor-lease"; sensitive: false }; + "tool.result": { params: ToolResultParams; result: Accepted; execution: "ephemeral"; permission: "executor-lease"; sensitive: false }; + "trace.export": { params: TraceExportParams; result: TraceExportResult; execution: "query"; permission: "root-association"; sensitive: false }; + "trace.page": { params: TracePageParams; result: SpanPage; execution: "query"; permission: "root-association"; sensitive: false }; + "upload.begin": { params: UploadBeginParams; result: Accepted; execution: "ephemeral"; permission: "content-grant"; sensitive: false }; + "upload.chunk": { params: UploadChunkParams; result: Accepted; execution: "ephemeral"; permission: "connection-upload"; sensitive: false }; + "upload.finish": { params: UploadFinishParams; result: ContentHandle; execution: "ephemeral"; permission: "content-grant"; sensitive: false }; + "workspace.complete": { params: CompletionParams; result: CompletionResult; execution: "query"; permission: "root-agent-association"; sensitive: false }; +} +export interface RuntimeOperations { + "agent.control": { params: IDParams; result: Empty; execution: "command"; permission: "agent-authority"; sensitive: false }; + "agent.delete": { params: IDParams; result: Empty; execution: "command"; permission: "agent-authority"; sensitive: false }; + "agent.submit": { params: AgentInputParams; result: AgentSubmitResult; execution: "command"; permission: "agent-admission"; sensitive: false }; + "agent.transcript": { params: IDParams; result: AgentTranscriptResult; execution: "query"; permission: "human-transcript-inspection"; sensitive: false }; + "agent.turn.cancel": { params: AgentCancelParams; result: Empty; execution: "command"; permission: "target-turn"; sensitive: false }; + "agents.list": { params: EmptyParams; result: AgentListResult; execution: "query"; permission: "root-association"; sensitive: false }; + "browser.set_driver": { params: BrowserDriverParams; result: BrowserStatusResult; execution: "command"; permission: "host-configuration"; sensitive: false }; + "browser.status": { params: EmptyParams; result: BrowserStatusResult; execution: "query"; permission: "root-association"; sensitive: false }; + "budget.cap": { params: BudgetCapParams; result: BudgetState; execution: "command"; permission: "budget-authority"; sensitive: false }; + "cancel": { params: CancelParams; result: Empty; execution: "command"; permission: "target-turn"; sensitive: false }; + "capability.revoke": { params: IDParams; result: CapabilityRecord; execution: "command"; permission: "capability-authority"; sensitive: false }; + "compaction.configure": { params: CompactionParams; result: CompactionSettingsResult; execution: "command"; permission: "root-idle"; sensitive: false }; + "computer.allow": { params: ComputerAppParams; result: ComputerStatusResult; execution: "command"; permission: "human-computer-policy"; sensitive: false }; + "computer.deny": { params: ComputerAppParams; result: ComputerStatusResult; execution: "command"; permission: "human-computer-policy"; sensitive: false }; + "computer.status": { params: EmptyParams; result: ComputerStatusResult; execution: "query"; permission: "root-association"; sensitive: false }; + "context.audit": { params: EmptyParams; result: ContextAuditResult; execution: "query"; permission: "root-association"; sensitive: false }; + "daemon.checkpoint": { params: CheckpointParams; result: RestartNotice; execution: "command"; permission: "host-runtime"; sensitive: false }; + "goal.from-context": { params: GoalContextParams; result: GoalResult; execution: "command"; permission: "root-admission"; sensitive: false }; + "goal.run": { params: TextParams; result: GoalResult; execution: "command"; permission: "root-admission"; sensitive: false }; + "goal.set": { params: TextParams; result: GoalResult; execution: "command"; permission: "root-admission"; sensitive: false }; + "history.clear": { params: ClearHistoryParams; result: Empty; execution: "command"; permission: "root-idle"; sensitive: false }; + "history.compact": { params: EmptyParams; result: CompactionResult; execution: "command"; permission: "root-idle"; sensitive: false }; + "history.compact.log": { params: EmptyParams; result: CompactionListResult; execution: "query"; permission: "root-association"; sensitive: false }; + "history.compact.retry": { params: EmptyParams; result: CompactionRetryResult; execution: "command"; permission: "root-idle"; sensitive: false }; + "history.rewind": { params: RewindParams; result: RewindResult; execution: "command"; permission: "root-idle"; sensitive: false }; + "history.user.list": { params: EmptyParams; result: UserHistoryResult; execution: "query"; permission: "root-association"; sensitive: false }; + "inbox.remove": { params: InboxRemoveParams; result: InboxControlResult; execution: "command"; permission: "root-association"; sensitive: false }; + "inbox.steer": { params: InboxSteerParams; result: InboxControlResult; execution: "command"; permission: "root-association"; sensitive: false }; + "lsp.status": { params: EmptyParams; result: LSPListResult; execution: "query"; permission: "root-association"; sensitive: false }; + "mcp.attach": { params: MCPAttachParams; result: Empty; execution: "ephemeral"; permission: "delegated-mcp-authority"; sensitive: true }; + "mcp.disable": { params: MCPServerParams; result: Empty; execution: "command"; permission: "delegated-mcp-authority"; sensitive: false }; + "mcp.enable": { params: MCPServerParams; result: Empty; execution: "command"; permission: "delegated-mcp-authority"; sensitive: false }; + "mcp.import.configure": { params: MCPImportParams; result: MCPImportStatusResult; execution: "command"; permission: "host-configuration"; sensitive: false }; + "mcp.import.status": { params: EmptyParams; result: MCPImportStatusResult; execution: "query"; permission: "host-configuration"; sensitive: false }; + "mcp.reconnect": { params: MCPServerParams; result: Empty; execution: "command"; permission: "delegated-mcp-authority"; sensitive: false }; + "mcp.refresh": { params: EmptyParams; result: MCPRefreshResult; execution: "command"; permission: "root-association"; sensitive: false }; + "mcp.status": { params: EmptyParams; result: MCPListResult; execution: "query"; permission: "root-association"; sensitive: false }; + "permission.forget": { params: IDParams; result: Empty; execution: "command"; permission: "rule-authority"; sensitive: false }; + "permission.mode": { params: PermissionConfigureParams; result: Empty; execution: "command"; permission: "trusted-client-mode"; sensitive: false }; + "permission.rules": { params: EmptyParams; result: PermissionRulesResult; execution: "query"; permission: "root-association"; sensitive: false }; + "provider.catalogs": { params: ProviderCatalogParams; result: ProviderCatalogsResult; execution: "query"; permission: "host-runtime"; sensitive: false }; + "question.answer": { params: QuestionAnswerParams; result: Empty; execution: "command"; permission: "pending-question"; sensitive: false }; + "run.configure": { params: RunConfigureParams; result: Empty; execution: "command"; permission: "root-idle"; sensitive: false }; + "schedule.create": { params: ScheduleCreateParams; result: ScheduleResult; execution: "command"; permission: "schedule-budget"; sensitive: false }; + "schedule.delete": { params: ScheduleDeleteParams; result: ScheduleResult; execution: "command"; permission: "root-association"; sensitive: false }; + "schedule.list": { params: EmptyParams; result: ScheduleListResult; execution: "query"; permission: "root-association"; sensitive: false }; + "session.archive": { params: ArchiveParams; result: ArchiveResult; execution: "command"; permission: "root-association"; sensitive: false }; + "session.autotitle": { params: EmptyParams; result: Empty; execution: "command"; permission: "root-association"; sensitive: false }; + "session.create": { params: CreateSessionParams; result: RootIDResult; execution: "command"; permission: "host-runtime"; sensitive: false }; + "session.delete": { params: RootParams; result: RootIDResult; execution: "command"; permission: "root-association"; sensitive: false }; + "session.effort": { params: EffortParams; result: EffortResult; execution: "command"; permission: "root-idle"; sensitive: false }; + "session.effort.get": { params: EmptyParams; result: EffortResult; execution: "query"; permission: "root-association"; sensitive: false }; + "session.fork": { params: ForkParams; result: RootIDResult; execution: "command"; permission: "root-association"; sensitive: false }; + "session.list": { params: ListParams; result: SessionListResult; execution: "query"; permission: "host-runtime"; sensitive: false }; + "session.model": { params: ModelParams; result: ModelResult; execution: "command"; permission: "root-idle"; sensitive: false }; + "session.model.get": { params: EmptyParams; result: ModelResult; execution: "query"; permission: "root-association"; sensitive: false }; + "session.open": { params: IDParams; result: RootIDResult; execution: "query"; permission: "root-association"; sensitive: false }; + "session.preview": { params: IDParams; result: SessionPreviewResult; execution: "query"; permission: "root-association"; sensitive: false }; + "session.reload": { params: EmptyParams; result: ModelResult; execution: "command"; permission: "root-idle"; sensitive: false }; + "session.rename": { params: TitleParams; result: TitleResult; execution: "command"; permission: "root-association"; sensitive: false }; + "shell.run": { params: ShellParams; result: TextResult; execution: "command"; permission: "tool-permissions"; sensitive: false }; + "steer": { params: SubmitPayload; result: TextResult; execution: "command"; permission: "root-admission"; sensitive: false }; + "submit": { params: SubmitPayload; result: TextResult; execution: "command"; permission: "root-admission"; sensitive: false }; + "terminal.input": { params: TerminalInputParams; result: Empty; execution: "ephemeral"; permission: "active-terminal"; sensitive: true }; + "tool.call": { params: ToolCallParams; result: TextResult; execution: "command"; permission: "tool-permissions"; sensitive: false }; + "tool.configure": { params: ToolConfigureParams; result: Empty; execution: "command"; permission: "tool-permissions"; sensitive: false }; + "tool.schema": { params: EmptyParams; result: ToolSchemaResult; execution: "query"; permission: "tool-authority"; sensitive: false }; + "workspace.inspect": { params: EmptyParams; result: PathResult; execution: "query"; permission: "root-association"; sensitive: false }; + "workspace.set": { params: PathParams; result: PathResult; execution: "command"; permission: "workspace-authority"; sensitive: false }; +} + +export type RpcMethod = keyof RpcMethods; +export type RuntimeOperation = keyof RuntimeOperations; +export type RuntimeOperationOf = { + [K in RuntimeOperation]: RuntimeOperations[K]['execution'] extends E ? K : never +}[RuntimeOperation]; +export type QueryOperation = RuntimeOperationOf<'query'>; +export type CommandOperation = RuntimeOperationOf<'command'>; +export type EphemeralOperation = RuntimeOperationOf<'ephemeral'>; +export interface OperationMetadata { + readonly name: string; + readonly surface: 'rpc' | 'runtime'; + readonly execution: 'query' | 'command' | 'ephemeral' | 'subscription' | 'lifecycle'; + readonly permission: string; + readonly sensitive: boolean; + readonly params_type: keyof ContractTypes; + readonly result_type: keyof ContractTypes; +} +export declare const rpcOperations: Readonly<{ [K in RpcMethod]: OperationMetadata & { readonly name: K; readonly execution: RpcMethods[K]['execution']; readonly permission: RpcMethods[K]['permission']; readonly sensitive: RpcMethods[K]['sensitive'] } }>; +export declare const runtimeOperations: Readonly<{ [K in RuntimeOperation]: OperationMetadata & { readonly name: K; readonly execution: RuntimeOperations[K]['execution']; readonly permission: RuntimeOperations[K]['permission']; readonly sensitive: RuntimeOperations[K]['sensitive'] } }>; +export type RootEventEnvelope = Omit; +export type TypedRootEvent = { [K in keyof EventPayloadTypes]: RootEventEnvelope & { unknown?: false; kind: K; payload: EventPayloadTypes[K] } }[keyof EventPayloadTypes]; +export type UnknownRootEvent = RootEventEnvelope & { unknown: true; kind: string; payload: unknown }; +export type RootEvent = TypedRootEvent | UnknownRootEvent; +export type TypedEventNotification = { event: RootEvent }; +export type ValidationMode = 'request' | 'response'; +export declare function validate(type: T, value: unknown, mode?: ValidationMode): value is ContractTypes[T]; +export declare function assertValid(type: T, value: unknown, mode?: ValidationMode): asserts value is ContractTypes[T]; +export declare const manifest: { + major: number; minor: number; + operations: readonly (Omit & { readonly sensitive?: boolean })[]; + events: Readonly>; + event_payloads: Readonly>; +}; diff --git a/vendor/@whip/protocol/generated/index.js b/vendor/@whip/protocol/generated/index.js new file mode 100644 index 0000000..923993f --- /dev/null +++ b/vendor/@whip/protocol/generated/index.js @@ -0,0 +1,1313 @@ +// Generated from Go wire types. Run npm run generate. +import * as requests from './request-validators.js'; +import * as responses from './response-validators.js'; +export const manifest = { + "major": 6, + "minor": 8, + "operations": [ + { + "name": "browser.command.result", + "surface": "rpc", + "execution": "ephemeral", + "permission": "browser-provider", + "params_type": "BrowserCommandResultParams", + "result_type": "Accepted" + }, + { + "name": "browser.inventory.result", + "surface": "rpc", + "execution": "ephemeral", + "permission": "browser-provider", + "params_type": "BrowserInventoryResultParams", + "result_type": "Accepted" + }, + { + "name": "browser.provider.bind", + "surface": "rpc", + "execution": "ephemeral", + "permission": "browser-provider", + "params_type": "BrowserProviderBindParams", + "result_type": "BrowserProviderBindResult" + }, + { + "name": "browser.provider.event", + "surface": "rpc", + "execution": "ephemeral", + "permission": "browser-provider", + "params_type": "BrowserProviderEventParams", + "result_type": "Accepted" + }, + { + "name": "browser.provider.unbind", + "surface": "rpc", + "execution": "ephemeral", + "permission": "browser-provider", + "params_type": "BrowserProviderUnbindParams", + "result_type": "Accepted" + }, + { + "name": "command.status", + "surface": "rpc", + "execution": "query", + "permission": "client-command-namespace", + "params_type": "CommandStatusParams", + "result_type": "CommandResult" + }, + { + "name": "command.submit", + "surface": "rpc", + "execution": "command", + "permission": "operation-specific", + "params_type": "CommandParams", + "result_type": "CommandResult" + }, + { + "name": "config.get", + "surface": "rpc", + "execution": "query", + "permission": "none", + "params_type": "Empty", + "result_type": "RuntimeConfiguration" + }, + { + "name": "config.update", + "surface": "rpc", + "execution": "ephemeral", + "permission": "configuration-revision", + "params_type": "ConfigurationUpdate", + "result_type": "RuntimeConfiguration" + }, + { + "name": "content.read", + "surface": "rpc", + "execution": "query", + "permission": "root-agent-content-grant", + "params_type": "ContentReadParams", + "result_type": "ContentReadResult" + }, + { + "name": "daemon.ping", + "surface": "rpc", + "execution": "query", + "permission": "none", + "params_type": "Empty", + "result_type": "PingResult" + }, + { + "name": "daemon.restart", + "surface": "rpc", + "execution": "lifecycle", + "permission": "armed-generation", + "params_type": "RestartParams", + "result_type": "Empty" + }, + { + "name": "daemon.stop", + "surface": "rpc", + "execution": "lifecycle", + "permission": "armed-generation", + "params_type": "RestartParams", + "result_type": "Empty" + }, + { + "name": "definitions.get", + "surface": "rpc", + "execution": "query", + "permission": "host-runtime", + "params_type": "DefinitionParams", + "result_type": "DefinitionRecord" + }, + { + "name": "definitions.list", + "surface": "rpc", + "execution": "query", + "permission": "host-runtime", + "params_type": "Empty", + "result_type": "DefinitionList" + }, + { + "name": "definitions.register", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-runtime", + "params_type": "DefinitionRegisterParams", + "result_type": "DefinitionRegisterResult" + }, + { + "name": "events.replay", + "surface": "rpc", + "execution": "query", + "permission": "root-association", + "params_type": "ReplayParams", + "result_type": "ReplayResult" + }, + { + "name": "events.subscribe", + "surface": "rpc", + "execution": "subscription", + "permission": "root-association", + "params_type": "SubscribeParams", + "result_type": "SubscribeResult" + }, + { + "name": "events.unsubscribe", + "surface": "rpc", + "execution": "subscription", + "permission": "connection-subscription", + "params_type": "UnsubscribeParams", + "result_type": "Empty" + }, + { + "name": "executor.bind", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-runtime", + "params_type": "ExecutorBindParams", + "result_type": "ExecutorBindResult" + }, + { + "name": "executor.pending", + "surface": "rpc", + "execution": "query", + "permission": "executor-lease", + "params_type": "ExecutorPendingParams", + "result_type": "ExecutorPendingResult" + }, + { + "name": "gateway.status", + "surface": "rpc", + "execution": "query", + "permission": "none", + "params_type": "Empty", + "result_type": "GatewayStatus" + }, + { + "name": "history.page", + "surface": "rpc", + "execution": "query", + "permission": "root-agent-association", + "params_type": "HistoryPageParams", + "result_type": "BoundedTranscriptPage" + }, + { + "name": "hook.result", + "surface": "rpc", + "execution": "ephemeral", + "permission": "executor-lease", + "params_type": "HookResultParams", + "result_type": "Accepted" + }, + { + "name": "host.attention", + "surface": "rpc", + "execution": "query", + "permission": "host-runtime", + "params_type": "HostAttentionParams", + "result_type": "HostAttentionResult" + }, + { + "name": "host.directories.list", + "surface": "rpc", + "execution": "query", + "permission": "host-runtime", + "params_type": "HostDirectoryParams", + "result_type": "HostDirectoryResult" + }, + { + "name": "host.directory.pick", + "surface": "rpc", + "execution": "query", + "permission": "host-runtime", + "params_type": "HostDirectoryPickParams", + "result_type": "HostDirectoryPickResult" + }, + { + "name": "host.skills.complete", + "surface": "rpc", + "execution": "query", + "permission": "host-runtime", + "params_type": "HostSkillCompletionParams", + "result_type": "CompletionResult" + }, + { + "name": "host.themes.list", + "surface": "rpc", + "execution": "query", + "permission": "host-runtime", + "params_type": "EmptyParams", + "result_type": "CatalogResult" + }, + { + "name": "host.themes.resolve", + "surface": "rpc", + "execution": "query", + "permission": "host-runtime", + "params_type": "HostThemeResolveParams", + "result_type": "Resolved" + }, + { + "name": "initialize", + "surface": "rpc", + "execution": "query", + "permission": "none", + "params_type": "InitializeParams", + "result_type": "InitializeResult" + }, + { + "name": "mailbox.list", + "surface": "rpc", + "execution": "query", + "permission": "root-agent-association", + "params_type": "MailboxPageParams", + "result_type": "MailboxPage" + }, + { + "name": "mailbox.read", + "surface": "rpc", + "execution": "query", + "permission": "root-agent-association", + "params_type": "MailboxReadParams", + "result_type": "MailboxInspection" + }, + { + "name": "mcp.brand.icons", + "surface": "rpc", + "execution": "query", + "permission": "host-configuration", + "params_type": "MCPBrandIconsParams", + "result_type": "MCPBrandIconsResult" + }, + { + "name": "mcp.import.apply", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-configuration", + "params_type": "MCPImportApplyParams", + "result_type": "MCPImportApplyResult" + }, + { + "name": "mcp.import.candidates", + "surface": "rpc", + "execution": "query", + "permission": "host-configuration", + "params_type": "MCPImportCandidatesParams", + "result_type": "MCPImportCandidatesResult" + }, + { + "name": "operation.invoke", + "surface": "rpc", + "execution": "ephemeral", + "permission": "operation-specific", + "sensitive": true, + "params_type": "QueryParams", + "result_type": "QueryResult" + }, + { + "name": "permission.decide", + "surface": "rpc", + "execution": "ephemeral", + "permission": "trusted-client-decision", + "params_type": "PermissionDecisionParams", + "result_type": "PermissionDecisionResult" + }, + { + "name": "provider.create", + "surface": "rpc", + "execution": "ephemeral", + "permission": "configuration-revision", + "sensitive": true, + "params_type": "ProviderCreateParams", + "result_type": "ProviderConfiguration" + }, + { + "name": "provider.disconnect", + "surface": "rpc", + "execution": "ephemeral", + "permission": "configuration-revision", + "params_type": "ProviderDisconnectParams", + "result_type": "ProviderStatus" + }, + { + "name": "provider.discover", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-configuration", + "params_type": "ProviderListParams", + "result_type": "ProviderList" + }, + { + "name": "provider.get", + "surface": "rpc", + "execution": "query", + "permission": "host-configuration", + "params_type": "ProviderNameParams", + "result_type": "ProviderConfiguration" + }, + { + "name": "provider.key.rotate", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-configuration", + "sensitive": true, + "params_type": "ProviderNameParams", + "result_type": "ProviderStatus" + }, + { + "name": "provider.key.set", + "surface": "rpc", + "execution": "ephemeral", + "permission": "configuration-revision", + "sensitive": true, + "params_type": "ProviderKeySetup", + "result_type": "RuntimeConfiguration" + }, + { + "name": "provider.list", + "surface": "rpc", + "execution": "query", + "permission": "host-configuration", + "params_type": "ProviderListParams", + "result_type": "ProviderList" + }, + { + "name": "provider.login.begin", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-configuration", + "params_type": "ProviderLoginBeginParams", + "result_type": "ProviderLoginStatus" + }, + { + "name": "provider.login.cancel", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-configuration", + "params_type": "ProviderLoginParams", + "result_type": "ProviderLoginStatus" + }, + { + "name": "provider.login.list", + "surface": "rpc", + "execution": "query", + "permission": "host-configuration", + "params_type": "Empty", + "result_type": "ProviderLoginList" + }, + { + "name": "provider.login.project.create", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-configuration", + "params_type": "ProviderLoginCreateParams", + "result_type": "ProviderLoginStatus" + }, + { + "name": "provider.login.project.select", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-configuration", + "params_type": "ProviderLoginProjectParams", + "result_type": "ProviderLoginStatus" + }, + { + "name": "provider.login.status", + "surface": "rpc", + "execution": "query", + "permission": "none", + "params_type": "ProviderLoginParams", + "result_type": "ProviderLoginStatus" + }, + { + "name": "provider.login.team.select", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-configuration", + "params_type": "ProviderLoginTeamParams", + "result_type": "ProviderLoginStatus" + }, + { + "name": "provider.logout", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-configuration", + "params_type": "ProviderNameParams", + "result_type": "ProviderStatus" + }, + { + "name": "provider.remove", + "surface": "rpc", + "execution": "ephemeral", + "permission": "configuration-revision", + "params_type": "ProviderRemoveParams", + "result_type": "ProviderRemoveResult" + }, + { + "name": "provider.status", + "surface": "rpc", + "execution": "query", + "permission": "host-configuration", + "params_type": "ProviderNameParams", + "result_type": "ProviderStatus" + }, + { + "name": "provider.update", + "surface": "rpc", + "execution": "ephemeral", + "permission": "configuration-revision", + "sensitive": true, + "params_type": "ProviderUpdateParams", + "result_type": "ProviderConfiguration" + }, + { + "name": "provider.validate", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-configuration", + "sensitive": true, + "params_type": "ProviderValidateParams", + "result_type": "ProviderValidateResult" + }, + { + "name": "query", + "surface": "rpc", + "execution": "query", + "permission": "operation-specific", + "params_type": "QueryParams", + "result_type": "QueryResult" + }, + { + "name": "root.collection", + "surface": "rpc", + "execution": "query", + "permission": "root-association", + "params_type": "RootCollectionParams", + "result_type": "RootCollectionPage" + }, + { + "name": "root.snapshot", + "surface": "rpc", + "execution": "query", + "permission": "root-association", + "params_type": "SnapshotParams", + "result_type": "RootSnapshot" + }, + { + "name": "sessions.get", + "surface": "rpc", + "execution": "query", + "permission": "root-association", + "params_type": "RootParams", + "result_type": "SessionMetadata" + }, + { + "name": "sessions.list", + "surface": "rpc", + "execution": "query", + "permission": "host-runtime", + "params_type": "SessionCatalogParams", + "result_type": "SessionCatalogPage" + }, + { + "name": "sessions.revision", + "surface": "rpc", + "execution": "query", + "permission": "host-runtime", + "params_type": "EmptyParams", + "result_type": "CatalogRevision" + }, + { + "name": "sessions.summaries", + "surface": "rpc", + "execution": "query", + "permission": "host-runtime", + "params_type": "SessionSummariesParams", + "result_type": "SessionSummariesResult" + }, + { + "name": "terminal.attach", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-terminal", + "params_type": "TerminalAttachParams", + "result_type": "TerminalAttachResult" + }, + { + "name": "terminal.close", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-terminal", + "params_type": "TerminalIDParams", + "result_type": "Accepted" + }, + { + "name": "terminal.open", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-terminal", + "params_type": "TerminalOpenParams", + "result_type": "TerminalOpenResult" + }, + { + "name": "terminal.resize", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-terminal", + "params_type": "TerminalResizeParams", + "result_type": "Accepted" + }, + { + "name": "terminal.write", + "surface": "rpc", + "execution": "ephemeral", + "permission": "host-terminal", + "sensitive": true, + "params_type": "TerminalWriteParams", + "result_type": "Accepted" + }, + { + "name": "tool.progress", + "surface": "rpc", + "execution": "ephemeral", + "permission": "executor-lease", + "params_type": "ToolProgressParams", + "result_type": "Accepted" + }, + { + "name": "tool.result", + "surface": "rpc", + "execution": "ephemeral", + "permission": "executor-lease", + "params_type": "ToolResultParams", + "result_type": "Accepted" + }, + { + "name": "trace.export", + "surface": "rpc", + "execution": "query", + "permission": "root-association", + "params_type": "TraceExportParams", + "result_type": "TraceExportResult" + }, + { + "name": "trace.page", + "surface": "rpc", + "execution": "query", + "permission": "root-association", + "params_type": "TracePageParams", + "result_type": "SpanPage" + }, + { + "name": "upload.begin", + "surface": "rpc", + "execution": "ephemeral", + "permission": "content-grant", + "params_type": "UploadBeginParams", + "result_type": "Accepted" + }, + { + "name": "upload.chunk", + "surface": "rpc", + "execution": "ephemeral", + "permission": "connection-upload", + "params_type": "UploadChunkParams", + "result_type": "Accepted" + }, + { + "name": "upload.finish", + "surface": "rpc", + "execution": "ephemeral", + "permission": "content-grant", + "params_type": "UploadFinishParams", + "result_type": "ContentHandle" + }, + { + "name": "workspace.complete", + "surface": "rpc", + "execution": "query", + "permission": "root-agent-association", + "params_type": "CompletionParams", + "result_type": "CompletionResult" + }, + { + "name": "agent.control", + "surface": "runtime", + "execution": "command", + "permission": "agent-authority", + "params_type": "IDParams", + "result_type": "Empty" + }, + { + "name": "agent.delete", + "surface": "runtime", + "execution": "command", + "permission": "agent-authority", + "params_type": "IDParams", + "result_type": "Empty" + }, + { + "name": "agent.submit", + "surface": "runtime", + "execution": "command", + "permission": "agent-admission", + "params_type": "AgentInputParams", + "result_type": "AgentSubmitResult" + }, + { + "name": "agent.transcript", + "surface": "runtime", + "execution": "query", + "permission": "human-transcript-inspection", + "params_type": "IDParams", + "result_type": "AgentTranscriptResult" + }, + { + "name": "agent.turn.cancel", + "surface": "runtime", + "execution": "command", + "permission": "target-turn", + "params_type": "AgentCancelParams", + "result_type": "Empty" + }, + { + "name": "agents.list", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "AgentListResult" + }, + { + "name": "browser.set_driver", + "surface": "runtime", + "execution": "command", + "permission": "host-configuration", + "params_type": "BrowserDriverParams", + "result_type": "BrowserStatusResult" + }, + { + "name": "browser.status", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "BrowserStatusResult" + }, + { + "name": "budget.cap", + "surface": "runtime", + "execution": "command", + "permission": "budget-authority", + "params_type": "BudgetCapParams", + "result_type": "BudgetState" + }, + { + "name": "cancel", + "surface": "runtime", + "execution": "command", + "permission": "target-turn", + "params_type": "CancelParams", + "result_type": "Empty" + }, + { + "name": "capability.revoke", + "surface": "runtime", + "execution": "command", + "permission": "capability-authority", + "params_type": "IDParams", + "result_type": "CapabilityRecord" + }, + { + "name": "compaction.configure", + "surface": "runtime", + "execution": "command", + "permission": "root-idle", + "params_type": "CompactionParams", + "result_type": "CompactionSettingsResult" + }, + { + "name": "computer.allow", + "surface": "runtime", + "execution": "command", + "permission": "human-computer-policy", + "params_type": "ComputerAppParams", + "result_type": "ComputerStatusResult" + }, + { + "name": "computer.deny", + "surface": "runtime", + "execution": "command", + "permission": "human-computer-policy", + "params_type": "ComputerAppParams", + "result_type": "ComputerStatusResult" + }, + { + "name": "computer.status", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "ComputerStatusResult" + }, + { + "name": "context.audit", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "ContextAuditResult" + }, + { + "name": "daemon.checkpoint", + "surface": "runtime", + "execution": "command", + "permission": "host-runtime", + "params_type": "CheckpointParams", + "result_type": "RestartNotice" + }, + { + "name": "goal.from-context", + "surface": "runtime", + "execution": "command", + "permission": "root-admission", + "params_type": "GoalContextParams", + "result_type": "GoalResult" + }, + { + "name": "goal.run", + "surface": "runtime", + "execution": "command", + "permission": "root-admission", + "params_type": "TextParams", + "result_type": "GoalResult" + }, + { + "name": "goal.set", + "surface": "runtime", + "execution": "command", + "permission": "root-admission", + "params_type": "TextParams", + "result_type": "GoalResult" + }, + { + "name": "history.clear", + "surface": "runtime", + "execution": "command", + "permission": "root-idle", + "params_type": "ClearHistoryParams", + "result_type": "Empty" + }, + { + "name": "history.compact", + "surface": "runtime", + "execution": "command", + "permission": "root-idle", + "params_type": "EmptyParams", + "result_type": "CompactionResult" + }, + { + "name": "history.compact.log", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "CompactionListResult" + }, + { + "name": "history.compact.retry", + "surface": "runtime", + "execution": "command", + "permission": "root-idle", + "params_type": "EmptyParams", + "result_type": "CompactionRetryResult" + }, + { + "name": "history.rewind", + "surface": "runtime", + "execution": "command", + "permission": "root-idle", + "params_type": "RewindParams", + "result_type": "RewindResult" + }, + { + "name": "history.user.list", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "UserHistoryResult" + }, + { + "name": "inbox.remove", + "surface": "runtime", + "execution": "command", + "permission": "root-association", + "params_type": "InboxRemoveParams", + "result_type": "InboxControlResult" + }, + { + "name": "inbox.steer", + "surface": "runtime", + "execution": "command", + "permission": "root-association", + "params_type": "InboxSteerParams", + "result_type": "InboxControlResult" + }, + { + "name": "lsp.status", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "LSPListResult" + }, + { + "name": "mcp.attach", + "surface": "runtime", + "execution": "ephemeral", + "permission": "delegated-mcp-authority", + "sensitive": true, + "params_type": "MCPAttachParams", + "result_type": "Empty" + }, + { + "name": "mcp.disable", + "surface": "runtime", + "execution": "command", + "permission": "delegated-mcp-authority", + "params_type": "MCPServerParams", + "result_type": "Empty" + }, + { + "name": "mcp.enable", + "surface": "runtime", + "execution": "command", + "permission": "delegated-mcp-authority", + "params_type": "MCPServerParams", + "result_type": "Empty" + }, + { + "name": "mcp.import.configure", + "surface": "runtime", + "execution": "command", + "permission": "host-configuration", + "params_type": "MCPImportParams", + "result_type": "MCPImportStatusResult" + }, + { + "name": "mcp.import.status", + "surface": "runtime", + "execution": "query", + "permission": "host-configuration", + "params_type": "EmptyParams", + "result_type": "MCPImportStatusResult" + }, + { + "name": "mcp.reconnect", + "surface": "runtime", + "execution": "command", + "permission": "delegated-mcp-authority", + "params_type": "MCPServerParams", + "result_type": "Empty" + }, + { + "name": "mcp.refresh", + "surface": "runtime", + "execution": "command", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "MCPRefreshResult" + }, + { + "name": "mcp.status", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "MCPListResult" + }, + { + "name": "permission.forget", + "surface": "runtime", + "execution": "command", + "permission": "rule-authority", + "params_type": "IDParams", + "result_type": "Empty" + }, + { + "name": "permission.mode", + "surface": "runtime", + "execution": "command", + "permission": "trusted-client-mode", + "params_type": "PermissionConfigureParams", + "result_type": "Empty" + }, + { + "name": "permission.rules", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "PermissionRulesResult" + }, + { + "name": "provider.catalogs", + "surface": "runtime", + "execution": "query", + "permission": "host-runtime", + "params_type": "ProviderCatalogParams", + "result_type": "ProviderCatalogsResult" + }, + { + "name": "question.answer", + "surface": "runtime", + "execution": "command", + "permission": "pending-question", + "params_type": "QuestionAnswerParams", + "result_type": "Empty" + }, + { + "name": "run.configure", + "surface": "runtime", + "execution": "command", + "permission": "root-idle", + "params_type": "RunConfigureParams", + "result_type": "Empty" + }, + { + "name": "schedule.create", + "surface": "runtime", + "execution": "command", + "permission": "schedule-budget", + "params_type": "ScheduleCreateParams", + "result_type": "ScheduleResult" + }, + { + "name": "schedule.delete", + "surface": "runtime", + "execution": "command", + "permission": "root-association", + "params_type": "ScheduleDeleteParams", + "result_type": "ScheduleResult" + }, + { + "name": "schedule.list", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "ScheduleListResult" + }, + { + "name": "session.archive", + "surface": "runtime", + "execution": "command", + "permission": "root-association", + "params_type": "ArchiveParams", + "result_type": "ArchiveResult" + }, + { + "name": "session.autotitle", + "surface": "runtime", + "execution": "command", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "Empty" + }, + { + "name": "session.create", + "surface": "runtime", + "execution": "command", + "permission": "host-runtime", + "params_type": "CreateSessionParams", + "result_type": "RootIDResult" + }, + { + "name": "session.delete", + "surface": "runtime", + "execution": "command", + "permission": "root-association", + "params_type": "RootParams", + "result_type": "RootIDResult" + }, + { + "name": "session.effort", + "surface": "runtime", + "execution": "command", + "permission": "root-idle", + "params_type": "EffortParams", + "result_type": "EffortResult" + }, + { + "name": "session.effort.get", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "EffortResult" + }, + { + "name": "session.fork", + "surface": "runtime", + "execution": "command", + "permission": "root-association", + "params_type": "ForkParams", + "result_type": "RootIDResult" + }, + { + "name": "session.list", + "surface": "runtime", + "execution": "query", + "permission": "host-runtime", + "params_type": "ListParams", + "result_type": "SessionListResult" + }, + { + "name": "session.model", + "surface": "runtime", + "execution": "command", + "permission": "root-idle", + "params_type": "ModelParams", + "result_type": "ModelResult" + }, + { + "name": "session.model.get", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "ModelResult" + }, + { + "name": "session.open", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "IDParams", + "result_type": "RootIDResult" + }, + { + "name": "session.preview", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "IDParams", + "result_type": "SessionPreviewResult" + }, + { + "name": "session.reload", + "surface": "runtime", + "execution": "command", + "permission": "root-idle", + "params_type": "EmptyParams", + "result_type": "ModelResult" + }, + { + "name": "session.rename", + "surface": "runtime", + "execution": "command", + "permission": "root-association", + "params_type": "TitleParams", + "result_type": "TitleResult" + }, + { + "name": "shell.run", + "surface": "runtime", + "execution": "command", + "permission": "tool-permissions", + "params_type": "ShellParams", + "result_type": "TextResult" + }, + { + "name": "steer", + "surface": "runtime", + "execution": "command", + "permission": "root-admission", + "params_type": "SubmitPayload", + "result_type": "TextResult" + }, + { + "name": "submit", + "surface": "runtime", + "execution": "command", + "permission": "root-admission", + "params_type": "SubmitPayload", + "result_type": "TextResult" + }, + { + "name": "terminal.input", + "surface": "runtime", + "execution": "ephemeral", + "permission": "active-terminal", + "sensitive": true, + "params_type": "TerminalInputParams", + "result_type": "Empty" + }, + { + "name": "tool.call", + "surface": "runtime", + "execution": "command", + "permission": "tool-permissions", + "params_type": "ToolCallParams", + "result_type": "TextResult" + }, + { + "name": "tool.configure", + "surface": "runtime", + "execution": "command", + "permission": "tool-permissions", + "params_type": "ToolConfigureParams", + "result_type": "Empty" + }, + { + "name": "tool.schema", + "surface": "runtime", + "execution": "query", + "permission": "tool-authority", + "params_type": "EmptyParams", + "result_type": "ToolSchemaResult" + }, + { + "name": "workspace.inspect", + "surface": "runtime", + "execution": "query", + "permission": "root-association", + "params_type": "EmptyParams", + "result_type": "PathResult" + }, + { + "name": "workspace.set", + "surface": "runtime", + "execution": "command", + "permission": "workspace-authority", + "params_type": "PathParams", + "result_type": "PathResult" + } + ], + "events": { + "browser.command": "BrowserCommand", + "browser.command.cancel": "BrowserCommandCancel", + "browser.inventory": "BrowserInventoryRequest", + "browser.provider.revoked": "BrowserProviderRevoked", + "event": "EventNotification", + "hook.cancel": "ToolCancelParams", + "hook.invoke": "HookInvokeParams", + "subscription.failed": "SubscriptionFailure", + "terminal.detached": "TerminalDetachedParams", + "terminal.exited": "TerminalExitedParams", + "terminal.output": "TerminalOutputParams", + "tool.cancel": "ToolCancelParams", + "tool.invoke": "ToolInvokeParams" + }, + "event_payloads": { + "agent.admitted": "LifecycleEvent", + "agent.prompt.queued": "LifecycleEvent", + "agent.subtree.deleted": "LifecycleEvent", + "agent.subtree.stopped": "LifecycleEvent", + "agent.turn.cancelled": "LifecycleEvent", + "agent.turn.failed": "LifecycleEvent", + "agent.turn.interrupted": "LifecycleEvent", + "agent.turn.started": "LifecycleEvent", + "agent.turn.succeeded": "LifecycleEvent", + "blackboard.append": "LifecycleEvent", + "blackboard.cas": "LifecycleEvent", + "blackboard.set": "LifecycleEvent", + "budget.active_child.reserved": "LifecycleEvent", + "budget.capped": "LifecycleEvent", + "capability.delegated": "LifecycleEvent", + "capability.revoked": "LifecycleEvent", + "command.cancelled": "LifecycleEvent", + "command.control.queued": "LifecycleEvent", + "command.failed": "LifecycleEvent", + "command.interrupted": "LifecycleEvent", + "command.queued": "LifecycleEvent", + "command.running": "LifecycleEvent", + "command.succeeded": "LifecycleEvent", + "command.waiting": "LifecycleEvent", + "goal.continued": "LifecycleEvent", + "inbox.consumed": "LifecycleEvent", + "inbox.failed": "LifecycleEvent", + "inbox.queued": "LifecycleEvent", + "inbox.removed": "LifecycleEvent", + "inbox.running": "LifecycleEvent", + "inbox.steering": "LifecycleEvent", + "message.deferred": "LifecycleEvent", + "message.delivered": "LifecycleEvent", + "message.done": "LifecycleEvent", + "message.queued": "LifecycleEvent", + "message.updated": "LifecycleEvent", + "model.call.corrected": "LifecycleEvent", + "model.call.interrupted": "LifecycleEvent", + "model.call.settled": "LifecycleEvent", + "model.call.started": "LifecycleEvent", + "permission.auto_approved": "LifecycleEvent", + "permission.pending": "LifecycleEvent", + "question.answered": "LifecycleEvent", + "question.closed": "LifecycleEvent", + "question.pending": "LifecycleEvent", + "root.failed": "LifecycleEvent", + "root.interrupted": "LifecycleEvent", + "root.stopped": "LifecycleEvent", + "schedule.fired": "LifecycleEvent", + "scratch.restored": "LifecycleEvent", + "session.archived.updated": "SessionUpdateEvent", + "session.cwd.updated": "SessionUpdateEvent", + "session.effort.updated": "SessionUpdateEvent", + "session.model.updated": "SessionUpdateEvent", + "session.permission_mode.updated": "SessionUpdateEvent", + "session.reload.failed": "LifecycleEvent", + "session.title.updated": "SessionUpdateEvent", + "span.ended": "SpanRecord", + "span.started": "SpanRecord", + "state.private.append": "LifecycleEvent", + "state.private.cas": "LifecycleEvent", + "state.private.set": "LifecycleEvent", + "stream.accounting": "StreamEvent", + "stream.cell.host": "StreamEvent", + "stream.cell.host.started": "StreamEvent", + "stream.discard": "StreamEvent", + "stream.hook.decision": "StreamEvent", + "stream.notice": "StreamEvent", + "stream.reasoning": "StreamEvent", + "stream.terminal.awaiting": "StreamEvent", + "stream.terminal.completed": "StreamEvent", + "stream.terminal.output": "StreamEvent", + "stream.terminal.started": "StreamEvent", + "stream.text": "StreamEvent", + "stream.tool.call": "StreamEvent", + "stream.tool.completed": "StreamEvent", + "stream.tool.output": "StreamEvent", + "stream.tool.progress": "StreamEvent", + "stream.tool.started": "StreamEvent", + "stream.usage": "StreamEvent", + "subscription.cancelled": "LifecycleEvent", + "subscription.created": "LifecycleEvent", + "turn.cancelled": "LifecycleEvent", + "turn.failed": "LifecycleEvent", + "turn.interrupted": "LifecycleEvent", + "turn.started": "LifecycleEvent", + "turn.succeeded": "LifecycleEvent" + } +}; +function operationLookup(surface) { + return Object.freeze(Object.fromEntries(manifest.operations.filter(operation => operation.surface === surface).map(operation => + [operation.name, Object.freeze({ ...operation, sensitive: operation.sensitive ?? false })], + ))); +} +export const rpcOperations = operationLookup('rpc'); +export const runtimeOperations = operationLookup('runtime'); +function validatorFor(type, mode) { + if (mode !== 'request' && mode !== 'response') throw new TypeError('Unknown WHIP validation mode: ' + mode); + const validators = mode === 'response' ? responses : requests; + if (!Object.hasOwn(validators, type)) throw new TypeError('Unknown WHIP contract type: ' + type); + return validators[type]; +} +export function validate(type, value, mode = 'request') { + return validatorFor(type, mode)(value); +} +export function assertValid(type, value, mode = 'request') { + const validator = validatorFor(type, mode); + if (!validator(value)) { + const details = validator.errors.map(error => (error.instancePath || '/') + ' ' + error.message).join('; '); + throw new TypeError('Invalid WHIP ' + type + ': ' + details); + } +} diff --git a/vendor/@whip/protocol/generated/request-validators.js b/vendor/@whip/protocol/generated/request-validators.js new file mode 100644 index 0000000..8c4d62a --- /dev/null +++ b/vendor/@whip/protocol/generated/request-validators.js @@ -0,0 +1,73016 @@ +// Generated standalone validators. Run npm run generate. +"use strict"; +export const Accepted = validate10; +const schema11 = {"type":"object","properties":{"accepted":{"type":"boolean"}},"$id":"https://whip.dev/protocol/v6/Accepted","$schema":"http://json-schema.org/draft-07/schema#","title":"Accepted","required":["accepted"],"additionalProperties":false}; + +function validate10(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){ +/*# sourceURL="https://whip.dev/protocol/v6/Accepted" */; +let vErrors = null; +let errors = 0; +if(data && typeof data == "object" && !Array.isArray(data)){ +if(data.accepted === undefined){ +const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "accepted"},message:"must have required property '"+"accepted"+"'"}; +if(vErrors === null){ +vErrors = [err0]; +} +else { +vErrors.push(err0); +} +errors++; +} +for(const key0 in data){ +if(!(key0 === "accepted")){ +const err1 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err1]; +} +else { +vErrors.push(err1); +} +errors++; +} +} +if(data.accepted !== undefined){ +if(typeof data.accepted !== "boolean"){ +const err2 = {instancePath:instancePath+"/accepted",schemaPath:"#/properties/accepted/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; +if(vErrors === null){ +vErrors = [err2]; +} +else { +vErrors.push(err2); +} +errors++; +} +} +} +else { +const err3 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err3]; +} +else { +vErrors.push(err3); +} +errors++; +} +validate10.errors = vErrors; +return errors === 0; +} + +export const AgentCancelParams = validate11; +const schema12 = {"type":"object","properties":{"id":{"type":"string"},"turn_id":{"type":"string"}},"$id":"https://whip.dev/protocol/v6/AgentCancelParams","$schema":"http://json-schema.org/draft-07/schema#","title":"AgentCancelParams","required":["id","turn_id"],"additionalProperties":false}; + +function validate11(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){ +/*# sourceURL="https://whip.dev/protocol/v6/AgentCancelParams" */; +let vErrors = null; +let errors = 0; +if(data && typeof data == "object" && !Array.isArray(data)){ +if(data.id === undefined){ +const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"}; +if(vErrors === null){ +vErrors = [err0]; +} +else { +vErrors.push(err0); +} +errors++; +} +if(data.turn_id === undefined){ +const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "turn_id"},message:"must have required property '"+"turn_id"+"'"}; +if(vErrors === null){ +vErrors = [err1]; +} +else { +vErrors.push(err1); +} +errors++; +} +for(const key0 in data){ +if(!((key0 === "id") || (key0 === "turn_id"))){ +const err2 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err2]; +} +else { +vErrors.push(err2); +} +errors++; +} +} +if(data.id !== undefined){ +if(typeof data.id !== "string"){ +const err3 = {instancePath:instancePath+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err3]; +} +else { +vErrors.push(err3); +} +errors++; +} +} +if(data.turn_id !== undefined){ +if(typeof data.turn_id !== "string"){ +const err4 = {instancePath:instancePath+"/turn_id",schemaPath:"#/properties/turn_id/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err4]; +} +else { +vErrors.push(err4); +} +errors++; +} +} +} +else { +const err5 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err5]; +} +else { +vErrors.push(err5); +} +errors++; +} +validate11.errors = vErrors; +return errors === 0; +} + +export const AgentInputParams = validate12; +const schema13 = {"type":"object","properties":{"design_context":{"type":["null","object"],"properties":{"context_attachment_id":{"type":"string"},"screenshot_attachment_id":{"type":"string"},"elements":{"type":["null","array"],"items":{"type":"object","properties":{"label":{"type":"string"},"selector":{"type":"string"}},"required":["label"],"additionalProperties":false}},"element_count":{"type":"integer"},"page_url":{"type":"string"},"page_title":{"type":"string"}},"required":["context_attachment_id","elements","element_count"],"additionalProperties":false},"id":{"type":"string"},"text":{"type":"string"},"delivery":{"type":"string"},"parts":{"type":["null","array"],"items":{"type":"object","properties":{"type":{"type":"string"},"text":{"type":"string"},"image_url":{"type":["null","object"],"properties":{"url":{"type":"string"}},"required":["url"],"additionalProperties":false},"w":{"type":"integer"},"h":{"type":"integer"}},"required":["type"],"additionalProperties":false}},"attachments":{"type":["null","array"],"items":{"type":"object","properties":{"kind":{"type":"string","enum":["image","text"]},"content":{"type":"object","properties":{"reference_id":{"type":"string"},"digest":{"type":"string"},"size":{"type":"string","pattern":"^-?(0|[1-9][0-9]*)$","format":"int64"},"media_type":{"type":"string"},"source":{"type":"string"}},"required":["reference_id","digest","size"],"additionalProperties":false},"name":{"type":"string"}},"required":["kind","content"],"additionalProperties":false}}},"$id":"https://whip.dev/protocol/v6/AgentInputParams","$schema":"http://json-schema.org/draft-07/schema#","title":"AgentInputParams","required":["id","text"],"additionalProperties":false}; +const formats0 = {int64: {type: 'string', validate: value => { + if (!/^-?(0|[1-9][0-9]*)$/.test(value) || value.length > 20) return false; + const number = BigInt(value); + return number >= -9223372036854775808n && number <= 9223372036854775807n; +}}}.int64; +const pattern0 = new RegExp("^-?(0|[1-9][0-9]*)$", "u"); + +function validate12(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){ +/*# sourceURL="https://whip.dev/protocol/v6/AgentInputParams" */; +let vErrors = null; +let errors = 0; +if(data && typeof data == "object" && !Array.isArray(data)){ +if(data.id === undefined){ +const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"}; +if(vErrors === null){ +vErrors = [err0]; +} +else { +vErrors.push(err0); +} +errors++; +} +if(data.text === undefined){ +const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "text"},message:"must have required property '"+"text"+"'"}; +if(vErrors === null){ +vErrors = [err1]; +} +else { +vErrors.push(err1); +} +errors++; +} +for(const key0 in data){ +if(!((((((key0 === "design_context") || (key0 === "id")) || (key0 === "text")) || (key0 === "delivery")) || (key0 === "parts")) || (key0 === "attachments"))){ +const err2 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err2]; +} +else { +vErrors.push(err2); +} +errors++; +} +} +if(data.design_context !== undefined){ +let data0 = data.design_context; +if((data0 !== null) && (!(data0 && typeof data0 == "object" && !Array.isArray(data0)))){ +const err3 = {instancePath:instancePath+"/design_context",schemaPath:"#/properties/design_context/type",keyword:"type",params:{type: schema13.properties.design_context.type},message:"must be null,object"}; +if(vErrors === null){ +vErrors = [err3]; +} +else { +vErrors.push(err3); +} +errors++; +} +if(data0 && typeof data0 == "object" && !Array.isArray(data0)){ +if(data0.context_attachment_id === undefined){ +const err4 = {instancePath:instancePath+"/design_context",schemaPath:"#/properties/design_context/required",keyword:"required",params:{missingProperty: "context_attachment_id"},message:"must have required property '"+"context_attachment_id"+"'"}; +if(vErrors === null){ +vErrors = [err4]; +} +else { +vErrors.push(err4); +} +errors++; +} +if(data0.elements === undefined){ +const err5 = {instancePath:instancePath+"/design_context",schemaPath:"#/properties/design_context/required",keyword:"required",params:{missingProperty: "elements"},message:"must have required property '"+"elements"+"'"}; +if(vErrors === null){ +vErrors = [err5]; +} +else { +vErrors.push(err5); +} +errors++; +} +if(data0.element_count === undefined){ +const err6 = {instancePath:instancePath+"/design_context",schemaPath:"#/properties/design_context/required",keyword:"required",params:{missingProperty: "element_count"},message:"must have required property '"+"element_count"+"'"}; +if(vErrors === null){ +vErrors = [err6]; +} +else { +vErrors.push(err6); +} +errors++; +} +for(const key1 in data0){ +if(!((((((key1 === "context_attachment_id") || (key1 === "screenshot_attachment_id")) || (key1 === "elements")) || (key1 === "element_count")) || (key1 === "page_url")) || (key1 === "page_title"))){ +const err7 = {instancePath:instancePath+"/design_context",schemaPath:"#/properties/design_context/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err7]; +} +else { +vErrors.push(err7); +} +errors++; +} +} +if(data0.context_attachment_id !== undefined){ +if(typeof data0.context_attachment_id !== "string"){ +const err8 = {instancePath:instancePath+"/design_context/context_attachment_id",schemaPath:"#/properties/design_context/properties/context_attachment_id/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err8]; +} +else { +vErrors.push(err8); +} +errors++; +} +} +if(data0.screenshot_attachment_id !== undefined){ +if(typeof data0.screenshot_attachment_id !== "string"){ +const err9 = {instancePath:instancePath+"/design_context/screenshot_attachment_id",schemaPath:"#/properties/design_context/properties/screenshot_attachment_id/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err9]; +} +else { +vErrors.push(err9); +} +errors++; +} +} +if(data0.elements !== undefined){ +let data3 = data0.elements; +if((data3 !== null) && (!(Array.isArray(data3)))){ +const err10 = {instancePath:instancePath+"/design_context/elements",schemaPath:"#/properties/design_context/properties/elements/type",keyword:"type",params:{type: schema13.properties.design_context.properties.elements.type},message:"must be null,array"}; +if(vErrors === null){ +vErrors = [err10]; +} +else { +vErrors.push(err10); +} +errors++; +} +if(Array.isArray(data3)){ +const len0 = data3.length; +for(let i0=0; i0=", limit: 0},message:"must be >= 0"}; +if(vErrors === null){ +vErrors = [err29]; +} +else { +vErrors.push(err29); +} +errors++; +} +} +} +} +else { +const err30 = {instancePath:instancePath+"/execution_engines/" + i0+"/default_limits",schemaPath:"#/properties/execution_engines/items/properties/default_limits/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err30]; +} +else { +vErrors.push(err30); +} +errors++; +} +} +if(data1.id !== undefined){ +if(typeof data1.id !== "string"){ +const err31 = {instancePath:instancePath+"/execution_engines/" + i0+"/id",schemaPath:"#/properties/execution_engines/items/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err31]; +} +else { +vErrors.push(err31); +} +errors++; +} +} +if(data1.language !== undefined){ +if(typeof data1.language !== "string"){ +const err32 = {instancePath:instancePath+"/execution_engines/" + i0+"/language",schemaPath:"#/properties/execution_engines/items/properties/language/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err32]; +} +else { +vErrors.push(err32); +} +errors++; +} +} +if(data1.label !== undefined){ +if(typeof data1.label !== "string"){ +const err33 = {instancePath:instancePath+"/execution_engines/" + i0+"/label",schemaPath:"#/properties/execution_engines/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err33]; +} +else { +vErrors.push(err33); +} +errors++; +} +} +} +else { +const err34 = {instancePath:instancePath+"/execution_engines/" + i0,schemaPath:"#/properties/execution_engines/items/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err34]; +} +else { +vErrors.push(err34); +} +errors++; +} +} +} +} +if(data.default_execution_engine !== undefined){ +if(typeof data.default_execution_engine !== "string"){ +const err35 = {instancePath:instancePath+"/default_execution_engine",schemaPath:"#/properties/default_execution_engine/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err35]; +} +else { +vErrors.push(err35); +} +errors++; +} +} +if(data.operations !== undefined){ +let data16 = data.operations; +if((data16 !== null) && (!(Array.isArray(data16)))){ +const err36 = {instancePath:instancePath+"/operations",schemaPath:"#/properties/operations/type",keyword:"type",params:{type: schema93.properties.operations.type},message:"must be null,array"}; +if(vErrors === null){ +vErrors = [err36]; +} +else { +vErrors.push(err36); +} +errors++; +} +if(Array.isArray(data16)){ +const len2 = data16.length; +for(let i2=0; i2 32){ +const err2 = {instancePath:instancePath+"/root_ids",schemaPath:"#/properties/root_ids/maxItems",keyword:"maxItems",params:{limit: 32},message:"must NOT have more than 32 items"}; +if(vErrors === null){ +vErrors = [err2]; +} +else { +vErrors.push(err2); +} +errors++; +} +const len0 = data0.length; +for(let i0=0; i0 1){ +const indices0 = {}; +for(;i1--;){ +let item0 = data0[i1]; +if(typeof item0 !== "string"){ +continue; +} +if(typeof indices0[item0] == "number"){ +j0 = indices0[item0]; +const err5 = {instancePath:instancePath+"/root_ids",schemaPath:"#/properties/root_ids/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"}; +if(vErrors === null){ +vErrors = [err5]; +} +else { +vErrors.push(err5); +} +errors++; +break; +} +indices0[item0] = i1; +} +} +} +else { +const err6 = {instancePath:instancePath+"/root_ids",schemaPath:"#/properties/root_ids/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err6]; +} +else { +vErrors.push(err6); +} +errors++; +} +} +} +else { +const err7 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err7]; +} +else { +vErrors.push(err7); +} +errors++; +} +validate171.errors = vErrors; +return errors === 0; +} + +export const SessionSummariesResult = validate172; +const schema173 = {"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"root_id":{"type":"string"},"missing":{"type":"boolean"},"archived":{"type":"boolean"},"title":{"type":"string"},"cwd":{"type":"string"},"workspace_id":{"type":"string"},"running_agents":{"type":"string","pattern":"^-?(0|[1-9][0-9]*)$","format":"int64"},"queued_agents":{"type":"string","pattern":"^-?(0|[1-9][0-9]*)$","format":"int64"},"pending_permissions":{"type":"string","pattern":"^-?(0|[1-9][0-9]*)$","format":"int64"},"pending_questions":{"type":"string","pattern":"^-?(0|[1-9][0-9]*)$","format":"int64"},"truncated":{"type":"boolean"}},"required":["root_id","missing","archived","title","cwd","running_agents","queued_agents","pending_permissions","pending_questions","truncated"],"additionalProperties":false},"maxItems":32}},"$id":"https://whip.dev/protocol/v6/SessionSummariesResult","$schema":"http://json-schema.org/draft-07/schema#","title":"SessionSummariesResult","required":["items"],"additionalProperties":false}; + +function validate172(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){ +/*# sourceURL="https://whip.dev/protocol/v6/SessionSummariesResult" */; +let vErrors = null; +let errors = 0; +if(data && typeof data == "object" && !Array.isArray(data)){ +if(data.items === undefined){ +const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "items"},message:"must have required property '"+"items"+"'"}; +if(vErrors === null){ +vErrors = [err0]; +} +else { +vErrors.push(err0); +} +errors++; +} +for(const key0 in data){ +if(!(key0 === "items")){ +const err1 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err1]; +} +else { +vErrors.push(err1); +} +errors++; +} +} +if(data.items !== undefined){ +let data0 = data.items; +if(Array.isArray(data0)){ +if(data0.length > 32){ +const err2 = {instancePath:instancePath+"/items",schemaPath:"#/properties/items/maxItems",keyword:"maxItems",params:{limit: 32},message:"must NOT have more than 32 items"}; +if(vErrors === null){ +vErrors = [err2]; +} +else { +vErrors.push(err2); +} +errors++; +} +const len0 = data0.length; +for(let i0=0; i0 { + if (!/^-?(0|[1-9][0-9]*)$/.test(value) || value.length > 20) return false; + const number = BigInt(value); + return number >= -9223372036854775808n && number <= 9223372036854775807n; +}}}.int64; +const pattern0 = new RegExp("^-?(0|[1-9][0-9]*)$", "u"); + +function validate12(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){ +/*# sourceURL="https://whip.dev/protocol/v6/AgentInputParams" */; +let vErrors = null; +let errors = 0; +if(data && typeof data == "object" && !Array.isArray(data)){ +if(data.id === undefined){ +const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "id"},message:"must have required property '"+"id"+"'"}; +if(vErrors === null){ +vErrors = [err0]; +} +else { +vErrors.push(err0); +} +errors++; +} +if(data.text === undefined){ +const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "text"},message:"must have required property '"+"text"+"'"}; +if(vErrors === null){ +vErrors = [err1]; +} +else { +vErrors.push(err1); +} +errors++; +} +if(data.design_context !== undefined){ +let data0 = data.design_context; +if((data0 !== null) && (!(data0 && typeof data0 == "object" && !Array.isArray(data0)))){ +const err2 = {instancePath:instancePath+"/design_context",schemaPath:"#/properties/design_context/type",keyword:"type",params:{type: schema13.properties.design_context.type},message:"must be null,object"}; +if(vErrors === null){ +vErrors = [err2]; +} +else { +vErrors.push(err2); +} +errors++; +} +if(data0 && typeof data0 == "object" && !Array.isArray(data0)){ +if(data0.context_attachment_id === undefined){ +const err3 = {instancePath:instancePath+"/design_context",schemaPath:"#/properties/design_context/required",keyword:"required",params:{missingProperty: "context_attachment_id"},message:"must have required property '"+"context_attachment_id"+"'"}; +if(vErrors === null){ +vErrors = [err3]; +} +else { +vErrors.push(err3); +} +errors++; +} +if(data0.elements === undefined){ +const err4 = {instancePath:instancePath+"/design_context",schemaPath:"#/properties/design_context/required",keyword:"required",params:{missingProperty: "elements"},message:"must have required property '"+"elements"+"'"}; +if(vErrors === null){ +vErrors = [err4]; +} +else { +vErrors.push(err4); +} +errors++; +} +if(data0.element_count === undefined){ +const err5 = {instancePath:instancePath+"/design_context",schemaPath:"#/properties/design_context/required",keyword:"required",params:{missingProperty: "element_count"},message:"must have required property '"+"element_count"+"'"}; +if(vErrors === null){ +vErrors = [err5]; +} +else { +vErrors.push(err5); +} +errors++; +} +if(data0.context_attachment_id !== undefined){ +if(typeof data0.context_attachment_id !== "string"){ +const err6 = {instancePath:instancePath+"/design_context/context_attachment_id",schemaPath:"#/properties/design_context/properties/context_attachment_id/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err6]; +} +else { +vErrors.push(err6); +} +errors++; +} +} +if(data0.screenshot_attachment_id !== undefined){ +if(typeof data0.screenshot_attachment_id !== "string"){ +const err7 = {instancePath:instancePath+"/design_context/screenshot_attachment_id",schemaPath:"#/properties/design_context/properties/screenshot_attachment_id/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err7]; +} +else { +vErrors.push(err7); +} +errors++; +} +} +if(data0.elements !== undefined){ +let data3 = data0.elements; +if((data3 !== null) && (!(Array.isArray(data3)))){ +const err8 = {instancePath:instancePath+"/design_context/elements",schemaPath:"#/properties/design_context/properties/elements/type",keyword:"type",params:{type: schema13.properties.design_context.properties.elements.type},message:"must be null,array"}; +if(vErrors === null){ +vErrors = [err8]; +} +else { +vErrors.push(err8); +} +errors++; +} +if(Array.isArray(data3)){ +const len0 = data3.length; +for(let i0=0; i0=", limit: 0},message:"must be >= 0"}; +if(vErrors === null){ +vErrors = [err27]; +} +else { +vErrors.push(err27); +} +errors++; +} +} +} +} +else { +const err28 = {instancePath:instancePath+"/execution_engines/" + i0+"/default_limits",schemaPath:"#/properties/execution_engines/items/properties/default_limits/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err28]; +} +else { +vErrors.push(err28); +} +errors++; +} +} +if(data1.id !== undefined){ +if(typeof data1.id !== "string"){ +const err29 = {instancePath:instancePath+"/execution_engines/" + i0+"/id",schemaPath:"#/properties/execution_engines/items/properties/id/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err29]; +} +else { +vErrors.push(err29); +} +errors++; +} +} +if(data1.language !== undefined){ +if(typeof data1.language !== "string"){ +const err30 = {instancePath:instancePath+"/execution_engines/" + i0+"/language",schemaPath:"#/properties/execution_engines/items/properties/language/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err30]; +} +else { +vErrors.push(err30); +} +errors++; +} +} +if(data1.label !== undefined){ +if(typeof data1.label !== "string"){ +const err31 = {instancePath:instancePath+"/execution_engines/" + i0+"/label",schemaPath:"#/properties/execution_engines/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err31]; +} +else { +vErrors.push(err31); +} +errors++; +} +} +} +else { +const err32 = {instancePath:instancePath+"/execution_engines/" + i0,schemaPath:"#/properties/execution_engines/items/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err32]; +} +else { +vErrors.push(err32); +} +errors++; +} +} +} +} +if(data.default_execution_engine !== undefined){ +if(typeof data.default_execution_engine !== "string"){ +const err33 = {instancePath:instancePath+"/default_execution_engine",schemaPath:"#/properties/default_execution_engine/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err33]; +} +else { +vErrors.push(err33); +} +errors++; +} +} +if(data.operations !== undefined){ +let data16 = data.operations; +if((data16 !== null) && (!(Array.isArray(data16)))){ +const err34 = {instancePath:instancePath+"/operations",schemaPath:"#/properties/operations/type",keyword:"type",params:{type: schema93.properties.operations.type},message:"must be null,array"}; +if(vErrors === null){ +vErrors = [err34]; +} +else { +vErrors.push(err34); +} +errors++; +} +if(Array.isArray(data16)){ +const len2 = data16.length; +for(let i2=0; i2 32){ +const err1 = {instancePath:instancePath+"/root_ids",schemaPath:"#/properties/root_ids/maxItems",keyword:"maxItems",params:{limit: 32},message:"must NOT have more than 32 items"}; +if(vErrors === null){ +vErrors = [err1]; +} +else { +vErrors.push(err1); +} +errors++; +} +const len0 = data0.length; +for(let i0=0; i0 1){ +const indices0 = {}; +for(;i1--;){ +let item0 = data0[i1]; +if(typeof item0 !== "string"){ +continue; +} +if(typeof indices0[item0] == "number"){ +j0 = indices0[item0]; +const err4 = {instancePath:instancePath+"/root_ids",schemaPath:"#/properties/root_ids/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"}; +if(vErrors === null){ +vErrors = [err4]; +} +else { +vErrors.push(err4); +} +errors++; +break; +} +indices0[item0] = i1; +} +} +} +else { +const err5 = {instancePath:instancePath+"/root_ids",schemaPath:"#/properties/root_ids/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err5]; +} +else { +vErrors.push(err5); +} +errors++; +} +} +} +else { +const err6 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err6]; +} +else { +vErrors.push(err6); +} +errors++; +} +validate171.errors = vErrors; +return errors === 0; +} + +export const SessionSummariesResult = validate172; +const schema173 = {"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"root_id":{"type":"string"},"missing":{"type":"boolean"},"archived":{"type":"boolean"},"title":{"type":"string"},"cwd":{"type":"string"},"workspace_id":{"type":"string"},"running_agents":{"type":"string","pattern":"^-?(0|[1-9][0-9]*)$","format":"int64"},"queued_agents":{"type":"string","pattern":"^-?(0|[1-9][0-9]*)$","format":"int64"},"pending_permissions":{"type":"string","pattern":"^-?(0|[1-9][0-9]*)$","format":"int64"},"pending_questions":{"type":"string","pattern":"^-?(0|[1-9][0-9]*)$","format":"int64"},"truncated":{"type":"boolean"}},"required":["root_id","missing","archived","title","cwd","running_agents","queued_agents","pending_permissions","pending_questions","truncated"],"additionalProperties":true},"maxItems":32}},"$id":"https://whip.dev/protocol/v6/SessionSummariesResult","$schema":"http://json-schema.org/draft-07/schema#","title":"SessionSummariesResult","required":["items"],"additionalProperties":true}; + +function validate172(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){ +/*# sourceURL="https://whip.dev/protocol/v6/SessionSummariesResult" */; +let vErrors = null; +let errors = 0; +if(data && typeof data == "object" && !Array.isArray(data)){ +if(data.items === undefined){ +const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "items"},message:"must have required property '"+"items"+"'"}; +if(vErrors === null){ +vErrors = [err0]; +} +else { +vErrors.push(err0); +} +errors++; +} +if(data.items !== undefined){ +let data0 = data.items; +if(Array.isArray(data0)){ +if(data0.length > 32){ +const err1 = {instancePath:instancePath+"/items",schemaPath:"#/properties/items/maxItems",keyword:"maxItems",params:{limit: 32},message:"must NOT have more than 32 items"}; +if(vErrors === null){ +vErrors = [err1]; +} +else { +vErrors.push(err1); +} +errors++; +} +const len0 = data0.length; +for(let i0=0; i0 string` and +`sha256: (bytes) => Promise>` in client options. The digest +must contain exactly 32 bytes. These instance-scoped adapters cover command, +permission, subscription and upload identities/content integrity; the SDK does +not import Expo or install global polyfills. The native shell supplies compatible +WebSocket, streaming fetch, encoders and abort primitives. + +Initialize those globals before creating a client. Cancellation requires +`AbortSignal.any`, `AbortSignal.timeout`, `signal.throwIfAborted()` and a controller +that publishes `signal.reason` **before** abort listeners run. React Native 0.86's +base controller lacks reason support; Expo 57 adds composition/timeout but does +not complete that contract. The [mobile compatibility entry point](../../apps/mobile/src/runtime/polyfills.ts) +adds the missing reason/checkpoint behavior without replacing supported methods. +Expo 57's default fetch supplies streaming bodies; opting into +`EXPO_PUBLIC_USE_RN_FETCH` requires another compatible implementation for content +transfer. Scoped reads and uploads require `Response.body.getReader()` and do not +fall back to downloading an unbounded `arrayBuffer()`. + +The built-in transport passes a string URL to native WebSocket constructors. +React Native 0.86 does not report `bufferedAmount`, so the adapter uses zero when +that property is absent; browser queue measurements are retained. Frame-size and +in-flight-request limits still apply, but the native socket's queued bytes cannot +be capped from that missing measurement. A timeout/abort releases a local waiter, +not necessarily queued native bytes. Applications needing strict outbound queue +accounting must supply a transport with a reliable `bufferedAmount`. + +Permission decisions are one-shot RPCs with a distinct status namespace: + +```ts +const id = client.createId(); +// Application persists this ID and request association before the RPC. +await client.permissions.decide({ command_id: id, root_id: rootId, permission_id: requestId, allow: true }); +const outcome = await client.permissions.status(id, { signal }); +``` + +`PermissionDecisionStatus` validates command identity, the `permission.decide` +operation, lifecycle and typed success/failure result. A missing status remains +`command_not_found`; unavailable or malformed status is not absence. The helper +never resends a decision. Applications own durable decision metadata and should +refresh authoritative pending requests after deciding. + +## Optional synchronized state and React + +```ts +import { createSessionView, createSessionListView } from '@whip/sdk/state'; +import { useSessionView } from '@whip/sdk/react'; + +const view = createSessionView(client.session(rootId)); +await view.start(); +// Any number of React components can call useSessionView(view). +// Other frameworks use view.subscribe and view.getSnapshot directly. +await view.openAgent(childId); +await view.loadOlder(childId); +await view.loadCollection('agents'); +await view.dispose(); +``` + +The application owns start/dispose and shares a view between components. +Hooks only subscribe; they never start another cache or connection. Create +resources inside an owning effect or outside React rendering, and dispose that +owner's resources on cleanup. Reusing a disposed resource is an error. + +Views combine consistent snapshots, ordered subscriptions, history revisions +and paginated collections. Stale state remains visible during recovery. Root and +child presentation remains separate from committed transcript entries. Inspecting +a child never adds its transcript to another agent's model context. + +When a retained view reconnects during the same active turn and history revision, +it tries one `events.replay` page of at most 1,000 events. A complete, contiguous, +untruncated replay through the snapshot cursor preserves already observed text +and applies missed deltas and discards without duplication. An unchanged cursor +needs no replay. Expired, incomplete, failed or larger ranges fall back to the +bounded snapshot; this cannot restore an evicted prefix or a cold view's missing +active output. Completed output continues to recover through durable history. + +`snapshot.root.upcoming_schedules` is the optional host-owned projection of +unclaimed wake occurrences, ordered by `next_fire` then schedule `id`. Render it +only while `snapshot.status === 'live'`; absence means unsupported, not an empty +queue. When provided, `upcoming_schedule_count` reports the host's exact pending +total even when `omitted.upcoming_schedules` marks partial row coverage. Without +that count, never infer an exact total from retained rows. A row's +`prompt_truncated` marks a preview; the existing schedules inspector/collection +reads remain the path to full prompt evidence. + +`schedule.fired` removes the matching ID **and** occurrence slot immediately, +using the host's canonical UTC timestamp (including nanoseconds), without +waiting for admitted work to run, and the existing coalesced snapshot +refresh obtains the next recurring slot. Between fire and refresh, coverage is +marked partial and the count becomes unknown: the event cannot establish whether +a recurring or omitted occurrence has another pending slot. Backward snapshot +cursors are rejected on reconnect as well as continuous refresh; historical +schedule collection pages do not populate this projection. No polling or extra +subscription is needed. + +`snapshot.history[agentId].gaps` describes missing raw records between retained +sections as inclusive `fromSeq`/`toSeq` ranges with `pending`, `loading`, `paused` +or `error` status. Offloaded message bodies are present records, not gaps. +`throughSeq` is the known end; `nextSeq`/`hasMore` still describe older paging. +The view repairs pending gaps after resuming its stream, sharing reads per agent +and limiting an automatic pass to four 128-record / 256-KiB pages. Failed or +paused gaps remain explicit until requested or reconnected; render their controls +without treating them as a disconnected session. + +Use `await view.loadHistoryGap(agentId, gap.toSeq)` to retry/continue one page. +The upper bound stays stable as the beginning fills. Explicit reads retain the +requested page within the existing 512-record / 8-MiB bounds. If navigation evicts +newer history, `history.latestMissing` is true: call `await view.loadLatest(agentId)` +before jumping to the newest content. Keep gap state scoped to agent and revision; +never concatenate response prose or activity groups across an unresolved gap. +All history recovery uses the existing root subscription and `history.page`, with no +implicit body reads or child transcript subscriptions. + +Pass the displayed revision to `session.history.clear(revision)` just as with +rewind, so another client's destructive edit causes a conflict instead of an +unconditional clear. Transcript message `content` is a string or a content-part +array; text attachments and images use the latter. Large message bodies remain +explicit scoped references under the normal page and view byte limits. + +Presentation rows append text, reasoning and terminal deltas. Tool-call arguments +and tool output are cumulative values, so updates replace the matching row by +call ID, including interleaved calls. Snapshots use the same grouping as live +events. A row keeps its first sequence as a stable rendering key; use the root +cursor or raw subscription cursor for stream progress. + +The default view retains at most 8 MiB of serialized payload and 512 messages per +opened agent; JavaScript heap overhead is additional. Large/missing output is +explicitly marked. Call `closeAgent` when no longer inspecting a child. The +catalog view polls revisions only while observed and never opens all roots. +There are at most 16 active root subscriptions per connection. + +`executionRows(view.getSnapshot(), agentId)` from `@whip/sdk/state` projects +read-only Starlark or JavaScript cells and restart markers for one agent. It merges loaded +history with `snapshot.executions`, which the existing session subscription +maintains. Calls retain stable keys across commit; cumulative arguments/output +replace earlier values; repeated host calls keep separate event identities. +`ExecutionCell` exposes code, output, result/error, status, executionEngine, +language, hasValue, optional Starlark steps or QuickJS quickjsJobs, scoped body +references and optional client-observed times. Recorded outcomes without enough +evidence are marked unknown, and historical durations are not invented. +Legacy Starlark result bodies remain readable. Format 2 uses `has_value`, so +an explicit JavaScript `null` remains distinct from no result, and completion +does not depend on Starlark steps. Numeric tags and bounded previews retain their +exact text. Engine-specific metrics are diagnostics, not comparable work units. + +The initialization snapshot advertises `execution_engines` and +`default_execution_engine`. Session creation accepts `execution_engine` as +`starlark` or `quickjs`; root metadata is authoritative thereafter and every +descendant inherits that engine. Persist an explicit selection alongside a +creation retry journal so changing host defaults cannot change a missing request. +`configuration.update({revision, default_execution_engine: 'quickjs'})` changes +future-session defaults only. + +Protocol 5.1 hosts emit `stream.cell.host.started` before dispatch and retain +`stream.cell.host` for completion. `ExecutionHostCall.status` moves from running +to completed/failed/cancelled; missing terminal evidence becomes unknown or +interrupted. Each invocation is scoped to the cell, agent and turn. A host failure +does not imply the cell failed, and a completed host invocation may have returned +a background-process handle. Snapshot replay restores confirmed active operations; +an evicted start is never guessed onto a newer cell with the same model call ID. +Legacy completion-only events remain supported. Older SDK versions keep ordered +delivery but mark the new event kind as unavailable detail until upgraded. + +Agent records also expose optional `last_turn` metadata, refreshed through the +existing lifecycle/snapshot flow. It records the latest turn's status and error +even when there are no execution cells. Agent lifecycle remains independent: +`status: 'idle'` can coexist with `last_turn.status: 'failed'`. A new turn replaces +the previous outcome. Error previews are capped at 4 KiB; `error_details` is a +scoped content handle for explicitly reading the full message. Missing summaries +from compatible older hosts mean unknown, not success. Keep all agent/session +IDs opaque: new IDs use 20 lowercase base32 characters, while legacy IDs and +existing links remain valid. Hierarchy comes from `root_id` and `parent_id`. + +Queue controls act on an already accepted input; they do not submit a new prompt: + +```ts +if (client.supports('runtime', 'inbox.steer') && client.supports('runtime', 'inbox.remove')) { + await session.inbox.steer(agentId, inboxSeq, expectedTurnId).result(); + // Or remove it while still waiting; this never cancels a running turn. + await session.inbox.remove(agentId, inboxSeq).result(); +} +``` + +The result distinguishes `steering`, `removed`, `already_started`, +`already_removed`, and `turn_ended`. Admission alone is not mutation success. +Steering preserves the original prompt, attachments, command identity and inbox +sequence; it delivers at the expected turn's next safe boundary. If that turn +ends first, the entry keeps its ordinary queue position. Direct `session.steer` +remains supported. Root input commands removed while queued settle as cancelled +with failure kind `queue_removed`; completed child admission commands stay complete. + +`inboxItems(view.getSnapshot(), agentId)` from `@whip/sdk/state` returns scoped +rows with freshness and additional-page availability. Optional inbox metadata +includes client origin/correlation, bounded text/attachment preview, pending +steer target and delivery sequence. Snapshot omissions retain bounded unverified +evidence; do not enable controls on stale rows or infer removal from absence. +Use `view.loadCollection('inbox')` and `{ more: true }` for explicit paging. +All retained evidence shares the existing session byte budget and subscription. + +Supplemental execution evidence is capped at 256 entries per root, 128 host calls +per cell and 1 MiB **inside** the view's payload budget. Truncation is explicit. +History revisions and root changes invalidate incompatible observations. When +a child was observed before its history was loaded, ambiguous reused call IDs +remain separate observed evidence instead of borrowing an older result. Durable +transcripts recover code/results; host traces and restart details observed before +the client attached may be unavailable. This API does not add a subscription, +fetch all history or load every child's transcript. Use `loadOlder(agentId)` and +scoped content reads explicitly when needed. + +`await view.loadTrace()` reads durable span pages for the root, including inactive +persisted conversations, independently of root snapshot/history refreshes. +Concurrent callers share the pending read. Each call loads at most eight pages; +`state.trace.hasMore` indicates explicit continuation. `loading` settles on +completion, error, page-budget exhaustion or disconnect; retry resumes at the +last durable page cursor. Call again on reconnect to catch up on missed span +events. Empty successful reads set `loaded` without fabricating spans for +conversations that predate tracing. Live span events share the root subscription +and do not advance the durable page cursor. + +For scripts, `await client.events.subscribe(rootId, cursor)` gives a single +bounded async iterator. Install a view or read a snapshot to obtain a cursor. +Expired cursors, sequence gaps, and slow consumers fail explicitly; reacquire a +snapshot or replay instead of continuing an incomplete stream. Unknown future +event kinds are marked `unknown: true`. + +## Model accounting + +Protocol v4.1 adds optional `RootSnapshot.accounting` for the entire session tree. +`SessionView` updates it from scoped, revision-ordered `stream.accounting` events, +without adding accounting rows to the conversation or execution evidence. Model +attempt lifecycle events refresh budgets through the existing coalesced snapshot +path. Reconnect restores the host snapshot; no separate polling is required. + +Cost counters are decimal strings in microdollars. `reported_cost_micros` is the +provider's charge (including zero); `estimated_cost_micros` uses saved catalog +rates. `unknown_cost_calls` and `estimated_calls` respectively count unknown +cost and incomplete token usage, and can overlap. `pending_calls` counts active +requests. Never treat absent accounting as a known zero or add subtree totals +to their descendants. `session.agents.inspect(id)` returns optional own-agent +accounting under `result.accounting`, distinguished by `scope: 'agent'`. + +## Content and human approvals + +```ts +const reference = await client.upload(bytes, { rootId, mediaType: 'text/plain' }); +const text = await reference.readText({ maxBytes: 1 << 20, signal }); +const stored = client.content(handle, { rootId, agentId }); +const value = await stored.readJSON({ maxBytes: 512 << 10 }); // unknown; validate before use +``` + +Content reads verify association, exact size and SHA-256. WebSocket clients use +HTTP; Unix clients use existing bounded RPC chunks. Uploads grant content to the +root; the daemon must explicitly grant it to a child. Large command outcomes +stay content references rather than causing implicit downloads. Upload failure +never submits a prompt or attaches a partial file. Browser WebCrypto requires +a secure context such as localhost or HTTPS. + +Connected clients can approve or deny requests directly: + +```ts +await client.permissions.decide({ root_id: rootId, permission_id: permissionId, allow: true }); +await client.permissions.setMode(rootId, false).result(); +``` + +Permission decisions require no signer, enrollment, or authentication. Both human +and automation client kinds can answer requests. The daemon validates the +permission's root and enforces tool permissions and delegated authority. Decisions are sent once; after an uncertain acknowledgement, inspect +pending state before explicitly retrying with the original `command_id`. +Permission-mode changes use ordinary durable command handles. Provider secrets +stay outside recovery storage and logging; the SDK does not log payloads. + +Provider/configuration helpers (`client.providers`, `client.configuration`) call +host services. Login status/list allow reconnect; restart interrupts incomplete +flows. Configuration updates require the last read revision and surface conflicts. +`providers.list()` reads credential-free connection/source metadata and a +configuration revision without model discovery. Optional `category`, `family`, +and `key_url` fields describe known-provider presentation. `key_source: "env_file"` +or `"key_file"` identifies a named file source; `environment_variable` and +`credential_path` expose only the reference name and configured path. +`providers.discover({ model?, provider? })` rereads host sources, persists missing +provider references without copying keys, and returns the same inventory plus an +optional `discovery_error`. It preserves explicit/disabled providers and defaults, +and does not fetch model catalogs. Use it on setup-open or explicit Refresh; +regular inventory polling stays read-only. Check `client.supports('rpc', +'provider.discover')` before using it with older hosts. Discovery is ephemeral and +never enters command recovery. `providers.disconnect({ provider, +revision })` removes WHIP-owned credentials and disables the route; environment +or external credentials instead use `configuration.update({ revision, +disabled_providers })`. These operations preserve model aliases and defaults. +Catalogs reuse their existing freshness metadata; `providers.catalogs({ refresh: +true })` requests an explicit upstream refresh. Unavailable provider descriptors +must be excluded from selectable model routes. +`providers.get(provider)` reads a redacted editable definition, credential source, +configured aliases and removal blockers. `providers.create({...})` and +`providers.update({...})` accept the expected revision, endpoint metadata and an +explicit credential mode (`api_key`, `environment`, `none`, or edit-only `keep`). +They can include a `manual_model` and explicit `allow_unverified` intent when +model discovery is unavailable. `providers.remove({ provider, revision })` +removes unused custom definitions; built-ins and referenced routes are protected. +These operations use host configuration files and do not enter session recovery +storage. After an uncertain save, reread the provider ID and revision; never +automatically replay a credential request or turn a duplicate create into update. +`client.providers.validate({ name, base_url, key })` checks a candidate key without +saving it. `setKey` returns an optional `discovery` result distinguishing loaded +catalogs from an unverified connection using a bundled or public catalog; normal +configuration reads omit this transient result. Neither outcome asserts that an +inference request succeeded. `rotateKey('inference')` +rotates the execution host's machine key. Both are ephemeral and sent once; +inspect provider status after an uncertain acknowledgement before retrying. +`session.terminalInput` is ephemeral and is never automatically retried. + +## Author, serve, run + +`@whip/sdk/agents` authors the agents a daemon runs. A definition is data: what +the agent is told, which host modules and capabilities it receives, model and +compaction defaults, MCP servers, custom tools, hooks, named children, an +output contract, and surface flags. The daemon validates it, stores it under a +content revision, and sessions pin that revision. The program below is +`examples/agents/support-triage.ts`, which has a unit test against the scripted +daemon and a live acceptance beside it. + +```ts +import { createWhipClient } from '@whip/sdk'; +import { defineAgent, tool } from '@whip/sdk/agents'; +import { z } from 'zod'; + +const lookupTicket = tool({ + name: 'lookup_ticket', + description: 'Fetch a support ticket by id', + input: z.object({ id: z.string() }), // any Standard JSON Schema: zod 4.2+, ArkType, Valibot + output: z.object({ id: z.string(), title: z.string(), status: z.enum(['open', 'closed']) }), + execute: async ({ id }, ctx) => { // id: string, inferred; the return must match output + ctx.progress(`looking up ${id}`); // streamed to the session while it runs + return tickets.get(id) ?? fail(`ticket ${id} not found`); + }, + timeoutMs: 30_000, // default 5 minutes, ceiling 15 +}); + +const support = defineAgent({ + id: 'support-triage', + instructions: { persona: 'You triage support tickets.', rules: 'Look tickets up before describing them.' }, + modules: ['context', 'files', 'user', 'agents'], + capabilities: ['read'], + tools: [lookupTicket], + output: z.object({ ticket: z.string(), summary: z.string(), escalatedTo: z.string().nullable() }), + children: { researcher: { modules: ['context'], tools: ['lookup_ticket'], report: 'message' } }, + hooks: { + beforeTool: async ({ operation, arguments: args }) => { // every host operation; return nothing to allow + if (operation === 'files.read' && String(args.path).endsWith('.env')) return { arguments: { ...args, path: `${args.path}.example` }, reason: 'secrets are redacted' }; + }, + beforeSpawn: async ({ spawn, resolved }) => { // the request and what it resolved to + if (resolved.capabilities?.includes('shell')) return { decision: 'deny', reason: 'children may not hold shell' }; + }, + turnStart: { optional: true, handler: async () => ({ context: `On call: ${await roster.current()}` }) }, + }, +}); + +const client = createWhipClient({ endpoint: 'http://127.0.0.1:8080', clientId: 'support-triage' }); +await client.connect(); +const runtime = await client.agents.serve(support); // register, bind tools and hooks, serve until close() +const session = await runtime.sessions.create({ cwd }); // pinned to this revision; refuses any other + +const turn = session.run('Triage ticket 42'); +for await (const event of turn) { + switch (event.type) { + case 'text': process.stdout.write(event.delta); break; + case 'host': console.log(`\n→ ${event.operation} ${event.status}`); break; + case 'hook': console.log(`\n[hook] ${event.decision} ${event.operation}: ${event.reason}`); break; + case 'question': await event.answer([event.options[0].label]); break; + case 'permission': await event.allow(); break; + } +} +const result = await turn.result(); // TurnResult<{ ticket, summary, escalatedTo }> +if (result.status === 'succeeded') console.log(result.output.summary); +else console.error(result.failure.message); +runtime.close(); +``` + +**Tools.** `tool({...})` takes one options object. A Standard JSON Schema +`input` infers the handler's argument type and derives the wire schema at +draft 2020-12; raw JSON Schema is accepted and types `unknown`. The daemon +validates every call against the input schema before the handler runs; the +library's own validation also runs locally, for refinements JSON Schema cannot +express and to apply defaults. `output` types the return value at compile time, +is validated before the result is posted, and travels to the daemon as +`output_schema`, where the guide shows the model what the tool returns and a +result from any executor that does not match is rejected. Handlers receive the +invocation id (reuse it to make side effects idempotent), root, agent, and +turn ids, the deadline, an `AbortSignal`, and `progress`. Large results reach +the cell as content handles. The SDK adds no schema dependency; the interfaces +are mirrored as types. + +**Serving.** `client.agents.serve(agent)` registers the definition, binds this +connection as the executor for its revision, and answers tool invocations and +hook events until `close()`. It returns an `AgentRuntime`: `runtime.sessions +.create(params)` and `.open(rootId)` hand out sessions pinned to that revision +and refuse any other, so a runtime never drives a session whose tools it does +not serve. A call made while no executor is bound fails after a short wait with +an error the model reads; the executor re-binds after a reconnect and drains +`executor.pending`. `client.agents.register/get/list` manage definitions +without serving. + +**Hooks.** `beforeTool` runs before every host operation the agent's cells +call (`operations: [...]` narrows it), `beforeSpawn` after a spawn request is +parsed and resolved, `turnStart` at the start of each turn to contribute +ephemeral context. Every result field is optional; returning nothing allows +unchanged. A rewrite takes the validated path the original arguments would, +so it cannot widen. A hook is required by default: unanswered or throwing, it +denies with an error the model reads; `{ optional: true, handler }` proceeds +with a notice instead. Hooks only narrow and never bypass the permission mode. + +**Turns.** `session.run(input)` submits one turn and returns a `Turn`: an +async iterable of typed events scoped to that turn (`text`, `reasoning`, +`cell`, `host`, `progress`, `hook`, `question`, `permission`, `child`, +`notice`, `usage`, `end`, and `raw` for anything else) plus `result()`, +`text()`, `cancel()`, and `turnId`. `question` and `permission` events carry +`answer`/`dismiss` and `allow`/`deny`, which post once. `session.prompts()` +rebuilds the open prompts from a snapshot for a client that attached +mid-turn. The subscription starts before the command so nothing is missed; the +command outcome is authoritative and the stream is best effort: a consumer +that falls behind fails the iterable, never the result. `includeChildren` +adds the events of agents spawned during the turn. + +**Output contracts.** `defineAgent({ output })` states an object schema the +final message must match. The guide tells the model; the daemon validates the +message, returns one mismatch for correction, and fails the turn on a second +with `output_invalid`. The validated value is `result.output`, typed by the +schema for sessions created through the runtime. + +## Testing + +`@whip/sdk/testing` exports `scriptedDaemon()`: the in-memory transport the +SDK's own tests use, with a reply table (`daemon.reply(method, handler)`), +`daemon.emit(rootId, kind, payload)` for journal events, `serveSessions()` to +answer registration, binding, snapshot, subscribe, submit, status, and +decisions with defaults, and `daemon.turn(rootId, script)` to play a whole +turn so a consumer of `session.run` is tested without a daemon: + +```ts +import { scriptedDaemon } from '@whip/sdk/testing'; +const daemon = scriptedDaemon().serveSessions(); +const client = createWhipClient({ endpoint: daemon.factory, clientId: 'test' }); +await client.connect(); +void daemon.turn('root', { steps: [{ text: 'Hello' }, { question: { id: 'q', question: 'Ok?', options: [{ label: 'Yes' }] } }], text: 'Hello', output: { ok: true } }); +``` + +`@whip/sdk/testing/node` exports `liveDaemon()` (also `startFixture`): the +integration test binary serving a real daemon from an isolated home, behind a +scripted model, as the SDK acceptance and `examples/agents` use. Node only; it +builds and spawns Go. + +## React example and validation + +Host bootstrap reads do not create sessions: + +```ts +const directories = await client.host.directories({ path: '~/projects' }); +const providers = await client.providers.catalogs(); +const themes = await client.host.themes.list(); +const colors = await client.host.themes.resolve('opencode'); +const attention = await client.host.attention(); +``` + +Directory and attention results have explicit pagination and truncation. Attention +is a live index: refresh from its first page for newly active roots. Session +catalog searches use `client.sessions.list({ search })`; group displayed paths +using `workspace_id` scoped to the host runtime, never the shortened `cwd` label. +The catalog defaults to active sessions; pass `status: 'archived'` or `'all'` +for other views. Cursors belong to their search and status. Use +`client.sessions.get(rootId)` for the full title, working directory, archive flag, +and decimal history revision without opening a transcript or subscription. +`session.archive(true)` and `session.archive(false)` are durable commands for +archive and restore. Archiving changes catalog visibility while open session +views, execution, history, and pending human requests remain available. +Custom theme JSON can be validated/resolved with `host.themes.resolveJSON(json)`; +selected themes remain client preferences. + +`session.mailbox.list({ agent_id, status })` and `session.mailbox.read(id, agentId)` +inspect inter-agent mail without changing delivery state or model context. +Bodies are bounded text or scoped content references. This is separate from the +execution command inbox, and message revisions remain decimal strings. + +Start a compatible daemon, then keep a gateway running in a separate terminal: + +```sh +whip daemon start +WHIP_ALLOWED_ORIGINS=http://localhost:3000 whip web --no-open +``` + +Use the printed gateway origin. `--no-open` runs in the foreground; it does not +print and exit. The gateway never starts or restarts the daemon. Alternatively, +`WHIP_NETWORK=1 WHIP_ALLOWED_ORIGINS=http://localhost:3000 whip daemon start` +opts a new daemon launch into the same gateway as an owned child; only that ready +managed endpoint appears in `whip daemon status --json`. `WHIP_LISTEN` alone +is not an opt-in, and `WHIP_NETWORK=0` does not forbid explicit `whip web`. + +The gateway defaults to `127.0.0.1:4444`, falling back to an ephemeral loopback +port only on address-in-use. Explicit binds never silently move. Its upstream +socket requires the daemon's acknowledged `network-client-v1` capability; old +daemons fail closed rather than treating browser connections as local clients. +Host/Origin checks remain exact, not authentication; remote access requires a +trusted network or authenticated proxy. The whipcode distribution uses +`WHIPCODE_*`. See [web setup](../../docs/web-app.md). + +Start the example with: + +```sh +npm run build +npm start -w @whip/client-example +``` + +Open http://localhost:3000 and enter the gateway origin. The example supplies +its own metadata-only localStorage recovery adapter and sessionStorage drafts. +Pending permissions expose Allow once and Deny whenever the client is connected. +This remains a minimal example of the SDK primitives. + +```sh +WHIP_SDK_RACE=1 npm run acceptance +npx playwright install chromium firefox +npm run test:browser +npm run test:example +npm run test:package +task check +task acceptance +``` + +Tests start isolated fake-provider daemons, never the user's active daemon. +The browser run loads the actual built SDK under strict CSP and checks React +StrictMode; on macOS it also opens actual Safari. Set +`WHIP_SDK_BROWSERS=chromium,firefox` for a Linux CI run. No Safari setting is changed. + +The registry tests cover all registered methods using Go-produced fixtures; +daemon acceptance tests cover execution and recovery behavior over both +transports. Measurements and completed release checks are recorded in +[`typescript-client/README.md`](../../.ai-docs/plans/typescript-client/README.md). diff --git a/vendor/@whip/sdk/dist/agents.d.ts b/vendor/@whip/sdk/dist/agents.d.ts new file mode 100644 index 0000000..3e701b9 --- /dev/null +++ b/vendor/@whip/sdk/dist/agents.d.ts @@ -0,0 +1,242 @@ +import type { CreateSessionParams, DefinitionList, SubmitPayload, DefinitionRecord, DefinitionRegisterParams, DefinitionRegisterResult, HookInvokeParams } from '@whip/protocol'; +import type { CallOptions, WhipClient } from './client.js'; +import type { CommandOptions } from './command.js'; +import { Session } from './session.js'; +import { Turn, type RunOptions } from './turn.js'; +import { type InferInput, type InferOutput, type Schema } from './schema.js'; +export type { InferInput, InferOutput, JsonSchema, Schema, StandardSchemaWithJSON } from './schema.js'; +/** The wire document the daemon validates and stores. Generated from the Go definition. */ +export type Definition = DefinitionRegisterParams['definition']; +export type ToolSpec = NonNullable[number]; +export type ChildDefinition = Definition['children'][string]; +/** Context handed to a tool handler for one invocation. */ +export interface ToolContext { + /** The daemon's ledger operation id; reuse it to make side effects idempotent. */ + readonly invocationId: string; + readonly rootId: string; + readonly agentId: string; + readonly turnId: string; + /** Unix milliseconds after which the daemon settles the call as timed out. */ + readonly deadline: number; + /** Aborted on cancellation, deadline, executor close, or disconnect. */ + readonly signal: AbortSignal; + /** Report intermediate output; the daemon streams it to the session. */ + progress(text: string): void; +} +/** What execute must return: the output schema's type when one is declared, otherwise anything JSON. */ +export type ToolReturn = O extends Schema ? InferOutput : unknown; +export type ToolHandler = (input: InferInput, context: ToolContext) => ToolReturn | Promise>; +export interface ToolOptions { + /** Lowercase identifier; the cell calls tools.. */ + name: string; + description: string; + /** Keyword arguments the cell passes. A Standard JSON Schema (zod, ArkType, Valibot) infers the handler's input type; raw JSON Schema types it unknown. */ + input: I; + /** What execute returns. Typed at compile time and validated locally before the result is posted. */ + output?: O; + execute(input: InferInput, context: ToolContext): ToolReturn | Promise>; + /** Default 5 minutes, ceiling 15. */ + timeoutMs?: number; + /** Run the schemas' own validation around execute (default true); raw JSON Schema has none. */ + validate?: boolean; +} +export interface ToolDefinition { + readonly spec: ToolSpec; + readonly input: I; + readonly output: O | undefined; + readonly validate: boolean; + execute(input: InferInput, context: ToolContext): ToolReturn | Promise>; +} +/** Wire shapes a before_spawn hook sees: the request as the model wrote it and what it resolved to. */ +export type SpawnRequest = NonNullable['request']; +export type ResolvedChild = NonNullable['resolved']; +export type HookName = 'before_tool' | 'before_spawn' | 'turn_start'; +/** Identity every hook event carries. */ +export interface HookContext { + readonly invocationId: string; + readonly rootId: string; + readonly agentId: string; + readonly turnId: string; + /** The session's permission mode; hooks only narrow it. */ + readonly permissionMode: string; + /** Unix milliseconds after which the daemon applies the hook's required or optional rule. */ + readonly deadline: number; + /** Aborted on cancellation, deadline, executor close, or disconnect. */ + readonly signal: AbortSignal; +} +export interface BeforeToolEvent extends HookContext { + /** module.operation, such as shell.run or tools.lookup_ticket. */ + readonly operation: string; + readonly arguments: Record; +} +/** Every field is optional; returning nothing allows the operation unchanged. */ +export interface BeforeToolResult { + decision?: 'allow' | 'deny'; + reason?: string; + arguments?: Record; +} +export interface BeforeSpawnEvent extends HookContext { + readonly spawn: SpawnRequest; + readonly resolved: ResolvedChild; +} +export interface BeforeSpawnResult { + decision?: 'allow' | 'deny'; + reason?: string; + spawn?: SpawnRequest; +} +export interface TurnStartEvent extends HookContext { + readonly input: string; +} +export interface TurnStartResult { + context?: string; +} +export type HookHandler = (event: E) => Promise | R | void; +export interface HookOptions { + /** Proceed with a notice when unanswered or failing, instead of denying. */ + optional?: boolean; + /** Default 30 seconds, ceiling 60. */ + timeoutMs?: number; +} +export interface HooksInput { + /** Runs before every host operation the agent's cells call; `operations` narrows it to module.operation names. */ + beforeTool?: HookHandler | (HookOptions & { + handler: HookHandler; + operations?: string[]; + }); + /** Runs after a spawn request is parsed and resolved, before the child is admitted. */ + beforeSpawn?: HookHandler | (HookOptions & { + handler: HookHandler; + }); + /** Contributes ephemeral context to each turn; it never gates. */ + turnStart?: HookHandler | (HookOptions & { + handler: HookHandler; + }); +} +export interface HookHandlers { + readonly before_tool?: HookHandler; + readonly before_spawn?: HookHandler; + readonly turn_start?: HookHandler; +} +export interface InstructionsInput { + persona?: string; + rules?: string; + /** Project instruction files read along the authorized directory chain. Omit to disable discovery. */ + projectFiles?: string[]; + skillDiscovery?: boolean; + standingInstructions?: boolean; +} +export interface ModelInput { + model?: string; + provider?: string; + effort?: string; +} +export interface CompactionInput { + model?: string; + provider?: string; + threshold?: number; +} +export interface ChildInput { + instructions?: InstructionsInput; + modules?: string[]; + capabilities?: string[]; + tools?: string[]; + model?: ModelInput; + budgets?: Record; + report?: 'notice' | 'inline' | 'message'; + /** Replaces the parent's output contract for this child. */ + output?: Schema; +} +/** The turn result type an agent's output contract implies: the schema's type, or unknown without one. */ +export type AgentOutput = O extends Schema ? InferOutput : unknown; +export interface AgentInput { + /** Lowercase id, letters, digits and hyphens; must not shadow a built-in. */ + id: string; + instructions?: InstructionsInput; + /** Built-in host modules the model may call. */ + modules: string[]; + capabilities?: string[]; + model?: ModelInput; + compaction?: CompactionInput; + /** Named MCP servers from host configuration; omit for every configured server. Requires the mcp capability. */ + mcp?: { + servers?: string[]; + }; + tools?: readonly ToolDefinition[]; + children?: Record; + surface?: { + autoTitle?: boolean; + goalLoop?: boolean; + }; + hooks?: HooksInput; + /** What a turn returns: an object schema the daemon enforces on the final message. It types the turn result. */ + output?: O; +} +/** An authored agent: the document the daemon stores plus the tools and hooks an executor serves. */ +export interface AgentDefinition { + readonly document: Definition; + /** Tool definitions by name; serve runs their execute and validates around it. */ + readonly handlers: ReadonlyMap; + readonly hooks: HookHandlers; + /** Carries the output contract's type; never set at runtime. */ + readonly outputType?: Output; +} +/** + * Declare one custom tool. The input schema's JSON Schema goes to the daemon, + * which validates every call against it before the handler runs; the output + * schema types execute's return and is checked before the result is posted. + */ +export declare function tool(options: ToolOptions): ToolDefinition; +/** + * Build the canonical definition document from authoring input. The daemon is + * the authority on validation; this only rejects shapes that cannot be sent. + */ +export declare function defineAgent(input: AgentInput): AgentDefinition>; +export interface ServeOptions { + signal?: AbortSignal; +} +/** Session creation on a served definition; the definition and its revision come from the runtime. */ +export type RuntimeSessionParams = Pick & Partial>; +/** A session on a served definition: run() returns a turn typed by the agent's output contract. */ +export declare class AgentSession extends Session { + run(input: string | SubmitPayload, options?: RunOptions): Turn; +} +/** + * A served agent: this process answers its tools and hooks for one definition + * revision, and the sessions created here pin that revision, so a runtime + * never drives a session whose tools it does not serve. + */ +export interface AgentRuntime { + readonly definition: string; + readonly revision: string; + /** Current lease generation; a reconnect re-binds and changes it. */ + readonly generation: string; + /** Tool and hook handlers currently running. */ + readonly active: number; + readonly sessions: { + /** Create a session on this definition and return it once the daemon has admitted it and it pins this revision. */ + create(params: RuntimeSessionParams, options?: CommandOptions): Promise>; + /** Open an existing session after checking that it pins this definition and revision. */ + open(rootId: string, options?: CallOptions): Promise>; + }; + /** Stop serving. Running handlers are aborted; later invocations fail fast. Sessions outlive the runtime. */ + close(): void; + /** Settles when serving stops. */ + readonly done: Promise; +} +/** The pre-runtime name; kept as an alias for one release. */ +export type Executor = AgentRuntime; +/** Registry operations. Registration is idempotent on content: the same document yields the same revision. */ +export declare class Agents { + private readonly client; + constructor(client: WhipClient); + /** + * Register the agent, bind this connection as its executor, and run its tool + * handlers until close(). Bind before creating sessions: a tool call with no + * executor fails after a short wait. The executor re-binds after a reconnect + * and drains invocations that were pending for it. + */ + serve(agent: AgentDefinition, options?: ServeOptions): Promise>; + register(agent: AgentDefinition | Definition, options?: CallOptions): Promise; + get(id: string, revision?: string, options?: CallOptions): Promise; + list(options?: CallOptions): Promise; +} diff --git a/vendor/@whip/sdk/dist/agents.js b/vendor/@whip/sdk/dist/agents.js new file mode 100644 index 0000000..98dba0d --- /dev/null +++ b/vendor/@whip/sdk/dist/agents.js @@ -0,0 +1,381 @@ +import { Session } from './session.js'; +import { Turn } from './turn.js'; +import { WhipError, abortError, asError } from './errors.js'; +import { describesObject, formatIssues, isStandardSchema, toJsonSchema, validateWith } from './schema.js'; +/** + * Declare one custom tool. The input schema's JSON Schema goes to the daemon, + * which validates every call against it before the handler runs; the output + * schema types execute's return and is checked before the result is posted. + */ +export function tool(options) { + const { name, description, input, output, execute, timeoutMs, validate = true } = options; + if (typeof name !== 'string' || !name.trim()) + throw new TypeError('tool name is required'); + if (typeof description !== 'string') + throw new TypeError(`tool ${name}: description is required`); + if (typeof execute !== 'function') + throw new TypeError(`tool ${name}: execute is required`); + if (timeoutMs !== undefined && (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0)) + throw new TypeError(`tool ${name}: timeoutMs must be a non-negative integer`); + const inputSchema = toJsonSchema(input, 'input', `tool ${name} input`); + if (!describesObject(inputSchema)) + throw new TypeError(`tool ${name}: input schema must describe an object; the cell passes keyword arguments`); + const outputSchema = output === undefined ? null : toJsonSchema(output, 'output', `tool ${name} output`); + return { spec: { name, description, input_schema: inputSchema, output_schema: outputSchema, timeout_millis: timeoutMs ?? 0 }, input, output, validate, execute }; +} +/** + * Build the canonical definition document from authoring input. The daemon is + * the authority on validation; this only rejects shapes that cannot be sent. + */ +export function defineAgent(input) { + if (!input.id.trim()) + throw new TypeError('agent id is required'); + const contract = (schema, subject) => { + if (schema === undefined) + return null; + const derived = toJsonSchema(schema, 'output', subject); + if (!describesObject(derived)) + throw new TypeError(`${subject}: output contract must describe an object`); + return derived; + }; + if (!Array.isArray(input.modules) || input.modules.length === 0) + throw new TypeError(`agent ${input.id}: select at least one host module`); + const handlers = new Map(); + const tools = []; + for (const definition of input.tools ?? []) { + if (handlers.has(definition.spec.name)) + throw new TypeError(`agent ${input.id}: tool ${definition.spec.name} is declared twice`); + handlers.set(definition.spec.name, definition); + tools.push(definition.spec); + } + const children = {}; + for (const [name, child] of Object.entries(input.children ?? {})) { + children[name] = { + instructions: child.instructions ? instructions(child.instructions) : null, + modules: list(child.modules), capabilities: list(child.capabilities), tools: list(child.tools), + model: model(child.model), budgets: { ...(child.budgets ?? {}) }, report: child.report ?? '', + output: contract(child.output, `agent ${input.id} child ${name} output`), + }; + } + const hooks = {}; + const hookSpecs = { before_tool: null, before_spawn: null, turn_start: null }; + const declare = (name, value) => { + if (!value) + return; + const options = typeof value === 'function' ? {} : value; + const handler = typeof value === 'function' ? value : value.handler; + if (typeof handler !== 'function') + throw new TypeError(`agent ${input.id}: hook ${name} needs a handler`); + if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 0)) + throw new TypeError(`agent ${input.id}: hook ${name} timeoutMs must be a non-negative integer`); + if (options.operations !== undefined && name !== 'before_tool') + throw new TypeError(`agent ${input.id}: only before_tool accepts operations`); + hooks[name] = handler; + hookSpecs[name] = { operations: list(options.operations), optional: options.optional ?? false, timeout_millis: options.timeoutMs ?? 0 }; + }; + declare('before_tool', input.hooks?.beforeTool); + declare('before_spawn', input.hooks?.beforeSpawn); + declare('turn_start', input.hooks?.turnStart); + const document = { + id: input.id, + instructions: instructions(input.instructions ?? {}), + modules: [...input.modules], + capabilities: list(input.capabilities), + model: model(input.model), + compaction: { model: input.compaction?.model ?? '', provider: input.compaction?.provider ?? '', threshold: input.compaction?.threshold ?? 0 }, + // The daemon distinguishes null (every host server) from [] (no servers); + // an explicit empty allowlist must not widen to everything. + mcp: { servers: input.mcp?.servers === undefined ? null : [...input.mcp.servers] }, + tools: tools.length > 0 ? tools : null, + output: contract(input.output, `agent ${input.id} output`), + children, + surface: { auto_title: input.surface?.autoTitle ?? true, goal_loop: input.surface?.goalLoop ?? false }, + hooks: Object.keys(hooks).length > 0 ? hookSpecs : null, + }; + return { document: Object.freeze(document), handlers, hooks: Object.freeze(hooks) }; +} +/** Turn a handler's return value into the optional-field hook reply. */ +function hookReply(result) { + if (!result || typeof result !== 'object') + return {}; + const value = result; + const reply = {}; + if (value.decision) + reply.decision = value.decision; + if (value.reason) + reply.reason = value.reason; + if (value.arguments) + reply.arguments = value.arguments; + if (value.spawn) + reply.spawn = value.spawn; + if (value.context) + reply.context = value.context; + return reply; +} +function instructions(input) { + return { + persona: input.persona ?? '', rules: input.rules ?? '', project_files: list(input.projectFiles), + skill_discovery: input.skillDiscovery ?? false, standing_instructions: input.standingInstructions ?? false, + }; +} +function model(input) { + return { model: input?.model ?? '', provider: input?.provider ?? '', effort: input?.effort ?? '' }; +} +/** Absent or empty lists are null in the canonical document. */ +function list(values) { + return values && values.length > 0 ? [...values] : null; +} +/** A session on a served definition: run() returns a turn typed by the agent's output contract. */ +export class AgentSession extends Session { + run(input, options = {}) { + return new Turn(this, typeof input === 'string' ? { text: input } : input, options); + } +} +/** Registry operations. Registration is idempotent on content: the same document yields the same revision. */ +export class Agents { + client; + constructor(client) { + this.client = client; + } + /** + * Register the agent, bind this connection as its executor, and run its tool + * handlers until close(). Bind before creating sessions: a tool call with no + * executor fails after a short wait. The executor re-binds after a reconnect + * and drains invocations that were pending for it. + */ + async serve(agent, options = {}) { + const hookNames = ['before_tool', 'before_spawn', 'turn_start'].filter(name => agent.hooks?.[name]); + if (agent.handlers.size === 0 && hookNames.length === 0) + throw new TypeError(`agent ${agent.document.id} declares no tools or hooks to serve`); + options.signal?.throwIfAborted(); + const client = this.client; + const registered = await this.register(agent, options); + const definition = agent.document.id; + const revision = registered.revision; + const tools = [...agent.handlers.keys()]; + const running = new Map(); + let generation = ''; + let closed = false; + let resolveDone; + const done = new Promise(resolve => { resolveDone = resolve; }); + const bind = async () => { + const lease = await client.call('executor.bind', { definition, revision, tools, ...(hookNames.length > 0 ? { hooks: hookNames } : {}) }, options); + generation = lease.generation; + }; + const settleHook = async (invocation, body) => { + try { + await client.call('hook.result', { invocation_id: invocation.invocation_id, generation: invocation.generation, ...body }); + } + catch { /* the lease moved or the hook already settled; the daemon's rule applies */ } + }; + const runHook = async (invocation) => { + if (closed) { + await settleHook(invocation, { error: 'executor closed' }); + return; + } + const handler = agent.hooks?.[invocation.hook]; + if (!handler) { + await settleHook(invocation, { error: `no handler for hook ${invocation.hook}` }); + return; + } + const controller = new AbortController(); + running.set(invocation.invocation_id, controller); + const deadline = Number(invocation.deadline_millis); + const timer = setTimeout(() => controller.abort(new WhipError('timeout', 'Hook deadline passed')), Math.max(0, deadline - Date.now())); + const context = { + invocationId: invocation.invocation_id, rootId: invocation.root_id, agentId: invocation.agent_id, turnId: invocation.turn_id, + permissionMode: invocation.permission_mode, deadline, signal: controller.signal, + }; + try { + let event; + if (invocation.hook === 'before_tool') + event = { ...context, operation: invocation.operation ?? '', arguments: (invocation.arguments ?? {}) }; + else if (invocation.hook === 'before_spawn') + event = { ...context, spawn: invocation.spawn.request, resolved: invocation.spawn.resolved }; + else + event = { ...context, input: invocation.input ?? '' }; + const result = await handler(event); + if (!controller.signal.aborted) + await settleHook(invocation, hookReply(result)); + } + catch (error) { + if (!controller.signal.aborted) + await settleHook(invocation, { error: asError(error).message || 'hook failed' }); + } + finally { + clearTimeout(timer); + running.delete(invocation.invocation_id); + } + }; + const settle = async (invocation, body) => { + try { + await client.call('tool.result', { invocation_id: invocation.invocation_id, generation: invocation.generation, ...body }); + } + catch { /* the lease moved or the call already settled; the daemon's record wins */ } + }; + const run = async (invocation) => { + if (closed) { + await settle(invocation, { error: 'executor closed' }); + return; + } + const definition = agent.handlers.get(invocation.tool); + if (!definition) { + await settle(invocation, { error: `no handler for tool ${invocation.tool}` }); + return; + } + const controller = new AbortController(); + running.set(invocation.invocation_id, controller); + const deadline = Number(invocation.deadline_millis); + const timer = setTimeout(() => controller.abort(new WhipError('timeout', 'Tool deadline passed')), Math.max(0, deadline - Date.now())); + // The daemon handles requests concurrently, so progress reports are + // sent one at a time, each acknowledged before the next, and the result + // is posted only after the last report landed. Order is preserved and no + // report arrives after the result and gets rejected as late. + let reports = Promise.resolve(); + const context = { + invocationId: invocation.invocation_id, rootId: invocation.root_id, agentId: invocation.agent_id, turnId: invocation.turn_id, + deadline, signal: controller.signal, + progress: text => { + if (controller.signal.aborted) + return; + reports = reports.then(() => client.call('tool.progress', { invocation_id: invocation.invocation_id, generation: invocation.generation, text })).catch(() => { }); + }, + }; + try { + // The daemon validated the input against the same JSON Schema; the + // library's own check adds refinements the schema cannot express and + // applies its defaults and transforms. + let input = invocation.input ?? {}; + if (definition.validate && isStandardSchema(definition.input)) { + const checked = await validateWith(definition.input, input); + if (checked.issues) { + await settle(invocation, { error: `tool ${invocation.tool} rejected its input: ${formatIssues(checked.issues)}` }); + return; + } + input = checked.value; + } + let output = await definition.execute(input, context); + if (definition.validate && isStandardSchema(definition.output)) { + const checked = await validateWith(definition.output, output); + if (checked.issues) { + await reports; + if (!controller.signal.aborted) + await settle(invocation, { error: `tool ${invocation.tool} returned a value that does not match its output schema (the handler already ran): ${formatIssues(checked.issues)}` }); + return; + } + output = checked.value; + } + await reports; + if (!controller.signal.aborted) + await settle(invocation, { output: output === undefined ? null : output }); + } + catch (error) { + await reports; + if (!controller.signal.aborted) + await settle(invocation, { error: asError(error).message || 'tool failed' }); + } + finally { + clearTimeout(timer); + running.delete(invocation.invocation_id); + } + }; + const abortAll = (reason) => { for (const controller of [...running.values()]) + controller.abort(reason); running.clear(); }; + // The invoke listener outlives close(): the daemon keeps routing to this + // connection's lease until it drops, and a fast "executor closed" beats a + // five-minute timeout. Only the lease this executor last held is answered. + client.onNotification('tool.invoke', invocation => { + if (invocation.definition !== definition || invocation.revision !== revision || invocation.generation !== generation) + return; + void run(invocation); + }); + client.onNotification('hook.invoke', invocation => { + if (invocation.definition !== definition || invocation.revision !== revision || invocation.generation !== generation) + return; + void runHook(invocation); + }); + const cancelled = (cancel) => { running.get(cancel.invocation_id)?.abort(new WhipError('cancelled', `Invocation cancelled: ${cancel.reason}`)); }; + const unsubscribe = [client.onNotification('tool.cancel', cancelled), client.onNotification('hook.cancel', cancelled)]; + // After a reconnect the daemon has dropped this lease and failed its calls; + // bind again and drain anything still addressed to the new lease. + let connected = true; + unsubscribe.push(client.subscribe(() => { + const state = client.getSnapshot().state; + if (state === 'closed' || state === 'incompatible') { + close(); + return; + } + if (state !== 'connected') { + if (connected) { + connected = false; + abortAll(new WhipError('disconnected', 'Executor connection lost')); + } + return; + } + if (connected || closed) + return; + connected = true; + void bind().then(() => client.call('executor.pending', { definition, revision, generation: generation })) + .then(pending => { + for (const invocation of pending.invocations ?? []) + void run(invocation); + for (const invocation of pending.hooks ?? []) + void runHook(invocation); + }) + .catch(() => { }); + })); + const close = () => { + if (closed) + return; + closed = true; + for (const stop of unsubscribe) + stop(); + abortAll(new WhipError('closed', 'Executor closed')); + resolveDone(); + }; + options.signal?.addEventListener('abort', close, { once: true }); + try { + await bind(); + } + catch (error) { + close(); + throw error; + } + if (options.signal?.aborted) { + close(); + throw abortError(options.signal); + } + // Creation resolves the definition's latest revision; a registration that + // raced ahead would leave the session pinned to tools this process does + // not serve, so both paths read the pin back before handing out a session. + const pinned = async (session, callOptions = {}) => { + const snapshot = await session.snapshot(callOptions); + if (snapshot.meta.definition !== definition || snapshot.meta.definition_revision !== revision) { + throw new WhipError('conflict', `session ${session.rootId} runs ${snapshot.meta.definition || 'coding'}@${snapshot.meta.definition_revision.slice(0, 12) || 'built-in'}, not ${definition}@${revision.slice(0, 12)} served here`); + } + return session; + }; + const sessions = { + create: async (params, commandOptions = {}) => { + const outcome = await client.sessions.create({ ...params, definition }, commandOptions).result(); + if (outcome.status !== 'succeeded' || !outcome.result) + throw new WhipError('execution_failed', `session creation ${outcome.status}: ${outcome.failure?.message ?? 'no root was returned'}`, { cause: outcome.failure ?? undefined }); + return pinned(new AgentSession(client, outcome.result.root_id)); + }, + open: (rootId, callOptions = {}) => pinned(new AgentSession(client, rootId), callOptions), + }; + return { definition, revision, get generation() { return generation; }, get active() { return running.size; }, sessions, close, done }; + } + register(agent, options = {}) { + const definition = 'document' in agent ? agent.document : agent; + return this.client.call('definitions.register', { definition }, options); + } + get(id, revision, options = {}) { + const params = revision ? { id, revision } : { id }; + return this.client.call('definitions.get', params, options); + } + list(options = {}) { + return this.client.call('definitions.list', {}, options); + } +} +//# sourceMappingURL=agents.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/agents.js.map b/vendor/@whip/sdk/dist/agents.js.map new file mode 100644 index 0000000..f7bc97e --- /dev/null +++ b/vendor/@whip/sdk/dist/agents.js.map @@ -0,0 +1 @@ +{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateSessionParams, DefinitionList, SubmitPayload, DefinitionParams, DefinitionRecord, DefinitionRegisterParams, DefinitionRegisterResult, HookInvokeParams, HookResultParams, ToolInvokeParams,\n} from '@whip/protocol';\nimport type { CallOptions, WhipClient } from './client.js';\nimport type { CommandOptions } from './command.js';\nimport { Session } from './session.js';\nimport { Turn, type RunOptions } from './turn.js';\nimport { WhipError, abortError, asError } from './errors.js';\nimport { describesObject, formatIssues, isStandardSchema, toJsonSchema, validateWith, type InferInput, type InferOutput, type Schema } from './schema.js';\n\nexport type { InferInput, InferOutput, JsonSchema, Schema, StandardSchemaWithJSON } from './schema.js';\n\n/** The wire document the daemon validates and stores. Generated from the Go definition. */\nexport type Definition = DefinitionRegisterParams['definition'];\nexport type ToolSpec = NonNullable[number];\nexport type ChildDefinition = Definition['children'][string];\n\n/** Context handed to a tool handler for one invocation. */\nexport interface ToolContext {\n /** The daemon's ledger operation id; reuse it to make side effects idempotent. */\n readonly invocationId: string;\n readonly rootId: string;\n readonly agentId: string;\n readonly turnId: string;\n /** Unix milliseconds after which the daemon settles the call as timed out. */\n readonly deadline: number;\n /** Aborted on cancellation, deadline, executor close, or disconnect. */\n readonly signal: AbortSignal;\n /** Report intermediate output; the daemon streams it to the session. */\n progress(text: string): void;\n}\n/** What execute must return: the output schema's type when one is declared, otherwise anything JSON. */\nexport type ToolReturn = O extends Schema ? InferOutput : unknown;\nexport type ToolHandler =\n (input: InferInput, context: ToolContext) => ToolReturn | Promise>;\n\nexport interface ToolOptions {\n /** Lowercase identifier; the cell calls tools.. */\n name: string;\n description: string;\n /** Keyword arguments the cell passes. A Standard JSON Schema (zod, ArkType, Valibot) infers the handler's input type; raw JSON Schema types it unknown. */\n input: I;\n /** What execute returns. Typed at compile time and validated locally before the result is posted. */\n output?: O;\n execute(input: InferInput, context: ToolContext): ToolReturn | Promise>;\n /** Default 5 minutes, ceiling 15. */\n timeoutMs?: number;\n /** Run the schemas' own validation around execute (default true); raw JSON Schema has none. */\n validate?: boolean;\n}\n\nexport interface ToolDefinition {\n readonly spec: ToolSpec;\n readonly input: I;\n readonly output: O | undefined;\n readonly validate: boolean;\n execute(input: InferInput, context: ToolContext): ToolReturn | Promise>;\n}\n\n/** Wire shapes a before_spawn hook sees: the request as the model wrote it and what it resolved to. */\nexport type SpawnRequest = NonNullable['request'];\nexport type ResolvedChild = NonNullable['resolved'];\nexport type HookName = 'before_tool' | 'before_spawn' | 'turn_start';\n\n/** Identity every hook event carries. */\nexport interface HookContext {\n readonly invocationId: string;\n readonly rootId: string;\n readonly agentId: string;\n readonly turnId: string;\n /** The session's permission mode; hooks only narrow it. */\n readonly permissionMode: string;\n /** Unix milliseconds after which the daemon applies the hook's required or optional rule. */\n readonly deadline: number;\n /** Aborted on cancellation, deadline, executor close, or disconnect. */\n readonly signal: AbortSignal;\n}\nexport interface BeforeToolEvent extends HookContext {\n /** module.operation, such as shell.run or tools.lookup_ticket. */\n readonly operation: string;\n readonly arguments: Record;\n}\n/** Every field is optional; returning nothing allows the operation unchanged. */\nexport interface BeforeToolResult { decision?: 'allow' | 'deny'; reason?: string; arguments?: Record }\nexport interface BeforeSpawnEvent extends HookContext { readonly spawn: SpawnRequest; readonly resolved: ResolvedChild }\nexport interface BeforeSpawnResult { decision?: 'allow' | 'deny'; reason?: string; spawn?: SpawnRequest }\nexport interface TurnStartEvent extends HookContext { readonly input: string }\nexport interface TurnStartResult { context?: string }\nexport type HookHandler = (event: E) => Promise | R | void;\nexport interface HookOptions {\n /** Proceed with a notice when unanswered or failing, instead of denying. */\n optional?: boolean;\n /** Default 30 seconds, ceiling 60. */\n timeoutMs?: number;\n}\nexport interface HooksInput {\n /** Runs before every host operation the agent's cells call; `operations` narrows it to module.operation names. */\n beforeTool?: HookHandler | (HookOptions & { handler: HookHandler; operations?: string[] });\n /** Runs after a spawn request is parsed and resolved, before the child is admitted. */\n beforeSpawn?: HookHandler | (HookOptions & { handler: HookHandler });\n /** Contributes ephemeral context to each turn; it never gates. */\n turnStart?: HookHandler | (HookOptions & { handler: HookHandler });\n}\nexport interface HookHandlers {\n readonly before_tool?: HookHandler;\n readonly before_spawn?: HookHandler;\n readonly turn_start?: HookHandler;\n}\n\nexport interface InstructionsInput {\n persona?: string;\n rules?: string;\n /** Project instruction files read along the authorized directory chain. Omit to disable discovery. */\n projectFiles?: string[];\n skillDiscovery?: boolean;\n standingInstructions?: boolean;\n}\nexport interface ModelInput { model?: string; provider?: string; effort?: string }\nexport interface CompactionInput { model?: string; provider?: string; threshold?: number }\nexport interface ChildInput {\n instructions?: InstructionsInput;\n modules?: string[];\n capabilities?: string[];\n tools?: string[];\n model?: ModelInput;\n budgets?: Record;\n report?: 'notice' | 'inline' | 'message';\n /** Replaces the parent's output contract for this child. */\n output?: Schema;\n}\n/** The turn result type an agent's output contract implies: the schema's type, or unknown without one. */\nexport type AgentOutput = O extends Schema ? InferOutput : unknown;\nexport interface AgentInput {\n /** Lowercase id, letters, digits and hyphens; must not shadow a built-in. */\n id: string;\n instructions?: InstructionsInput;\n /** Built-in host modules the model may call. */\n modules: string[];\n capabilities?: string[];\n model?: ModelInput;\n compaction?: CompactionInput;\n /** Named MCP servers from host configuration; omit for every configured server. Requires the mcp capability. */\n mcp?: { servers?: string[] };\n tools?: readonly ToolDefinition[];\n children?: Record;\n surface?: { autoTitle?: boolean; goalLoop?: boolean };\n hooks?: HooksInput;\n /** What a turn returns: an object schema the daemon enforces on the final message. It types the turn result. */\n output?: O;\n}\n\n/** An authored agent: the document the daemon stores plus the tools and hooks an executor serves. */\nexport interface AgentDefinition {\n readonly document: Definition;\n /** Tool definitions by name; serve runs their execute and validates around it. */\n readonly handlers: ReadonlyMap;\n readonly hooks: HookHandlers;\n /** Carries the output contract's type; never set at runtime. */\n readonly outputType?: Output;\n}\n\n/**\n * Declare one custom tool. The input schema's JSON Schema goes to the daemon,\n * which validates every call against it before the handler runs; the output\n * schema types execute's return and is checked before the result is posted.\n */\nexport function tool(options: ToolOptions): ToolDefinition {\n const { name, description, input, output, execute, timeoutMs, validate = true } = options;\n if (typeof name !== 'string' || !name.trim()) throw new TypeError('tool name is required');\n if (typeof description !== 'string') throw new TypeError(`tool ${name}: description is required`);\n if (typeof execute !== 'function') throw new TypeError(`tool ${name}: execute is required`);\n if (timeoutMs !== undefined && (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0)) throw new TypeError(`tool ${name}: timeoutMs must be a non-negative integer`);\n const inputSchema = toJsonSchema(input, 'input', `tool ${name} input`);\n if (!describesObject(inputSchema)) throw new TypeError(`tool ${name}: input schema must describe an object; the cell passes keyword arguments`);\n const outputSchema = output === undefined ? null : toJsonSchema(output, 'output', `tool ${name} output`);\n return { spec: { name, description, input_schema: inputSchema, output_schema: outputSchema, timeout_millis: timeoutMs ?? 0 }, input, output, validate, execute };\n}\n\n/**\n * Build the canonical definition document from authoring input. The daemon is\n * the authority on validation; this only rejects shapes that cannot be sent.\n */\nexport function defineAgent(input: AgentInput): AgentDefinition> {\n if (!input.id.trim()) throw new TypeError('agent id is required');\n const contract = (schema: Schema | undefined, subject: string) => {\n if (schema === undefined) return null;\n const derived = toJsonSchema(schema, 'output', subject);\n if (!describesObject(derived)) throw new TypeError(`${subject}: output contract must describe an object`);\n return derived;\n };\n if (!Array.isArray(input.modules) || input.modules.length === 0) throw new TypeError(`agent ${input.id}: select at least one host module`);\n const handlers = new Map();\n const tools: ToolSpec[] = [];\n for (const definition of input.tools ?? []) {\n if (handlers.has(definition.spec.name)) throw new TypeError(`agent ${input.id}: tool ${definition.spec.name} is declared twice`);\n handlers.set(definition.spec.name, definition);\n tools.push(definition.spec);\n }\n const children: Definition['children'] = {};\n for (const [name, child] of Object.entries(input.children ?? {})) {\n children[name] = {\n instructions: child.instructions ? instructions(child.instructions) : null,\n modules: list(child.modules), capabilities: list(child.capabilities), tools: list(child.tools),\n model: model(child.model), budgets: { ...(child.budgets ?? {}) }, report: child.report ?? '',\n output: contract(child.output, `agent ${input.id} child ${name} output`),\n };\n }\n const hooks: HookHandlers = {};\n const hookSpecs: Record['before_tool']> = { before_tool: null, before_spawn: null, turn_start: null };\n const declare = (name: HookName, value: HookHandler | (HookOptions & { handler: HookHandler; operations?: string[] }) | undefined) => {\n if (!value) return;\n const options: HookOptions & { operations?: string[] } = typeof value === 'function' ? {} : value;\n const handler = typeof value === 'function' ? value : value.handler;\n if (typeof handler !== 'function') throw new TypeError(`agent ${input.id}: hook ${name} needs a handler`);\n if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 0)) throw new TypeError(`agent ${input.id}: hook ${name} timeoutMs must be a non-negative integer`);\n if (options.operations !== undefined && name !== 'before_tool') throw new TypeError(`agent ${input.id}: only before_tool accepts operations`);\n (hooks as Record)[name] = handler;\n hookSpecs[name] = { operations: list(options.operations), optional: options.optional ?? false, timeout_millis: options.timeoutMs ?? 0 };\n };\n declare('before_tool', input.hooks?.beforeTool);\n declare('before_spawn', input.hooks?.beforeSpawn);\n declare('turn_start', input.hooks?.turnStart);\n const document: Definition = {\n id: input.id,\n instructions: instructions(input.instructions ?? {}),\n modules: [...input.modules],\n capabilities: list(input.capabilities),\n model: model(input.model),\n compaction: { model: input.compaction?.model ?? '', provider: input.compaction?.provider ?? '', threshold: input.compaction?.threshold ?? 0 },\n // The daemon distinguishes null (every host server) from [] (no servers);\n // an explicit empty allowlist must not widen to everything.\n mcp: { servers: input.mcp?.servers === undefined ? null : [...input.mcp.servers] },\n tools: tools.length > 0 ? tools : null,\n output: contract(input.output, `agent ${input.id} output`),\n children,\n surface: { auto_title: input.surface?.autoTitle ?? true, goal_loop: input.surface?.goalLoop ?? false },\n hooks: Object.keys(hooks).length > 0 ? hookSpecs : null,\n };\n return { document: Object.freeze(document), handlers, hooks: Object.freeze(hooks) };\n}\n\n/** Turn a handler's return value into the optional-field hook reply. */\nfunction hookReply(result: unknown): Partial {\n if (!result || typeof result !== 'object') return {};\n const value = result as BeforeToolResult & BeforeSpawnResult & TurnStartResult;\n const reply: Partial = {};\n if (value.decision) reply.decision = value.decision;\n if (value.reason) reply.reason = value.reason;\n if (value.arguments) reply.arguments = value.arguments;\n if (value.spawn) reply.spawn = value.spawn;\n if (value.context) reply.context = value.context;\n return reply;\n}\n\nfunction instructions(input: InstructionsInput): Definition['instructions'] {\n return {\n persona: input.persona ?? '', rules: input.rules ?? '', project_files: list(input.projectFiles),\n skill_discovery: input.skillDiscovery ?? false, standing_instructions: input.standingInstructions ?? false,\n };\n}\nfunction model(input: ModelInput | undefined): Definition['model'] {\n return { model: input?.model ?? '', provider: input?.provider ?? '', effort: input?.effort ?? '' };\n}\n/** Absent or empty lists are null in the canonical document. */\nfunction list(values: string[] | undefined): null | string[] {\n return values && values.length > 0 ? [...values] : null;\n}\n\nexport interface ServeOptions { signal?: AbortSignal }\n\n/** Session creation on a served definition; the definition and its revision come from the runtime. */\nexport type RuntimeSessionParams = Pick & Partial>;\n\n/** A session on a served definition: run() returns a turn typed by the agent's output contract. */\nexport class AgentSession extends Session {\n override run(input: string | SubmitPayload, options: RunOptions = {}): Turn {\n return new Turn(this, typeof input === 'string' ? { text: input } : input, options);\n }\n}\n\n/**\n * A served agent: this process answers its tools and hooks for one definition\n * revision, and the sessions created here pin that revision, so a runtime\n * never drives a session whose tools it does not serve.\n */\nexport interface AgentRuntime {\n readonly definition: string;\n readonly revision: string;\n /** Current lease generation; a reconnect re-binds and changes it. */\n readonly generation: string;\n /** Tool and hook handlers currently running. */\n readonly active: number;\n readonly sessions: {\n /** Create a session on this definition and return it once the daemon has admitted it and it pins this revision. */\n create(params: RuntimeSessionParams, options?: CommandOptions): Promise>;\n /** Open an existing session after checking that it pins this definition and revision. */\n open(rootId: string, options?: CallOptions): Promise>;\n };\n /** Stop serving. Running handlers are aborted; later invocations fail fast. Sessions outlive the runtime. */\n close(): void;\n /** Settles when serving stops. */\n readonly done: Promise;\n}\n/** The pre-runtime name; kept as an alias for one release. */\nexport type Executor = AgentRuntime;\n\n/** Registry operations. Registration is idempotent on content: the same document yields the same revision. */\nexport class Agents {\n constructor(private readonly client: WhipClient) {}\n /**\n * Register the agent, bind this connection as its executor, and run its tool\n * handlers until close(). Bind before creating sessions: a tool call with no\n * executor fails after a short wait. The executor re-binds after a reconnect\n * and drains invocations that were pending for it.\n */\n async serve(agent: AgentDefinition, options: ServeOptions = {}): Promise> {\n const hookNames = (['before_tool', 'before_spawn', 'turn_start'] as const).filter(name => agent.hooks?.[name]);\n if (agent.handlers.size === 0 && hookNames.length === 0) throw new TypeError(`agent ${agent.document.id} declares no tools or hooks to serve`);\n options.signal?.throwIfAborted();\n const client = this.client;\n const registered = await this.register(agent, options);\n const definition = agent.document.id;\n const revision = registered.revision;\n const tools = [...agent.handlers.keys()];\n const running = new Map();\n let generation = '';\n let closed = false;\n let resolveDone!: () => void;\n const done = new Promise(resolve => { resolveDone = resolve; });\n const bind = async () => {\n const lease = await client.call('executor.bind', { definition, revision, tools, ...(hookNames.length > 0 ? { hooks: hookNames } : {}) }, options);\n generation = lease.generation;\n };\n const settleHook = async (invocation: HookInvokeParams, body: Partial) => {\n try { await client.call('hook.result', { invocation_id: invocation.invocation_id, generation: invocation.generation, ...body }); }\n catch { /* the lease moved or the hook already settled; the daemon's rule applies */ }\n };\n const runHook = async (invocation: HookInvokeParams) => {\n if (closed) { await settleHook(invocation, { error: 'executor closed' }); return; }\n const handler = agent.hooks?.[invocation.hook as HookName] as HookHandler | undefined;\n if (!handler) { await settleHook(invocation, { error: `no handler for hook ${invocation.hook}` }); return; }\n const controller = new AbortController();\n running.set(invocation.invocation_id, controller);\n const deadline = Number(invocation.deadline_millis);\n const timer = setTimeout(() => controller.abort(new WhipError('timeout', 'Hook deadline passed')), Math.max(0, deadline - Date.now()));\n const context: HookContext = {\n invocationId: invocation.invocation_id, rootId: invocation.root_id, agentId: invocation.agent_id, turnId: invocation.turn_id,\n permissionMode: invocation.permission_mode, deadline, signal: controller.signal,\n };\n try {\n let event: unknown;\n if (invocation.hook === 'before_tool') event = { ...context, operation: invocation.operation ?? '', arguments: (invocation.arguments ?? {}) as Record } satisfies BeforeToolEvent;\n else if (invocation.hook === 'before_spawn') event = { ...context, spawn: invocation.spawn!.request, resolved: invocation.spawn!.resolved } satisfies BeforeSpawnEvent;\n else event = { ...context, input: invocation.input ?? '' } satisfies TurnStartEvent;\n const result = await handler(event);\n if (!controller.signal.aborted) await settleHook(invocation, hookReply(result));\n } catch (error) {\n if (!controller.signal.aborted) await settleHook(invocation, { error: asError(error).message || 'hook failed' });\n } finally {\n clearTimeout(timer);\n running.delete(invocation.invocation_id);\n }\n };\n const settle = async (invocation: ToolInvokeParams, body: { output?: unknown; error?: string }) => {\n try { await client.call('tool.result', { invocation_id: invocation.invocation_id, generation: invocation.generation, ...body }); }\n catch { /* the lease moved or the call already settled; the daemon's record wins */ }\n };\n const run = async (invocation: ToolInvokeParams) => {\n if (closed) { await settle(invocation, { error: 'executor closed' }); return; }\n const definition = agent.handlers.get(invocation.tool);\n if (!definition) { await settle(invocation, { error: `no handler for tool ${invocation.tool}` }); return; }\n const controller = new AbortController();\n running.set(invocation.invocation_id, controller);\n const deadline = Number(invocation.deadline_millis);\n const timer = setTimeout(() => controller.abort(new WhipError('timeout', 'Tool deadline passed')), Math.max(0, deadline - Date.now()));\n // The daemon handles requests concurrently, so progress reports are\n // sent one at a time, each acknowledged before the next, and the result\n // is posted only after the last report landed. Order is preserved and no\n // report arrives after the result and gets rejected as late.\n let reports: Promise = Promise.resolve();\n const context: ToolContext = {\n invocationId: invocation.invocation_id, rootId: invocation.root_id, agentId: invocation.agent_id, turnId: invocation.turn_id,\n deadline, signal: controller.signal,\n progress: text => {\n if (controller.signal.aborted) return;\n reports = reports.then(() => client.call('tool.progress', { invocation_id: invocation.invocation_id, generation: invocation.generation, text })).catch(() => {});\n },\n };\n try {\n // The daemon validated the input against the same JSON Schema; the\n // library's own check adds refinements the schema cannot express and\n // applies its defaults and transforms.\n let input: unknown = invocation.input ?? {};\n if (definition.validate && isStandardSchema(definition.input)) {\n const checked = await validateWith(definition.input, input);\n if (checked.issues) { await settle(invocation, { error: `tool ${invocation.tool} rejected its input: ${formatIssues(checked.issues)}` }); return; }\n input = checked.value;\n }\n let output: unknown = await definition.execute(input, context);\n if (definition.validate && isStandardSchema(definition.output)) {\n const checked = await validateWith(definition.output, output);\n if (checked.issues) {\n await reports;\n if (!controller.signal.aborted) await settle(invocation, { error: `tool ${invocation.tool} returned a value that does not match its output schema (the handler already ran): ${formatIssues(checked.issues)}` });\n return;\n }\n output = checked.value;\n }\n await reports;\n if (!controller.signal.aborted) await settle(invocation, { output: output === undefined ? null : output });\n } catch (error) {\n await reports;\n if (!controller.signal.aborted) await settle(invocation, { error: asError(error).message || 'tool failed' });\n } finally {\n clearTimeout(timer);\n running.delete(invocation.invocation_id);\n }\n };\n const abortAll = (reason: Error) => { for (const controller of [...running.values()]) controller.abort(reason); running.clear(); };\n // The invoke listener outlives close(): the daemon keeps routing to this\n // connection's lease until it drops, and a fast \"executor closed\" beats a\n // five-minute timeout. Only the lease this executor last held is answered.\n client.onNotification('tool.invoke', invocation => {\n if (invocation.definition !== definition || invocation.revision !== revision || invocation.generation !== generation) return;\n void run(invocation);\n });\n client.onNotification('hook.invoke', invocation => {\n if (invocation.definition !== definition || invocation.revision !== revision || invocation.generation !== generation) return;\n void runHook(invocation);\n });\n const cancelled = (cancel: { invocation_id: string; reason: string }) => { running.get(cancel.invocation_id)?.abort(new WhipError('cancelled', `Invocation cancelled: ${cancel.reason}`)); };\n const unsubscribe = [client.onNotification('tool.cancel', cancelled), client.onNotification('hook.cancel', cancelled)];\n // After a reconnect the daemon has dropped this lease and failed its calls;\n // bind again and drain anything still addressed to the new lease.\n let connected = true;\n unsubscribe.push(client.subscribe(() => {\n const state = client.getSnapshot().state;\n if (state === 'closed' || state === 'incompatible') { close(); return; }\n if (state !== 'connected') { if (connected) { connected = false; abortAll(new WhipError('disconnected', 'Executor connection lost')); } return; }\n if (connected || closed) return;\n connected = true;\n void bind().then(() => client.call('executor.pending', { definition, revision, generation: generation }))\n .then(pending => {\n for (const invocation of pending.invocations ?? []) void run(invocation);\n for (const invocation of pending.hooks ?? []) void runHook(invocation);\n })\n .catch(() => { /* the next reconnect retries; the daemon fails calls with no executor */ });\n }));\n const close = () => {\n if (closed) return;\n closed = true;\n for (const stop of unsubscribe) stop();\n abortAll(new WhipError('closed', 'Executor closed'));\n resolveDone();\n };\n options.signal?.addEventListener('abort', close, { once: true });\n try { await bind(); } catch (error) { close(); throw error; }\n if (options.signal?.aborted) { close(); throw abortError(options.signal); }\n // Creation resolves the definition's latest revision; a registration that\n // raced ahead would leave the session pinned to tools this process does\n // not serve, so both paths read the pin back before handing out a session.\n const pinned = async (session: AgentSession, callOptions: CallOptions = {}): Promise> => {\n const snapshot = await session.snapshot(callOptions);\n if (snapshot.meta.definition !== definition || snapshot.meta.definition_revision !== revision) {\n throw new WhipError('conflict', `session ${session.rootId} runs ${snapshot.meta.definition || 'coding'}@${snapshot.meta.definition_revision.slice(0, 12) || 'built-in'}, not ${definition}@${revision.slice(0, 12)} served here`);\n }\n return session;\n };\n const sessions: AgentRuntime['sessions'] = {\n create: async (params, commandOptions = {}) => {\n const outcome = await client.sessions.create({ ...params, definition }, commandOptions).result();\n if (outcome.status !== 'succeeded' || !outcome.result) throw new WhipError('execution_failed', `session creation ${outcome.status}: ${outcome.failure?.message ?? 'no root was returned'}`, { cause: outcome.failure ?? undefined });\n return pinned(new AgentSession(client, outcome.result.root_id));\n },\n open: (rootId, callOptions = {}) => pinned(new AgentSession(client, rootId), callOptions),\n };\n return { definition, revision, get generation() { return generation; }, get active() { return running.size; }, sessions, close, done };\n }\n register(agent: AgentDefinition | Definition, options: CallOptions = {}): Promise {\n const definition = 'document' in agent ? agent.document : agent;\n return this.client.call('definitions.register', { definition } satisfies DefinitionRegisterParams, options);\n }\n get(id: string, revision?: string, options: CallOptions = {}): Promise {\n const params: DefinitionParams = revision ? { id, revision } : { id };\n return this.client.call('definitions.get', params, options);\n }\n list(options: CallOptions = {}): Promise {\n return this.client.call('definitions.list', {}, options);\n }\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/browser.d.ts b/vendor/@whip/sdk/dist/browser.d.ts new file mode 100644 index 0000000..e23ce19 --- /dev/null +++ b/vendor/@whip/sdk/dist/browser.d.ts @@ -0,0 +1,42 @@ +import { type BrowserInventoryRequest, type BrowserInventoryResultParams, type BrowserCommand, type BrowserCommandCancel, type BrowserCommandResultParams, type BrowserProviderBindParams, type BrowserProviderBindResult, type BrowserProviderEventParams } from '@whip/protocol'; +import type { CallOptions, WhipClient } from './client.js'; +/** Structural native adapter. SDK never imports Electron or application packages. */ +export interface BrowserProviderBridge { + select(input: { + offer: BrowserProviderBindParams; + provider: BrowserProviderBindResult; + connectionId?: string; + projectId?: string; + }): Promise; + inventory?(request: BrowserInventoryRequest): Promise; + dispatch(command: BrowserCommand): Promise; + cancel(input: BrowserCommandCancel): void; + release(input: { + rootId: string; + providerEpoch: string; + }): Promise; + onEvent(listener: (event: { + kind: string; + event?: BrowserProviderEventParams; + }) => void): () => void; +} +export interface BrowserSelectionOptions extends CallOptions { + connectionId?: string; + projectId?: string; + onError?(error: Error): void; +} +export interface BrowserSelection { + readonly provider: Readonly; + readonly active: boolean; + /** Releases this exact epoch only. It never closes human Browser tabs. */ + release(): Promise; +} +/** Explicit root association, not a default/newest-wins browser destination. */ +export declare class BrowserProviders { + private readonly client; + private readonly roots; + constructor(client: WhipClient); + select(input: BrowserProviderBindParams, bridge: BrowserProviderBridge, options?: BrowserSelectionOptions): Promise; +} diff --git a/vendor/@whip/sdk/dist/browser.js b/vendor/@whip/sdk/dist/browser.js new file mode 100644 index 0000000..82c1302 --- /dev/null +++ b/vendor/@whip/sdk/dist/browser.js @@ -0,0 +1,295 @@ +import { assertValid } from '@whip/protocol'; +import { RpcError, WhipError, asError } from './errors.js'; +import { upload } from './content.js'; +import { byteLength, withSignal } from './util.js'; +/** Explicit root association, not a default/newest-wins browser destination. */ +export class BrowserProviders { + client; + roots = new Map(); + constructor(client) { + this.client = client; + client.onNotification('browser.inventory', request => this.roots.get(request.root_id)?.inventory(request)); + client.onNotification('browser.command', command => this.roots.get(command.root_id)?.receive(command)); + client.onNotification('browser.command.cancel', command => this.roots.get(command.root_id)?.cancel(command)); + client.onNotification('browser.provider.revoked', event => this.roots.get(event.root_id)?.revoke(event.provider_epoch, event.reason)); + client.subscribe(() => { + if (client.getSnapshot().state !== 'connected') { + for (const selection of this.roots.values()) + selection.disconnected(); + } + }); + } + async select(input, bridge, options = {}) { + this.client.requireConnected(); + const offer = structuredClone(input); + assertValid('BrowserProviderBindParams', offer); + if (this.roots.has(offer.root_id)) + throw new WhipError('invalid_arguments', 'Release the existing Browser provider before selecting another'); + const selection = new SelectedBrowser(this.client, offer, bridge, { ...options }, () => { + if (this.roots.get(offer.root_id) === selection) + this.roots.delete(offer.root_id); + }); + this.roots.set(offer.root_id, selection); + return selection.start(); + } +} +class SelectedBrowser { + client; + offer; + bridge; + options; + removed; + binding; + unbinding; + live = true; + serverRevoked = false; + pending = new Map(); + revoked = new Map(); + seen = new Set(); + events = Promise.resolve(); + inventoryCount = 0; + eventCount = 0; + eventBytes = 0; + retiredEvents = new Set(); + lifetime = new AbortController(); + ready; + acknowledge; + off; + constructor(client, offer, bridge, options, removed) { + this.client = client; + this.offer = offer; + this.bridge = bridge; + this.options = options; + this.removed = removed; + this.ready = new Promise(resolve => { this.acknowledge = resolve; }); + this.events = this.ready; + this.off = bridge.onEvent(message => { + const event = message.event; + if (message.kind !== 'provider' || !event || !this.matches(event.root_id, event.provider_epoch)) + return; + this.observe(event); + }); + } + observe(event) { + const key = JSON.stringify([event.tab_id, event.tab_generation, event.attachment_id, event.attachment_generation]); + if (this.retiredEvents.has(key)) + return; + try { + // Snapshot once and bound retained payloads, not just the RPC frame size. + const encoded = JSON.stringify(event); + const bytes = byteLength(encoded); + if (this.eventCount >= 64 || this.eventBytes + bytes > 2 * 1024 * 1024) { + throw new WhipError('resource_limit', 'Browser observation queue exceeded its limit; select the provider again explicitly'); + } + const snapshot = JSON.parse(encoded); + this.eventCount++; + this.eventBytes += bytes; + // Preserve observation order. Dropping a live sequence would hide a gap. + this.events = this.events.then(async () => { + if (this.matches(snapshot.root_id, snapshot.provider_epoch) && !this.retiredEvents.has(key)) { + await this.client.call('browser.provider.event', snapshot, { signal: this.lifetime.signal, timeoutMs: 10_000 }); + } + }).catch(error => { + if (error instanceof RpcError && error.kind === 'browser_event_stale') { + // The broker authenticated the holder but this exact attachment retired. + // Other attachments keep their independent authority and event sequence. + if (this.retiredEvents.size >= 8192) + return this.fail(new WhipError('resource_limit', 'Browser retired observation capacity reached')); + this.retiredEvents.add(key); + return; + } + if (this.live) + return this.fail(error); + }).finally(() => { this.eventCount--; this.eventBytes -= bytes; }); + } + catch (error) { + void this.fail(error); + } + } + start() { + return this.bind(); + } + async bind() { + try { + const signal = AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(this.options.timeoutMs ?? 30_000), ...(this.options.signal ? [this.options.signal] : [])]); + const binding = await this.client.call('browser.provider.bind', this.offer, { ...this.options, signal }); + this.binding = binding; + if (binding.version !== this.offer.version || (binding.version === 2 && !this.bridge.inventory)) + throw new WhipError('unavailable_capability', 'Browser discovery requires an updated native Desktop'); + const revoked = this.revoked.get(binding.provider_epoch); + if (revoked !== undefined) { + this.revoke(binding.provider_epoch, revoked); + throw new WhipError('unavailable_capability', 'Browser provider was revoked before selection completed'); + } + this.revoked.clear(); + if (!this.live) { + await this.release(); + throw new WhipError('disconnected', 'Browser selection was interrupted'); + } + const native = this.bridge.select({ offer: this.offer, provider: binding, + ...(this.options.connectionId ? { connectionId: this.options.connectionId } : {}), + ...(this.options.projectId ? { projectId: this.options.projectId } : {}) }); + // A timed-out native invocation cannot be cancelled or replayed. A late ACK + // must release its exact epoch without touching any replacement selection. + void native.then(() => { + if (signal.aborted || !this.live) + return this.bridge.release({ rootId: this.offer.root_id, providerEpoch: binding.provider_epoch }); + }).catch(error => { if (!this.live) + this.report(error); }); + await withSignal(native, signal); + signal.throwIfAborted(); + this.acknowledge(); + const selected = this; + return { provider: Object.freeze({ ...binding }), get active() { return selected.live; }, release: () => selected.release() }; + } + catch (error) { + void this.release().catch(releaseError => this.report(releaseError)); + throw error; + } + } + matches(root, epoch) { return this.live && root === this.offer.root_id && epoch === this.binding?.provider_epoch; } + inventory(request) { + if (!this.live || this.inventoryCount >= 32) + return; + this.inventoryCount++; + void this.ready.then(async () => { + if (!this.matches(request.root_id, request.provider_epoch)) + return; + let result; + try { + if (!this.bridge.inventory) + throw new WhipError('unavailable_capability', 'Native tab discovery is unavailable'); + result = await withSignal(this.bridge.inventory(request), AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(10_000)])); + assertValid('BrowserInventoryResultParams', result); + if (result.request_id !== request.request_id || result.root_id !== request.root_id || result.provider_epoch !== request.provider_epoch || byteLength(JSON.stringify(result)) > 64 * 1024) { + throw new WhipError('invalid_arguments', 'Native inventory identity or size mismatch'); + } + } + catch { + result = { request_id: request.request_id, root_id: request.root_id, provider_epoch: request.provider_epoch, tabs: [], error: { kind: 'desktop_unavailable', message: 'Native inventory could not complete safely' } }; + } + if (this.matches(request.root_id, request.provider_epoch)) + await this.client.call('browser.inventory.result', result, { signal: this.lifetime.signal, timeoutMs: 10_000 }); + }).catch(() => { + // A metadata reply may outlive the caller's cancelled/expired request. + // Never replay it or change an independent control association's state. + }).finally(() => { this.inventoryCount--; }); + } + receive(command) { + // Commands may race the bind reply; never dispatch until native selection ACKs. + if (!this.live || this.seen.has(command.command_id)) + return; + if (this.seen.size >= 8192 || this.pending.size >= 32) { + void this.fail(new WhipError('resource_limit', 'Browser provider command capacity reached')); + return; + } + this.seen.add(command.command_id); + const state = { cancelled: false, command }; + this.pending.set(command.command_id, state); + void this.ready.then(async () => { + if (state.cancelled || !this.matches(command.root_id, command.provider_epoch)) + return; + let result; + try { + if (BigInt(command.deadline_millis) <= BigInt(Date.now())) { + result = this.failure(command, 'deadline_exceeded', 'Browser command expired before native delivery'); + } + else { + const native = await this.bridge.dispatch(command); + const { screenshotBytes, ...wire } = native; + assertValid('BrowserCommandResultParams', wire, 'response'); + if (wire.command_id !== command.command_id || wire.root_id !== command.root_id || wire.provider_epoch !== command.provider_epoch || wire.attachment_generation !== (command.scope.attachment_generation ?? '')) { + throw new WhipError('invalid_response', 'Native Browser result identity changed'); + } + result = wire; + if (screenshotBytes) { + if (wire.screenshot || screenshotBytes.byteLength > 8 * 1024 * 1024 || screenshotBytes.byteLength < 3 || screenshotBytes[0] !== 0xff || screenshotBytes[1] !== 0xd8 || screenshotBytes[2] !== 0xff) + throw new WhipError('invalid_response', 'Invalid native Browser screenshot'); + if (state.cancelled || !this.matches(command.root_id, command.provider_epoch)) + return; + const content = await upload(this.client, new Uint8Array(screenshotBytes), { rootId: command.root_id, agentId: command.agent_id, mediaType: 'image/jpeg', source: 'desktop-browser', signal: this.lifetime.signal, timeoutMs: Math.max(1, Math.min(30_000, Number(BigInt(command.deadline_millis) - BigInt(Date.now())))) }, 'connection'); + result = { ...wire, screenshot: content.handle }; + } + } + } + catch (error) { + // Native delivery may already have changed the page. Never replay it. + result = this.failure(command, 'outcome_unknown', asError(error).message); + } + if (!state.cancelled && this.matches(command.root_id, command.provider_epoch)) + await this.client.call('browser.command.result', result); + }).catch(error => { + // An exact cancellation may arrive after result submission but before its + // rejection. Retired work must not tear down other attachments or epochs. + if (!state.cancelled && this.matches(command.root_id, command.provider_epoch)) + return this.fail(error); + }).finally(() => this.pending.delete(command.command_id)); + } + cancel(command) { + if (command.root_id !== this.offer.root_id || (this.binding && command.provider_epoch !== this.binding.provider_epoch)) + return; + const pending = this.pending.get(command.command_id); + if (!pending || pending.command.provider_epoch !== command.provider_epoch || (pending.command.scope.attachment_generation ?? '') !== command.attachment_generation) + return; + pending.cancelled = true; + if (this.matches(command.root_id, command.provider_epoch)) + this.bridge.cancel(command); + } + failure(command, kind, message) { + return { command_id: command.command_id, root_id: command.root_id, provider_epoch: command.provider_epoch, + attachment_generation: command.scope.attachment_generation ?? '', document_revision: command.expected_document ?? '', error: { kind, message: message.slice(0, 2048) } }; + } + revoke(epoch, reason) { + if (!this.live) + return; + if (!this.binding) { + if (this.revoked.size >= 8) { + void this.fail(new WhipError('resource_limit', 'Browser selection revocation capacity reached')); + return; + } + this.revoked.set(epoch, reason); + return; + } + if (this.binding.provider_epoch !== epoch) + return; + this.serverRevoked = true; + this.disconnected(); + this.report(new WhipError('unavailable_capability', `Browser provider was revoked (${reason.slice(0, 128)}); select a provider again explicitly`)); + } + disconnected() { void this.release(false).catch(error => this.report(error)); } + report(error) { + // An observer cannot break teardown or create an unhandled command rejection. + try { + this.options.onError?.(asError(error)); + } + catch { /* observer failure */ } + } + async fail(error) { + const cleanup = this.release(); + this.report(error); + try { + await cleanup; + } + catch (releaseError) { + this.report(releaseError); + } + } + async release(notify = true) { + this.live = false; + this.lifetime.abort(new WhipError('disconnected', 'Browser selection was released')); + this.acknowledge(); + this.off(); + this.removed(); + for (const pending of this.pending.values()) + pending.cancelled = true; + const binding = this.binding; + if (!binding) + return; + const native = withSignal(Promise.resolve().then(() => this.bridge.release({ rootId: this.offer.root_id, providerEpoch: binding.provider_epoch })), AbortSignal.timeout(5_000)); + if (notify && !this.serverRevoked && this.client.getSnapshot().state === 'connected' && !this.unbinding) { + this.unbinding = this.client.call('browser.provider.unbind', { root_id: this.offer.root_id, provider_epoch: binding.provider_epoch }, { timeoutMs: 5_000 }); + } + await Promise.all([native, this.unbinding]); + } +} +//# sourceMappingURL=browser.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/browser.js.map b/vendor/@whip/sdk/dist/browser.js.map new file mode 100644 index 0000000..d6fa5f5 --- /dev/null +++ b/vendor/@whip/sdk/dist/browser.js.map @@ -0,0 +1 @@ +{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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","sourcesContent":["import { assertValid, type BrowserInventoryRequest, type BrowserInventoryResultParams, type BrowserCommand, type BrowserCommandCancel, type BrowserCommandResultParams, type BrowserProviderBindParams, type BrowserProviderBindResult, type BrowserProviderEventParams } from '@whip/protocol';\nimport type { CallOptions, WhipClient } from './client.js';\nimport { RpcError, WhipError, asError } from './errors.js';\nimport { upload } from './content.js';\nimport { byteLength, withSignal } from './util.js';\n\n/** Structural native adapter. SDK never imports Electron or application packages. */\nexport interface BrowserProviderBridge {\n select(input: { offer: BrowserProviderBindParams; provider: BrowserProviderBindResult; connectionId?: string; projectId?: string }): Promise;\n inventory?(request: BrowserInventoryRequest): Promise;\n dispatch(command: BrowserCommand): Promise;\n cancel(input: BrowserCommandCancel): void;\n release(input: { rootId: string; providerEpoch: string }): Promise;\n onEvent(listener: (event: { kind: string; event?: BrowserProviderEventParams }) => void): () => void;\n}\nexport interface BrowserSelectionOptions extends CallOptions {\n connectionId?: string;\n projectId?: string;\n onError?(error: Error): void;\n}\nexport interface BrowserSelection {\n readonly provider: Readonly;\n readonly active: boolean;\n /** Releases this exact epoch only. It never closes human Browser tabs. */\n release(): Promise;\n}\n\n/** Explicit root association, not a default/newest-wins browser destination. */\nexport class BrowserProviders {\n private readonly roots = new Map();\n constructor(private readonly client: WhipClient) {\n client.onNotification('browser.inventory', request => this.roots.get(request.root_id)?.inventory(request));\n client.onNotification('browser.command', command => this.roots.get(command.root_id)?.receive(command));\n client.onNotification('browser.command.cancel', command => this.roots.get(command.root_id)?.cancel(command));\n client.onNotification('browser.provider.revoked', event => this.roots.get(event.root_id)?.revoke(event.provider_epoch, event.reason));\n client.subscribe(() => {\n if (client.getSnapshot().state !== 'connected') {\n for (const selection of this.roots.values()) selection.disconnected();\n }\n });\n }\n async select(input: BrowserProviderBindParams, bridge: BrowserProviderBridge, options: BrowserSelectionOptions = {}): Promise {\n this.client.requireConnected();\n const offer = structuredClone(input);\n assertValid('BrowserProviderBindParams', offer);\n if (this.roots.has(offer.root_id)) throw new WhipError('invalid_arguments', 'Release the existing Browser provider before selecting another');\n const selection = new SelectedBrowser(this.client, offer, bridge, { ...options }, () => {\n if (this.roots.get(offer.root_id) === selection) this.roots.delete(offer.root_id);\n });\n this.roots.set(offer.root_id, selection);\n return selection.start();\n }\n}\n\nclass SelectedBrowser {\n private binding?: BrowserProviderBindResult;\n private unbinding?: Promise;\n private live = true;\n private serverRevoked = false;\n private readonly pending = new Map();\n private readonly revoked = new Map();\n private readonly seen = new Set();\n private events = Promise.resolve();\n private inventoryCount = 0;\n private eventCount = 0;\n private eventBytes = 0;\n private readonly retiredEvents = new Set();\n private readonly lifetime = new AbortController();\n private readonly ready: Promise;\n private acknowledge!: () => void;\n private readonly off: () => void;\n constructor(private readonly client: WhipClient, private readonly offer: BrowserProviderBindParams, private readonly bridge: BrowserProviderBridge,\n private readonly options: BrowserSelectionOptions, private readonly removed: () => void) {\n this.ready = new Promise(resolve => { this.acknowledge = resolve; });\n this.events = this.ready;\n this.off = bridge.onEvent(message => {\n const event = message.event;\n if (message.kind !== 'provider' || !event || !this.matches(event.root_id, event.provider_epoch)) return;\n this.observe(event);\n });\n }\n private observe(event: BrowserProviderEventParams): void {\n const key = JSON.stringify([event.tab_id, event.tab_generation, event.attachment_id, event.attachment_generation]);\n if (this.retiredEvents.has(key)) return;\n try {\n // Snapshot once and bound retained payloads, not just the RPC frame size.\n const encoded = JSON.stringify(event);\n const bytes = byteLength(encoded);\n if (this.eventCount >= 64 || this.eventBytes + bytes > 2 * 1024 * 1024) {\n throw new WhipError('resource_limit', 'Browser observation queue exceeded its limit; select the provider again explicitly');\n }\n const snapshot: BrowserProviderEventParams = JSON.parse(encoded);\n this.eventCount++; this.eventBytes += bytes;\n // Preserve observation order. Dropping a live sequence would hide a gap.\n this.events = this.events.then(async () => {\n if (this.matches(snapshot.root_id, snapshot.provider_epoch) && !this.retiredEvents.has(key)) {\n await this.client.call('browser.provider.event', snapshot, { signal: this.lifetime.signal, timeoutMs: 10_000 });\n }\n }).catch(error => {\n if (error instanceof RpcError && error.kind === 'browser_event_stale') {\n // The broker authenticated the holder but this exact attachment retired.\n // Other attachments keep their independent authority and event sequence.\n if (this.retiredEvents.size >= 8192) return this.fail(new WhipError('resource_limit', 'Browser retired observation capacity reached'));\n this.retiredEvents.add(key);\n return;\n }\n if (this.live) return this.fail(error);\n }).finally(() => { this.eventCount--; this.eventBytes -= bytes; });\n } catch (error) { void this.fail(error); }\n }\n start(): Promise {\n return this.bind();\n }\n private async bind(): Promise {\n try {\n const signal = AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(this.options.timeoutMs ?? 30_000), ...(this.options.signal ? [this.options.signal] : [])]);\n const binding = await this.client.call('browser.provider.bind', this.offer, { ...this.options, signal });\n this.binding = binding;\n if (binding.version !== this.offer.version || (binding.version === 2 && !this.bridge.inventory)) throw new WhipError('unavailable_capability', 'Browser discovery requires an updated native Desktop');\n const revoked = this.revoked.get(binding.provider_epoch);\n if (revoked !== undefined) { this.revoke(binding.provider_epoch, revoked); throw new WhipError('unavailable_capability', 'Browser provider was revoked before selection completed'); }\n this.revoked.clear();\n if (!this.live) { await this.release(); throw new WhipError('disconnected', 'Browser selection was interrupted'); }\n const native = this.bridge.select({ offer: this.offer, provider: binding,\n ...(this.options.connectionId ? { connectionId: this.options.connectionId } : {}),\n ...(this.options.projectId ? { projectId: this.options.projectId } : {}) });\n // A timed-out native invocation cannot be cancelled or replayed. A late ACK\n // must release its exact epoch without touching any replacement selection.\n void native.then(() => {\n if (signal.aborted || !this.live) return this.bridge.release({ rootId: this.offer.root_id, providerEpoch: binding.provider_epoch });\n }).catch(error => { if (!this.live) this.report(error); });\n await withSignal(native, signal);\n signal.throwIfAborted();\n this.acknowledge();\n const selected = this;\n return { provider: Object.freeze({ ...binding }), get active() { return selected.live; }, release: () => selected.release() };\n } catch (error) { void this.release().catch(releaseError => this.report(releaseError)); throw error; }\n }\n private matches(root: string, epoch: string): boolean { return this.live && root === this.offer.root_id && epoch === this.binding?.provider_epoch; }\n inventory(request: BrowserInventoryRequest): void {\n if (!this.live || this.inventoryCount >= 32) return;\n this.inventoryCount++;\n void this.ready.then(async () => {\n if (!this.matches(request.root_id, request.provider_epoch)) return;\n let result: BrowserInventoryResultParams;\n try {\n if (!this.bridge.inventory) throw new WhipError('unavailable_capability', 'Native tab discovery is unavailable');\n result = await withSignal(this.bridge.inventory(request), AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(10_000)]));\n assertValid('BrowserInventoryResultParams', result);\n if (result.request_id !== request.request_id || result.root_id !== request.root_id || result.provider_epoch !== request.provider_epoch || byteLength(JSON.stringify(result)) > 64 * 1024) {\n throw new WhipError('invalid_arguments', 'Native inventory identity or size mismatch');\n }\n } catch {\n result = { request_id: request.request_id, root_id: request.root_id, provider_epoch: request.provider_epoch, tabs: [], error: { kind: 'desktop_unavailable', message: 'Native inventory could not complete safely' } };\n }\n if (this.matches(request.root_id, request.provider_epoch)) await this.client.call('browser.inventory.result', result, { signal: this.lifetime.signal, timeoutMs: 10_000 });\n }).catch(() => {\n // A metadata reply may outlive the caller's cancelled/expired request.\n // Never replay it or change an independent control association's state.\n }).finally(() => { this.inventoryCount--; });\n }\n receive(command: BrowserCommand): void {\n // Commands may race the bind reply; never dispatch until native selection ACKs.\n if (!this.live || this.seen.has(command.command_id)) return;\n if (this.seen.size >= 8192 || this.pending.size >= 32) { void this.fail(new WhipError('resource_limit', 'Browser provider command capacity reached')); return; }\n this.seen.add(command.command_id);\n const state = { cancelled: false, command };\n this.pending.set(command.command_id, state);\n void this.ready.then(async () => {\n if (state.cancelled || !this.matches(command.root_id, command.provider_epoch)) return;\n let result: BrowserCommandResultParams;\n try {\n if (BigInt(command.deadline_millis) <= BigInt(Date.now())) {\n result = this.failure(command, 'deadline_exceeded', 'Browser command expired before native delivery');\n } else {\n const native = await this.bridge.dispatch(command);\n const { screenshotBytes, ...wire } = native;\n assertValid('BrowserCommandResultParams', wire, 'response');\n if (wire.command_id !== command.command_id || wire.root_id !== command.root_id || wire.provider_epoch !== command.provider_epoch || wire.attachment_generation !== (command.scope.attachment_generation ?? '')) {\n throw new WhipError('invalid_response', 'Native Browser result identity changed');\n }\n result = wire;\n if (screenshotBytes) {\n if (wire.screenshot || screenshotBytes.byteLength > 8 * 1024 * 1024 || screenshotBytes.byteLength < 3 || screenshotBytes[0] !== 0xff || screenshotBytes[1] !== 0xd8 || screenshotBytes[2] !== 0xff) throw new WhipError('invalid_response', 'Invalid native Browser screenshot');\n if (state.cancelled || !this.matches(command.root_id, command.provider_epoch)) return;\n const content = await upload(this.client, new Uint8Array(screenshotBytes), { rootId: command.root_id, agentId: command.agent_id, mediaType: 'image/jpeg', source: 'desktop-browser', signal: this.lifetime.signal, timeoutMs: Math.max(1, Math.min(30_000, Number(BigInt(command.deadline_millis) - BigInt(Date.now())))) }, 'connection');\n result = { ...wire, screenshot: content.handle };\n }\n }\n } catch (error) {\n // Native delivery may already have changed the page. Never replay it.\n result = this.failure(command, 'outcome_unknown', asError(error).message);\n }\n if (!state.cancelled && this.matches(command.root_id, command.provider_epoch)) await this.client.call('browser.command.result', result);\n }).catch(error => {\n // An exact cancellation may arrive after result submission but before its\n // rejection. Retired work must not tear down other attachments or epochs.\n if (!state.cancelled && this.matches(command.root_id, command.provider_epoch)) return this.fail(error);\n }).finally(() => this.pending.delete(command.command_id));\n }\n cancel(command: BrowserCommandCancel): void {\n if (command.root_id !== this.offer.root_id || (this.binding && command.provider_epoch !== this.binding.provider_epoch)) return;\n const pending = this.pending.get(command.command_id);\n if (!pending || pending.command.provider_epoch !== command.provider_epoch || (pending.command.scope.attachment_generation ?? '') !== command.attachment_generation) return;\n pending.cancelled = true;\n if (this.matches(command.root_id, command.provider_epoch)) this.bridge.cancel(command);\n }\n private failure(command: BrowserCommand, kind: string, message: string): BrowserCommandResultParams {\n return { command_id: command.command_id, root_id: command.root_id, provider_epoch: command.provider_epoch,\n attachment_generation: command.scope.attachment_generation ?? '', document_revision: command.expected_document ?? '', error: { kind, message: message.slice(0, 2048) } };\n }\n revoke(epoch: string, reason: string): void {\n if (!this.live) return;\n if (!this.binding) {\n if (this.revoked.size >= 8) { void this.fail(new WhipError('resource_limit', 'Browser selection revocation capacity reached')); return; }\n this.revoked.set(epoch, reason);\n return;\n }\n if (this.binding.provider_epoch !== epoch) return;\n this.serverRevoked = true;\n this.disconnected();\n this.report(new WhipError('unavailable_capability', `Browser provider was revoked (${reason.slice(0, 128)}); select a provider again explicitly`));\n }\n disconnected(): void { void this.release(false).catch(error => this.report(error)); }\n private report(error: unknown): void {\n // An observer cannot break teardown or create an unhandled command rejection.\n try { this.options.onError?.(asError(error)); } catch { /* observer failure */ }\n }\n private async fail(error: unknown): Promise {\n const cleanup = this.release();\n this.report(error);\n try { await cleanup; } catch (releaseError) { this.report(releaseError); }\n }\n async release(notify = true): Promise {\n this.live = false;\n this.lifetime.abort(new WhipError('disconnected', 'Browser selection was released'));\n this.acknowledge();\n this.off();\n this.removed();\n for (const pending of this.pending.values()) pending.cancelled = true;\n const binding = this.binding;\n if (!binding) return;\n const native = withSignal(Promise.resolve().then(() => this.bridge.release({ rootId: this.offer.root_id, providerEpoch: binding.provider_epoch })), AbortSignal.timeout(5_000));\n if (notify && !this.serverRevoked && this.client.getSnapshot().state === 'connected' && !this.unbinding) {\n this.unbinding = this.client.call('browser.provider.unbind', { root_id: this.offer.root_id, provider_epoch: binding.provider_epoch }, { timeoutMs: 5_000 });\n }\n await Promise.all([native, this.unbinding]);\n }\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/client.d.ts b/vendor/@whip/sdk/dist/client.d.ts new file mode 100644 index 0000000..294e017 --- /dev/null +++ b/vendor/@whip/sdk/dist/client.d.ts @@ -0,0 +1,164 @@ +import { type CommandOperation, type CommandResult, type EphemeralOperation, type InitializeResult, type QueryOperation, type QueryResult, type RootEvent, type RpcMethod, type RpcMethods, type RuntimeOperation, type RuntimeOperations, type HookInvokeParams, type ToolCancelParams, type ToolInvokeParams, type TerminalDetachedParams, type TerminalExitedParams, type TerminalOutputParams, type BrowserInventoryRequest, type BrowserCommand, type BrowserCommandCancel, type BrowserProviderRevoked } from '@whip/protocol'; +import { CommandHandle, type CommandOptions, type RecoveryRecord, type RecoveryStorage, type CommandOutcome } from './command.js'; +import { ContentReference, type ContentScope, type UploadOptions } from './content.js'; +import { Host, Permissions, Providers, Configuration, MCPImport } from './services.js'; +import { Agents } from './agents.js'; +import { Terminals } from './terminals.js'; +import { BrowserProviders } from './browser.js'; +import { Session, Sessions } from './session.js'; +import { Subscription, type SubscriptionOptions } from './subscription.js'; +import { type Transport, type TransportFactory } from './transport.js'; +export type SdkEvent = RootEvent; +/** Notifications the daemon addresses to one connection: executor leases and terminal attachments. */ +export interface Notifications { + 'browser.inventory': BrowserInventoryRequest; + 'browser.command': BrowserCommand; + 'browser.command.cancel': BrowserCommandCancel; + 'browser.provider.revoked': BrowserProviderRevoked; + 'tool.invoke': ToolInvokeParams; + 'tool.cancel': ToolCancelParams; + 'hook.invoke': HookInvokeParams; + 'hook.cancel': ToolCancelParams; + 'terminal.output': TerminalOutputParams; + 'terminal.exited': TerminalExitedParams; + 'terminal.detached': TerminalDetachedParams; +} +export type Notification = keyof Notifications; +/** Notifications the daemon sends to the connection holding an executor lease. */ +export type ExecutorNotifications = Pick; +export type ExecutorNotification = keyof ExecutorNotifications; +export type ConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'incompatible' | 'paused' | 'closed'; +export interface ConnectionSnapshot { + readonly state: ConnectionState; + readonly info?: Readonly; + readonly error?: Error; +} +export interface CallOptions { + signal?: AbortSignal; + timeoutMs?: number; +} +export interface ClientOptions { + endpoint: string | TransportFactory; + clientId: string; + clientKind?: 'human' | 'automation'; + /** Refuse attachment to another installation before exposing connected state. */ + expectedRuntimeId?: string; + buildId?: string; + /** Advertise native Browser provider support; selection remains explicit per root. */ + browserProvider?: boolean; + reconnect?: boolean; + queryTimeoutMs?: number; + connectTimeoutMs?: number; + heartbeatIntervalMs?: number; + heartbeatTimeoutMs?: number; + commandPollMs?: number; + recoveryStorage?: RecoveryStorage; + /** Native runtimes can supply OS-backed primitives without global polyfills. */ + randomUUID?: () => string; + sha256?: (bytes: Uint8Array) => Promise>; +} +export type QueryOutcome = Omit & { + result?: RuntimeOperations[O]['result']; +}; +/** Owns one connection. It never starts, stops, or implicitly replaces a daemon. */ +export declare class WhipClient { + readonly clientId: string; + readonly clientKind: 'human' | 'automation'; + readonly recoveryStorage?: RecoveryStorage; + readonly sessions: Sessions; + readonly providers: Providers; + readonly configuration: Configuration; + readonly mcpImport: MCPImport; + readonly permissions: Permissions; + readonly host: Host; + readonly agents: Agents; + readonly terminals: Terminals; + readonly browser: BrowserProviders; + readonly events: { + subscribe: (rootId: string, cursor: string, options?: SubscriptionOptions) => Promise; + replay: (rootId: string, cursor: string, options?: CallOptions & { + limit?: number; + }) => Promise<{ + events: RootEvent[]; + latest: string; + expired?: boolean; + }>; + }; + private readonly options; + private readonly factory; + private snapshot; + private readonly listeners; + private readonly eventListeners; + private readonly commandListeners; + private readonly notificationListeners; + private readonly pending; + private readonly streams; + private readonly lookups; + private readonly ticks; + private ticker?; + private connection?; + private controller?; + private opening?; + private heartbeat?; + private retryTimer?; + private retries; + private epoch; + private nextId; + private closed; + private paused; + private runtimeId?; + constructor(options: ClientOptions); + getSnapshot: () => ConnectionSnapshot; + subscribe: (listener: () => void) => (() => void); + onEvent(listener: (event: SdkEvent) => void): () => void; + onCommand(listener: (outcome: CommandResult) => void): () => void; + /** Notifications addressed to this connection; agents.serve and terminals consume them. */ + onNotification(method: M, listener: (params: Notifications[M]) => void): () => void; + session(rootId: string): Session; + get transportKind(): Transport['kind'] | undefined; + get httpEndpoint(): string | undefined; + get lifetimeSignal(): AbortSignal; + connect(options?: Pick): Promise; + private open; + private setState; + private disconnected; + /** Refreshes a connection without replaying pending requests. */ + reconnect(): void; + /** Suspend observation without cancelling accepted work or discarding command identities. */ + pause(): void; + /** Resume the same runtime; commands are reconciled, never resubmitted. */ + resume(options?: Pick): Promise; + createId(): string; + digestHex(bytes: Uint8Array): Promise; + close(): void; + requireConnected(): Readonly; + whenConnected(signal?: AbortSignal): Promise>; + supports(surface: 'rpc' | 'runtime', name: string): boolean; + call(method: M, params: RpcMethods[M]['params'], options?: CallOptions): Promise; + /** Internal exact JSON path for command retries. */ + callEncoded(method: M, paramsJSON: string, options?: CallOptions): Promise; + private dispatch; + private validateNested; + private receive; + private decodeEvent; + private scheduleHeartbeat; + releaseSubscription(id: string): void; + query(operation: O, payload: RuntimeOperations[O]['params'], options?: CallOptions & { + rootId?: string; + }): Promise>; + invoke(operation: O, payload: RuntimeOperations[O]['params'], options?: CallOptions & { + rootId?: string; + }): Promise>; + private runtimeCall; + submit(operation: O, payload: RuntimeOperations[O]['params'], options?: CommandOptions): CommandHandle; + recover(record: RecoveryRecord, payload?: RuntimeOperations[O]['params']): CommandHandle; + recoveryRecords(): Promise; + forget(record: RecoveryRecord): Promise; + commandStatus(record: RecoveryRecord, options?: CallOptions): Promise>; + commandOutcome(record: RecoveryRecord, result: CommandResult): CommandOutcome; + waitForCommandTick(signal?: AbortSignal): Promise; + private wakeCommands; + content(handle: ConstructorParameters[1], scope: ContentScope): ContentReference; + upload(bytes: Uint8Array, options: UploadOptions): Promise; +} +export declare function createWhipClient(options: ClientOptions): WhipClient; diff --git a/vendor/@whip/sdk/dist/client.js b/vendor/@whip/sdk/dist/client.js new file mode 100644 index 0000000..0e52249 --- /dev/null +++ b/vendor/@whip/sdk/dist/client.js @@ -0,0 +1,532 @@ +import { assertValid, manifest, rpcOperations, runtimeOperations, validate, } from '@whip/protocol'; +import { CommandHandle } from './command.js'; +import { ContentReference, upload } from './content.js'; +import { Host, Permissions, Providers, Configuration, MCPImport } from './services.js'; +import { Agents } from './agents.js'; +import { Terminals } from './terminals.js'; +import { BrowserProviders } from './browser.js'; +import { Session, Sessions } from './session.js'; +import { Subscription } from './subscription.js'; +import { WhipError, RpcError, abortError, asError } from './errors.js'; +import { webSocket } from './transport.js'; +import { byteLength, frozen, notify, object, withSignal, uuid, digestHex } from './util.js'; +/** Owns one connection. It never starts, stops, or implicitly replaces a daemon. */ +export class WhipClient { + clientId; + clientKind; + recoveryStorage; + sessions; + providers; + configuration; + mcpImport; + permissions; + host; + agents; + terminals; + browser; + events = { + subscribe: async (rootId, cursor, options = {}) => { + this.requireConnected(); + const limit = this.snapshot.info.limits.root_subscriptions; + if (this.streams.size >= limit) + throw new WhipError('resource_limit', 'Root subscription limit reached'); + const stream = new Subscription(this, rootId, cursor, options); + // Register before sending: notifications may arrive before the acknowledgement. + this.streams.set(stream.id, stream); + try { + await stream.start(options.signal); + return stream; + } + catch (error) { + stream.fail(asError(error)); + throw error; + } + }, + replay: async (rootId, cursor, options = {}) => { + const page = await this.call('events.replay', { root_id: rootId, cursor, limit: options.limit ?? 1000 }, options); + return { ...page, events: (page.events ?? []).map(event => this.decodeEvent(event)) }; + }, + }; + options; + factory; + snapshot = Object.freeze({ state: 'closed' }); + listeners = new Set(); + eventListeners = new Set(); + commandListeners = new Set(); + notificationListeners = new Map(); + pending = new Map(); + streams = new Map(); + lookups = new Map(); + ticks = new Set(); + ticker; + connection; + controller; + opening; + heartbeat; + retryTimer; + retries = 0; + epoch = 0; + nextId = 0; + closed = false; + paused = false; + runtimeId; + constructor(options) { + if (!options.clientId.trim()) + throw new TypeError('clientId is required; persist it to recover command identities'); + for (const value of [options.queryTimeoutMs, options.connectTimeoutMs, options.heartbeatIntervalMs, options.heartbeatTimeoutMs, options.commandPollMs]) { + if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) + throw new TypeError('Timeouts and polling intervals must be positive integer milliseconds'); + } + this.options = { ...options }; + this.clientId = options.clientId; + this.clientKind = options.clientKind ?? 'automation'; + this.recoveryStorage = options.recoveryStorage; + this.factory = typeof options.endpoint === 'string' ? webSocket(options.endpoint) : options.endpoint; + this.sessions = new Sessions(this); + this.providers = new Providers(this); + this.configuration = new Configuration(this); + this.mcpImport = new MCPImport(this); + this.permissions = new Permissions(this); + this.host = new Host(this); + this.agents = new Agents(this); + this.terminals = new Terminals(this); + this.browser = new BrowserProviders(this); + } + getSnapshot = () => this.snapshot; + subscribe = (listener) => { this.listeners.add(listener); return () => this.listeners.delete(listener); }; + onEvent(listener) { this.eventListeners.add(listener); return () => this.eventListeners.delete(listener); } + onCommand(listener) { this.commandListeners.add(listener); return () => this.commandListeners.delete(listener); } + /** Notifications addressed to this connection; agents.serve and terminals consume them. */ + onNotification(method, listener) { + let listeners = this.notificationListeners.get(method); + if (!listeners) { + listeners = new Set(); + this.notificationListeners.set(method, listeners); + } + const typed = listener; + listeners.add(typed); + return () => { listeners.delete(typed); }; + } + session(rootId) { return new Session(this, rootId); } + get transportKind() { return this.connection?.kind; } + get httpEndpoint() { return this.connection?.httpEndpoint; } + get lifetimeSignal() { this.requireConnected(); return this.controller.signal; } + async connect(options = {}) { + options.signal?.throwIfAborted(); + if (this.closed) + throw new WhipError('closed', 'Client is closed; create a new client to attach again'); + if (this.snapshot.state === 'incompatible') + throw this.snapshot.error; + if (this.paused) + throw new WhipError('paused', 'Client observation is paused; resume it before connecting'); + if (this.snapshot.state === 'connected') + return; + if (!this.opening) { + clearTimeout(this.retryTimer); + this.retryTimer = undefined; + this.opening = this.open().finally(() => { this.opening = undefined; }); + } + return withSignal(this.opening, options.signal); + } + async open() { + const epoch = ++this.epoch; + const controller = new AbortController(); + this.controller = controller; + this.setState(this.runtimeId ? 'reconnecting' : 'connecting'); + const timer = setTimeout(() => controller.abort(new WhipError('timeout', 'Daemon initialization timed out')), this.options.connectTimeoutMs ?? 5000); + try { + const connection = await this.factory({ + message: message => { if (epoch === this.epoch) + this.receive(message); }, + close: error => { if (epoch === this.epoch) + this.disconnected(error); }, + }, controller.signal); + if (epoch !== this.epoch || this.closed || controller.signal.aborted) { + connection.close(); + throw abortError(controller.signal); + } + this.connection = connection; + const info = await this.dispatch('initialize', { + protocol_major: manifest.major, client_id: this.clientId, client_kind: this.clientKind, + build_id: this.options.buildId ?? '@whip/sdk', capabilities: ['commands', 'events', 'snapshots', 'uploads', 'history_pages', 'collections', 'host_configuration', 'workspace_completion', 'host_skill_completion', 'host_global_skill_completion', 'skill_catalog_completion', 'host_views', 'themes', 'mailbox_inspection', 'input_attachments', 'session_summaries', 'execution_engines', ...(this.options.browserProvider ? ['desktop-browser-v1', 'desktop-browser-v2'] : [])], + }, { signal: controller.signal }, true); + if (epoch !== this.epoch || this.closed || controller.signal.aborted) + throw abortError(controller.signal); + if (info.protocol_major !== manifest.major) + throw new WhipError('unsupported_protocol', 'Daemon protocol major is incompatible'); + const expectedRuntime = this.runtimeId ?? this.options.expectedRuntimeId; + if (expectedRuntime && expectedRuntime !== info.runtime_id) + throw new WhipError('runtime_changed', 'The endpoint now serves a different runtime; create a new client explicitly'); + if (!info.runtime_id || info.limits.frame_bytes < 1 || info.limits.in_flight_requests < 1 || BigInt(info.limits.outbound_bytes) < 1n || info.limits.root_subscriptions < 1 || info.limits.content_chunk_bytes < 1 || BigInt(info.limits.upload_bytes) < 0n) { + throw new WhipError('invalid_response', 'Daemon reported invalid identity or limits'); + } + this.runtimeId = info.runtime_id; + this.retries = 0; + this.setState('connected', undefined, frozen(info)); + this.scheduleHeartbeat(); + } + catch (value) { + const error = asError(value); + if (epoch === this.epoch) + this.disconnected(error); + throw error; + } + finally { + clearTimeout(timer); + } + } + setState(state, error, info = this.snapshot.info) { + this.snapshot = Object.freeze({ state, ...(info ? { info } : {}), ...(error ? { error } : {}) }); + notify(this.listeners, undefined); + this.wakeCommands(); + } + disconnected(error) { + ++this.epoch; + const connection = this.connection; + this.connection = undefined; + this.controller?.abort(error); + connection?.close(); + clearTimeout(this.heartbeat); + for (const pending of this.pending.values()) + pending.reject(error); + this.pending.clear(); + this.lookups.clear(); + for (const stream of [...this.streams.values()]) + stream.fail(error); + if (this.closed) + return; + if (this.paused) { + clearTimeout(this.retryTimer); + this.retryTimer = undefined; + this.setState('paused'); + return; + } + const incompatible = error instanceof WhipError && ['unsupported_protocol', 'runtime_changed'].includes(error.kind); + this.setState(incompatible ? 'incompatible' : this.options.reconnect === false ? 'closed' : 'reconnecting', error); + if (!incompatible && this.options.reconnect !== false) { + clearTimeout(this.retryTimer); + const delay = Math.min(10_000, 250 * 2 ** Math.min(this.retries++, 6)) * (0.75 + Math.random() * 0.5); + this.retryTimer = setTimeout(() => { void this.connect().catch(() => { }); }, delay); + } + } + /** Refreshes a connection without replaying pending requests. */ + reconnect() { + this.requireConnected(); + this.disconnected(new WhipError('disconnected', 'Refreshing daemon connection')); + } + /** Suspend observation without cancelling accepted work or discarding command identities. */ + pause() { + if (this.closed || this.paused || this.snapshot.state === 'incompatible') + return; + this.paused = true; + this.disconnected(new WhipError('paused', 'Client observation paused')); + } + /** Resume the same runtime; commands are reconciled, never resubmitted. */ + async resume(options = {}) { + options.signal?.throwIfAborted(); + this.paused = false; + const resuming = (async () => { + // A paused initialization may still be unwinding its aborted transport. + await this.opening?.catch(() => { }); + await this.connect(); + })(); + return withSignal(resuming, options.signal); + } + createId() { return (this.options.randomUUID ?? uuid)(); } + async digestHex(bytes) { + if (!this.options.sha256) + return digestHex(bytes); + const digest = await this.options.sha256(bytes); + if (digest.byteLength !== 32) + throw new WhipError('invalid_response', 'SHA-256 provider returned an invalid digest'); + return Array.from(digest, byte => byte.toString(16).padStart(2, '0')).join(''); + } + close() { + if (this.closed) + return; + this.closed = true; + clearTimeout(this.retryTimer); + clearTimeout(this.heartbeat); + clearTimeout(this.ticker); + this.disconnected(new WhipError('closed', 'Client closed')); + this.setState('closed'); + this.listeners.clear(); + this.eventListeners.clear(); + this.commandListeners.clear(); + } + requireConnected() { + if (this.snapshot.state !== 'connected' || !this.connection) + throw this.snapshot.error ?? new WhipError('disconnected', 'Connect to the daemon before sending requests'); + return this.snapshot.info; + } + async whenConnected(signal) { + signal?.throwIfAborted(); + if (this.snapshot.state === 'connected') + return this.snapshot.info; + if (this.closed || this.snapshot.state === 'incompatible' || this.snapshot.state === 'closed') + throw this.snapshot.error ?? new WhipError('closed', 'Client is not connected'); + return new Promise((resolve, reject) => { + const cleanup = () => { unsubscribe(); signal?.removeEventListener('abort', abort); }; + const check = () => { + if (this.snapshot.state === 'connected') { + cleanup(); + resolve(this.snapshot.info); + } + else if (['closed', 'incompatible'].includes(this.snapshot.state)) { + cleanup(); + reject(this.snapshot.error ?? new WhipError('closed', 'Client closed')); + } + }; + const abort = () => { cleanup(); reject(abortError(signal)); }; + const unsubscribe = this.subscribe(check); + signal?.addEventListener('abort', abort, { once: true }); + if (signal?.aborted) + abort(); + else + check(); + }); + } + supports(surface, name) { + return this.snapshot.info?.operations?.some(operation => operation.surface === surface && operation.name === name) ?? false; + } + call(method, params, options = {}) { + return this.dispatch(method, params, options); + } + /** Internal exact JSON path for command retries. */ + callEncoded(method, paramsJSON, options = {}) { + return this.dispatch(method, JSON.parse(paramsJSON), options, false, paramsJSON); + } + async dispatch(method, params, options = {}, initializing = false, encoded) { + options.signal?.throwIfAborted(); + if (!initializing) + this.requireConnected(); + if (!Object.hasOwn(rpcOperations, method)) + throw new WhipError('unsupported_operation', 'Unknown RPC method'); + if (!initializing && !this.supports('rpc', method)) + throw new WhipError('unsupported_operation', `Daemon does not support ${method}`); + const metadata = rpcOperations[method]; + assertValid(metadata.params_type, params); + this.validateNested(method, params); + const limit = this.snapshot.info?.limits; + if (this.pending.size >= (limit?.in_flight_requests ?? 32)) + throw new WhipError('resource_limit', 'In-flight request limit reached'); + const id = String(++this.nextId); + const data = `{"jsonrpc":"2.0","id":${JSON.stringify(id)},"method":${JSON.stringify(method)},"params":${encoded ?? JSON.stringify(params)}}`; + const connection = this.connection; + const bytes = byteLength(data) + (connection.kind === 'unix' ? 1 : 0); + if (bytes > (limit?.frame_bytes ?? 1 << 20)) + throw new WhipError('resource_limit', 'Request exceeds the frame limit; upload large content separately'); + if (BigInt(connection.bufferedAmount + bytes) > BigInt(limit?.outbound_bytes ?? 8 << 20)) + throw new WhipError('resource_limit', 'Outbound byte limit reached'); + return new Promise((resolve, reject) => { + const cleanup = () => { clearTimeout(timer); options.signal?.removeEventListener('abort', abort); this.pending.delete(id); }; + const fail = (error) => { cleanup(); reject(error); }; + const abort = () => fail(abortError(options.signal)); + const timer = setTimeout(() => fail(new WhipError('timeout', `Request timed out: ${method}`)), options.timeoutMs ?? this.options.queryTimeoutMs ?? 10_000); + this.pending.set(id, { method, resolve: value => { cleanup(); resolve(value); }, reject: fail }); + options.signal?.addEventListener('abort', abort, { once: true }); + try { + connection.send(data); + } + catch (error) { + fail(asError(error)); + } + }); + } + validateNested(method, value) { + if (!['command.submit', 'query', 'operation.invoke'].includes(method) || !object(value)) + return; + const operation = value.operation; + if (!Object.hasOwn(runtimeOperations, operation)) + throw new WhipError('unsupported_operation', 'Unknown runtime operation'); + const metadata = runtimeOperations[operation]; + const expected = method === 'command.submit' ? 'command' : method === 'query' ? 'query' : 'ephemeral'; + if (metadata.execution !== expected) + throw new WhipError('invalid_arguments', `Operation must use the ${metadata.execution} interface`); + if (!this.supports('runtime', operation)) + throw new WhipError('unsupported_operation', `Daemon does not support ${operation}`); + assertValid(metadata.params_type, value.payload ?? {}); + } + receive(text) { + try { + if (byteLength(text) > (this.snapshot.info?.limits.frame_bytes ?? 1 << 20)) + throw new WhipError('resource_limit', 'Received message exceeds the frame limit'); + const envelope = JSON.parse(text); + if (!object(envelope) || envelope.jsonrpc !== '2.0') + throw new WhipError('invalid_response', 'Invalid JSON-RPC envelope'); + if (Object.hasOwn(envelope, 'id')) { + if (typeof envelope.id !== 'string') + throw new WhipError('invalid_response', 'Response ID must match the string request ID'); + const pending = this.pending.get(envelope.id); + if (!pending) + return; // Late reply after cancellation/timeout. + const hasError = Object.hasOwn(envelope, 'error'); + if (hasError === Object.hasOwn(envelope, 'result')) + throw new WhipError('invalid_response', 'Expected one response result or error'); + if (hasError) { + assertValid('RPCError', envelope.error, 'response'); + pending.reject(new RpcError(envelope.error)); + } + else { + // Older compatible hosts predate MCP import/logo settings. Supply only + // absent fields for features they do not advertise; malformed values + // and hosts claiming those features still use strict validation. + if (rpcOperations[pending.method].result_type === 'RuntimeConfiguration' && object(envelope.result)) { + if (!Object.hasOwn(envelope.result, 'mcp_import_offered') && !this.supports('rpc', 'mcp.import.candidates')) + envelope.result.mcp_import_offered = true; + if (!Object.hasOwn(envelope.result, 'brand_icons') && !this.supports('rpc', 'mcp.brand.icons')) + envelope.result.brand_icons = false; + } + assertValid(rpcOperations[pending.method].result_type, envelope.result, 'response'); + pending.resolve(frozen(envelope.result)); + } + return; + } + if (typeof envelope.method !== 'string') + throw new WhipError('invalid_response', 'Notification method is missing'); + if (envelope.method === 'event') { + assertValid('EventNotification', envelope.params, 'response'); + const event = this.decodeEvent(envelope.params.event); + const stream = event.subscription_id ? this.streams.get(event.subscription_id) : undefined; + if (!stream) + return; + stream.push(event); + notify(this.eventListeners, event); + this.wakeCommands(); + } + else if (envelope.method === 'subscription.failed') { + assertValid('SubscriptionFailure', envelope.params, 'response'); + const failure = envelope.params; + this.streams.get(failure.subscription_id)?.fail(failure.error ? new RpcError(failure.error) : new WhipError('resynchronization_required', 'Subscription failed')); + } + else if (Object.hasOwn(manifest.events, envelope.method)) { + // Connection-addressed notifications are typed by the generated contract; anything else is malformed. + assertValid(manifest.events[envelope.method], envelope.params, 'response'); + const listeners = this.notificationListeners.get(envelope.method); + if (listeners) + notify(listeners, frozen(envelope.params)); + } + } + catch (error) { + this.disconnected(new WhipError('invalid_response', 'Malformed daemon response', { cause: error })); + } + } + decodeEvent(input) { + const wrapper = { event: input }; + assertValid('EventNotification', wrapper, 'response'); + const value = wrapper.event; + const schema = Object.hasOwn(manifest.event_payloads, value.kind) ? manifest.event_payloads[value.kind] : undefined; + if (!schema) + return frozen({ ...value, payload: value.payload, unknown: true }); + if (!validate(schema, value.payload, 'response') && !validate('ContentEventPayload', value.payload, 'response')) + throw new WhipError('invalid_response', `Invalid ${value.kind} event payload`); + return frozen({ ...value, unknown: false }); + } + scheduleHeartbeat() { + clearTimeout(this.heartbeat); + if (this.snapshot.state !== 'connected') + return; + const epoch = this.epoch; + this.heartbeat = setTimeout(() => { + if (epoch !== this.epoch || this.closed) + return; + void this.call('daemon.ping', {}, { timeoutMs: this.options.heartbeatTimeoutMs ?? 10_000 }) + .then(() => { if (epoch === this.epoch && !this.closed) + this.scheduleHeartbeat(); }, error => { if (epoch === this.epoch && !this.closed) + this.disconnected(asError(error)); }); + }, this.options.heartbeatIntervalMs ?? 30_000); + } + releaseSubscription(id) { this.streams.delete(id); } + query(operation, payload, options = {}) { + return this.runtimeCall('query', operation, payload, options); + } + invoke(operation, payload, options = {}) { + return this.runtimeCall('operation.invoke', operation, payload, options); + } + async runtimeCall(method, operation, payload, options) { + const result = await this.call(method, { operation, payload, ...(options.rootId ? { root_id: options.rootId } : {}) }, options); + if (result.result !== undefined) + assertValid(runtimeOperations[operation].result_type, result.result, 'response'); + return result; + } + submit(operation, payload, options = {}) { + const info = this.requireConnected(); + if (!Object.hasOwn(runtimeOperations, operation) || runtimeOperations[operation].execution !== 'command') + throw new WhipError('invalid_arguments', 'Only durable runtime commands can be submitted'); + if (!this.supports('runtime', operation)) + throw new WhipError('unsupported_operation', `Daemon does not support ${operation}`); + assertValid(runtimeOperations[operation].params_type, payload); + return CommandHandle.submit(this, info.runtime_id, operation, payload, options); + } + recover(record, payload) { + if (record.version !== 1 || record.clientId !== this.clientId || !record.commandId || !Object.hasOwn(runtimeOperations, record.operation) || runtimeOperations[record.operation].execution !== 'command') + throw new WhipError('invalid_arguments', 'Invalid recovery record or command namespace'); + return CommandHandle.recover(this, record, payload); + } + async recoveryRecords() { return this.recoveryStorage ? this.recoveryStorage.list() : []; } + async forget(record) { + if (record.clientId !== this.clientId) + throw new WhipError('invalid_arguments', 'Recovery record belongs to a different client namespace'); + await this.recoveryStorage?.delete(record); + } + async commandStatus(record, options = {}) { + options.signal?.throwIfAborted(); + if (record.clientId !== this.clientId) + throw new WhipError('invalid_arguments', 'Command belongs to another client namespace'); + const info = await this.whenConnected(options.signal); + if (info.runtime_id !== record.runtimeId) + throw new WhipError('runtime_changed', 'Command belongs to a different persistent runtime'); + let pending = this.lookups.get(record.commandId); + if (!pending) { + pending = this.call('command.status', { command_id: record.commandId }); + this.lookups.set(record.commandId, pending); + void pending.finally(() => { if (this.lookups.get(record.commandId) === pending) + this.lookups.delete(record.commandId); }).catch(() => { }); + } + const result = await withSignal(pending, options.signal); + return this.commandOutcome(record, result); + } + commandOutcome(record, result) { + if (result.command_id !== record.commandId || result.operation !== record.operation || !['queued', 'running', 'waiting', 'succeeded', 'failed', 'cancelled', 'interrupted'].includes(result.status)) + throw new WhipError('invalid_response', 'Command response does not match its identity or lifecycle'); + if (result.status === 'succeeded' && result.result !== undefined) + assertValid(runtimeOperations[record.operation].result_type, result.result, 'response'); + const outcome = result; + notify(this.commandListeners, outcome); + return outcome; + } + waitForCommandTick(signal) { + if (this.closed) + return Promise.reject(new WhipError('closed', 'Client closed')); + return new Promise((resolve, reject) => { + const cleanup = () => { + this.ticks.delete(done); + signal?.removeEventListener('abort', abort); + if (this.ticks.size === 0) { + clearTimeout(this.ticker); + this.ticker = undefined; + } + }; + const done = () => { cleanup(); resolve(); }; + const abort = () => { cleanup(); reject(abortError(signal)); }; + this.ticks.add(done); + signal?.addEventListener('abort', abort, { once: true }); + if (signal?.aborted) { + abort(); + return; + } + if (!this.paused && !this.closed) + this.ticker ??= setTimeout(() => this.wakeCommands(), this.options.commandPollMs ?? 250); + }); + } + wakeCommands() { + clearTimeout(this.ticker); + this.ticker = undefined; + for (const done of [...this.ticks]) + done(); + } + content(handle, scope) { return new ContentReference(this, handle, scope); } + upload(bytes, options) { return upload(this, bytes, options); } +} +export function createWhipClient(options) { return new WhipClient(options); } +//# sourceMappingURL=client.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/client.js.map b/vendor/@whip/sdk/dist/client.js.map new file mode 100644 index 0000000..8e9ef51 --- /dev/null +++ b/vendor/@whip/sdk/dist/client.js.map @@ -0,0 +1 @@ +{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n assertValid, manifest, rpcOperations, runtimeOperations, validate,\n type CommandOperation, type CommandResult, type EphemeralOperation,\n type InitializeResult, type QueryOperation, type QueryResult,\n type RootEvent, type RpcMethod, type RpcMethods, type RuntimeOperation, type RuntimeOperations,\n type HookInvokeParams, type ToolCancelParams, type ToolInvokeParams,\n type TerminalDetachedParams, type TerminalExitedParams, type TerminalOutputParams,\n type BrowserInventoryRequest, type BrowserCommand, type BrowserCommandCancel, type BrowserProviderRevoked,\n} from '@whip/protocol';\nimport { CommandHandle, type CommandOptions, type RecoveryRecord, type RecoveryStorage, type CommandOutcome } from './command.js';\nimport { ContentReference, upload, type ContentScope, type UploadOptions } from './content.js';\nimport { Host, Permissions, Providers, Configuration, MCPImport } from './services.js';\nimport { Agents } from './agents.js';\nimport { Terminals } from './terminals.js';\nimport { BrowserProviders } from './browser.js';\nimport { Session, Sessions } from './session.js';\nimport { Subscription, type SubscriptionOptions } from './subscription.js';\nimport { WhipError, RpcError, abortError, asError } from './errors.js';\nimport { webSocket, type Transport, type TransportFactory } from './transport.js';\nimport { byteLength, frozen, notify, object, withSignal, uuid, digestHex } from './util.js';\n\nexport type SdkEvent = RootEvent;\n/** Notifications the daemon addresses to one connection: executor leases and terminal attachments. */\nexport interface Notifications {\n 'browser.inventory': BrowserInventoryRequest;\n 'browser.command': BrowserCommand; 'browser.command.cancel': BrowserCommandCancel;\n 'browser.provider.revoked': BrowserProviderRevoked;\n 'tool.invoke': ToolInvokeParams; 'tool.cancel': ToolCancelParams;\n 'hook.invoke': HookInvokeParams; 'hook.cancel': ToolCancelParams;\n 'terminal.output': TerminalOutputParams; 'terminal.exited': TerminalExitedParams; 'terminal.detached': TerminalDetachedParams;\n}\nexport type Notification = keyof Notifications;\n/** Notifications the daemon sends to the connection holding an executor lease. */\nexport type ExecutorNotifications = Pick;\nexport type ExecutorNotification = keyof ExecutorNotifications;\nexport type ConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'incompatible' | 'paused' | 'closed';\nexport interface ConnectionSnapshot {\n readonly state: ConnectionState;\n readonly info?: Readonly;\n readonly error?: Error;\n}\nexport interface CallOptions { signal?: AbortSignal; timeoutMs?: number }\nexport interface ClientOptions {\n endpoint: string | TransportFactory;\n clientId: string;\n clientKind?: 'human' | 'automation';\n /** Refuse attachment to another installation before exposing connected state. */\n expectedRuntimeId?: string;\n buildId?: string;\n /** Advertise native Browser provider support; selection remains explicit per root. */\n browserProvider?: boolean;\n reconnect?: boolean;\n queryTimeoutMs?: number;\n connectTimeoutMs?: number;\n heartbeatIntervalMs?: number;\n heartbeatTimeoutMs?: number;\n commandPollMs?: number;\n recoveryStorage?: RecoveryStorage;\n /** Native runtimes can supply OS-backed primitives without global polyfills. */\n randomUUID?: () => string;\n sha256?: (bytes: Uint8Array) => Promise>;\n}\nexport type QueryOutcome = Omit & { result?: RuntimeOperations[O]['result'] };\ninterface Pending {\n method: RpcMethod;\n resolve(value: unknown): void;\n reject(error: Error): void;\n}\n\n/** Owns one connection. It never starts, stops, or implicitly replaces a daemon. */\nexport class WhipClient {\n readonly clientId: string;\n readonly clientKind: 'human' | 'automation';\n readonly recoveryStorage?: RecoveryStorage;\n readonly sessions: Sessions;\n readonly providers: Providers;\n readonly configuration: Configuration;\n readonly mcpImport: MCPImport;\n readonly permissions: Permissions;\n readonly host: Host;\n readonly agents: Agents;\n readonly terminals: Terminals;\n readonly browser: BrowserProviders;\n readonly events = {\n subscribe: async (rootId: string, cursor: string, options: SubscriptionOptions = {}): Promise => {\n this.requireConnected();\n const limit = this.snapshot.info!.limits.root_subscriptions;\n if (this.streams.size >= limit) throw new WhipError('resource_limit', 'Root subscription limit reached');\n const stream = new Subscription(this, rootId, cursor, options);\n // Register before sending: notifications may arrive before the acknowledgement.\n this.streams.set(stream.id, stream);\n try { await stream.start(options.signal); return stream; }\n catch (error) { stream.fail(asError(error)); throw error; }\n },\n replay: async (rootId: string, cursor: string, options: CallOptions & { limit?: number } = {}) => {\n const page = await this.call('events.replay', { root_id: rootId, cursor, limit: options.limit ?? 1000 }, options);\n return { ...page, events: (page.events ?? []).map(event => this.decodeEvent(event)) };\n },\n };\n private readonly options: ClientOptions;\n private readonly factory: TransportFactory;\n private snapshot: ConnectionSnapshot = Object.freeze({ state: 'closed' });\n private readonly listeners = new Set<() => void>();\n private readonly eventListeners = new Set<(event: SdkEvent) => void>();\n private readonly commandListeners = new Set<(outcome: CommandResult) => void>();\n private readonly notificationListeners = new Map void>>();\n private readonly pending = new Map();\n private readonly streams = new Map();\n private readonly lookups = new Map>();\n private readonly ticks = new Set<() => void>();\n private ticker?: ReturnType;\n private connection?: Transport;\n private controller?: AbortController;\n private opening?: Promise;\n private heartbeat?: ReturnType;\n private retryTimer?: ReturnType;\n private retries = 0;\n private epoch = 0;\n private nextId = 0;\n private closed = false;\n private paused = false;\n private runtimeId?: string;\n\n constructor(options: ClientOptions) {\n if (!options.clientId.trim()) throw new TypeError('clientId is required; persist it to recover command identities');\n for (const value of [options.queryTimeoutMs, options.connectTimeoutMs, options.heartbeatIntervalMs, options.heartbeatTimeoutMs, options.commandPollMs]) {\n if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) throw new TypeError('Timeouts and polling intervals must be positive integer milliseconds');\n }\n this.options = { ...options };\n this.clientId = options.clientId;\n this.clientKind = options.clientKind ?? 'automation';\n this.recoveryStorage = options.recoveryStorage;\n this.factory = typeof options.endpoint === 'string' ? webSocket(options.endpoint) : options.endpoint;\n this.sessions = new Sessions(this);\n this.providers = new Providers(this);\n this.configuration = new Configuration(this);\n this.mcpImport = new MCPImport(this);\n this.permissions = new Permissions(this);\n this.host = new Host(this);\n this.agents = new Agents(this);\n this.terminals = new Terminals(this);\n this.browser = new BrowserProviders(this);\n }\n getSnapshot = (): ConnectionSnapshot => this.snapshot;\n subscribe = (listener: () => void): (() => void) => { this.listeners.add(listener); return () => this.listeners.delete(listener); };\n onEvent(listener: (event: SdkEvent) => void): () => void { this.eventListeners.add(listener); return () => this.eventListeners.delete(listener); }\n onCommand(listener: (outcome: CommandResult) => void): () => void { this.commandListeners.add(listener); return () => this.commandListeners.delete(listener); }\n /** Notifications addressed to this connection; agents.serve and terminals consume them. */\n onNotification(method: M, listener: (params: Notifications[M]) => void): () => void {\n let listeners = this.notificationListeners.get(method);\n if (!listeners) { listeners = new Set(); this.notificationListeners.set(method, listeners); }\n const typed = listener as (params: unknown) => void;\n listeners.add(typed);\n return () => { listeners.delete(typed); };\n }\n session(rootId: string): Session { return new Session(this, rootId); }\n get transportKind(): Transport['kind'] | undefined { return this.connection?.kind; }\n get httpEndpoint(): string | undefined { return this.connection?.httpEndpoint; }\n get lifetimeSignal(): AbortSignal { this.requireConnected(); return this.controller!.signal; }\n\n async connect(options: Pick = {}): Promise {\n options.signal?.throwIfAborted();\n if (this.closed) throw new WhipError('closed', 'Client is closed; create a new client to attach again');\n if (this.snapshot.state === 'incompatible') throw this.snapshot.error;\n if (this.paused) throw new WhipError('paused', 'Client observation is paused; resume it before connecting');\n if (this.snapshot.state === 'connected') return;\n if (!this.opening) {\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n this.opening = this.open().finally(() => { this.opening = undefined; });\n }\n return withSignal(this.opening, options.signal);\n }\n private async open(): Promise {\n const epoch = ++this.epoch;\n const controller = new AbortController();\n this.controller = controller;\n this.setState(this.runtimeId ? 'reconnecting' : 'connecting');\n const timer = setTimeout(() => controller.abort(new WhipError('timeout', 'Daemon initialization timed out')), this.options.connectTimeoutMs ?? 5000);\n try {\n const connection = await this.factory({\n message: message => { if (epoch === this.epoch) this.receive(message); },\n close: error => { if (epoch === this.epoch) this.disconnected(error); },\n }, controller.signal);\n if (epoch !== this.epoch || this.closed || controller.signal.aborted) { connection.close(); throw abortError(controller.signal); }\n this.connection = connection;\n const info = await this.dispatch('initialize', {\n protocol_major: manifest.major, client_id: this.clientId, client_kind: this.clientKind,\n build_id: this.options.buildId ?? '@whip/sdk', capabilities: ['commands', 'events', 'snapshots', 'uploads', 'history_pages', 'collections', 'host_configuration', 'workspace_completion', 'host_skill_completion', 'host_global_skill_completion', 'skill_catalog_completion', 'host_views', 'themes', 'mailbox_inspection', 'input_attachments', 'session_summaries', 'execution_engines', ...(this.options.browserProvider ? ['desktop-browser-v1', 'desktop-browser-v2'] : [])],\n }, { signal: controller.signal }, true);\n if (epoch !== this.epoch || this.closed || controller.signal.aborted) throw abortError(controller.signal);\n if (info.protocol_major !== manifest.major) throw new WhipError('unsupported_protocol', 'Daemon protocol major is incompatible');\n const expectedRuntime = this.runtimeId ?? this.options.expectedRuntimeId;\n if (expectedRuntime && expectedRuntime !== info.runtime_id) throw new WhipError('runtime_changed', 'The endpoint now serves a different runtime; create a new client explicitly');\n if (!info.runtime_id || info.limits.frame_bytes < 1 || info.limits.in_flight_requests < 1 || BigInt(info.limits.outbound_bytes) < 1n || info.limits.root_subscriptions < 1 || info.limits.content_chunk_bytes < 1 || BigInt(info.limits.upload_bytes) < 0n) {\n throw new WhipError('invalid_response', 'Daemon reported invalid identity or limits');\n }\n this.runtimeId = info.runtime_id;\n this.retries = 0;\n this.setState('connected', undefined, frozen(info));\n this.scheduleHeartbeat();\n } catch (value) {\n const error = asError(value);\n if (epoch === this.epoch) this.disconnected(error);\n throw error;\n } finally { clearTimeout(timer); }\n }\n private setState(state: ConnectionState, error?: Error, info = this.snapshot.info): void {\n this.snapshot = Object.freeze({ state, ...(info ? { info } : {}), ...(error ? { error } : {}) });\n notify(this.listeners, undefined);\n this.wakeCommands();\n }\n private disconnected(error: Error): void {\n ++this.epoch;\n const connection = this.connection;\n this.connection = undefined;\n this.controller?.abort(error);\n connection?.close();\n clearTimeout(this.heartbeat);\n for (const pending of this.pending.values()) pending.reject(error);\n this.pending.clear();\n this.lookups.clear();\n for (const stream of [...this.streams.values()]) stream.fail(error);\n if (this.closed) return;\n if (this.paused) {\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n this.setState('paused');\n return;\n }\n const incompatible = error instanceof WhipError && ['unsupported_protocol', 'runtime_changed'].includes(error.kind);\n this.setState(incompatible ? 'incompatible' : this.options.reconnect === false ? 'closed' : 'reconnecting', error);\n if (!incompatible && this.options.reconnect !== false) {\n clearTimeout(this.retryTimer);\n const delay = Math.min(10_000, 250 * 2 ** Math.min(this.retries++, 6)) * (0.75 + Math.random() * 0.5);\n this.retryTimer = setTimeout(() => { void this.connect().catch(() => { /* open records state and schedules recovery */ }); }, delay);\n }\n }\n /** Refreshes a connection without replaying pending requests. */\n reconnect(): void {\n this.requireConnected();\n this.disconnected(new WhipError('disconnected', 'Refreshing daemon connection'));\n }\n /** Suspend observation without cancelling accepted work or discarding command identities. */\n pause(): void {\n if (this.closed || this.paused || this.snapshot.state === 'incompatible') return;\n this.paused = true;\n this.disconnected(new WhipError('paused', 'Client observation paused'));\n }\n /** Resume the same runtime; commands are reconciled, never resubmitted. */\n async resume(options: Pick = {}): Promise {\n options.signal?.throwIfAborted();\n this.paused = false;\n const resuming = (async () => {\n // A paused initialization may still be unwinding its aborted transport.\n await this.opening?.catch(() => {});\n await this.connect();\n })();\n return withSignal(resuming, options.signal);\n }\n createId(): string { return (this.options.randomUUID ?? uuid)(); }\n async digestHex(bytes: Uint8Array): Promise {\n if (!this.options.sha256) return digestHex(bytes);\n const digest = await this.options.sha256(bytes);\n if (digest.byteLength !== 32) throw new WhipError('invalid_response', 'SHA-256 provider returned an invalid digest');\n return Array.from(digest, byte => byte.toString(16).padStart(2, '0')).join('');\n }\n close(): void {\n if (this.closed) return;\n this.closed = true;\n clearTimeout(this.retryTimer); clearTimeout(this.heartbeat); clearTimeout(this.ticker);\n this.disconnected(new WhipError('closed', 'Client closed'));\n this.setState('closed');\n this.listeners.clear(); this.eventListeners.clear(); this.commandListeners.clear();\n }\n requireConnected(): Readonly {\n if (this.snapshot.state !== 'connected' || !this.connection) throw this.snapshot.error ?? new WhipError('disconnected', 'Connect to the daemon before sending requests');\n return this.snapshot.info!;\n }\n async whenConnected(signal?: AbortSignal): Promise> {\n signal?.throwIfAborted();\n if (this.snapshot.state === 'connected') return this.snapshot.info!;\n if (this.closed || this.snapshot.state === 'incompatible' || this.snapshot.state === 'closed') throw this.snapshot.error ?? new WhipError('closed', 'Client is not connected');\n return new Promise((resolve, reject) => {\n const cleanup = () => { unsubscribe(); signal?.removeEventListener('abort', abort); };\n const check = () => {\n if (this.snapshot.state === 'connected') { cleanup(); resolve(this.snapshot.info!); }\n else if (['closed', 'incompatible'].includes(this.snapshot.state)) { cleanup(); reject(this.snapshot.error ?? new WhipError('closed', 'Client closed')); }\n };\n const abort = () => { cleanup(); reject(abortError(signal)); };\n const unsubscribe = this.subscribe(check);\n signal?.addEventListener('abort', abort, { once: true });\n if (signal?.aborted) abort(); else check();\n });\n }\n supports(surface: 'rpc' | 'runtime', name: string): boolean {\n return this.snapshot.info?.operations?.some(operation => operation.surface === surface && operation.name === name) ?? false;\n }\n call(method: M, params: RpcMethods[M]['params'], options: CallOptions = {}): Promise {\n return this.dispatch(method, params, options);\n }\n /** Internal exact JSON path for command retries. */\n callEncoded(method: M, paramsJSON: string, options: CallOptions = {}): Promise {\n return this.dispatch(method, JSON.parse(paramsJSON), options, false, paramsJSON);\n }\n private async dispatch(method: M, params: RpcMethods[M]['params'], options: CallOptions = {}, initializing = false, encoded?: string): Promise {\n options.signal?.throwIfAborted();\n if (!initializing) this.requireConnected();\n if (!Object.hasOwn(rpcOperations, method)) throw new WhipError('unsupported_operation', 'Unknown RPC method');\n if (!initializing && !this.supports('rpc', method)) throw new WhipError('unsupported_operation', `Daemon does not support ${method}`);\n const metadata = rpcOperations[method];\n assertValid(metadata.params_type, params);\n this.validateNested(method, params);\n const limit = this.snapshot.info?.limits;\n if (this.pending.size >= (limit?.in_flight_requests ?? 32)) throw new WhipError('resource_limit', 'In-flight request limit reached');\n const id = String(++this.nextId);\n const data = `{\"jsonrpc\":\"2.0\",\"id\":${JSON.stringify(id)},\"method\":${JSON.stringify(method)},\"params\":${encoded ?? JSON.stringify(params)}}`;\n const connection = this.connection!;\n const bytes = byteLength(data) + (connection.kind === 'unix' ? 1 : 0);\n if (bytes > (limit?.frame_bytes ?? 1 << 20)) throw new WhipError('resource_limit', 'Request exceeds the frame limit; upload large content separately');\n if (BigInt(connection.bufferedAmount + bytes) > BigInt(limit?.outbound_bytes ?? 8 << 20)) throw new WhipError('resource_limit', 'Outbound byte limit reached');\n return new Promise((resolve, reject) => {\n const cleanup = () => { clearTimeout(timer); options.signal?.removeEventListener('abort', abort); this.pending.delete(id); };\n const fail = (error: Error) => { cleanup(); reject(error); };\n const abort = () => fail(abortError(options.signal));\n const timer = setTimeout(() => fail(new WhipError('timeout', `Request timed out: ${method}`)), options.timeoutMs ?? this.options.queryTimeoutMs ?? 10_000);\n this.pending.set(id, { method, resolve: value => { cleanup(); resolve(value as RpcMethods[M]['result']); }, reject: fail });\n options.signal?.addEventListener('abort', abort, { once: true });\n try { connection.send(data); } catch (error) { fail(asError(error)); }\n });\n }\n private validateNested(method: RpcMethod, value: unknown): void {\n if (!['command.submit', 'query', 'operation.invoke'].includes(method) || !object(value)) return;\n const operation = value.operation as RuntimeOperation;\n if (!Object.hasOwn(runtimeOperations, operation)) throw new WhipError('unsupported_operation', 'Unknown runtime operation');\n const metadata = runtimeOperations[operation];\n const expected = method === 'command.submit' ? 'command' : method === 'query' ? 'query' : 'ephemeral';\n if (metadata.execution !== expected) throw new WhipError('invalid_arguments', `Operation must use the ${metadata.execution} interface`);\n if (!this.supports('runtime', operation)) throw new WhipError('unsupported_operation', `Daemon does not support ${operation}`);\n assertValid(metadata.params_type, value.payload ?? {});\n }\n private receive(text: string): void {\n try {\n if (byteLength(text) > (this.snapshot.info?.limits.frame_bytes ?? 1 << 20)) throw new WhipError('resource_limit', 'Received message exceeds the frame limit');\n const envelope: unknown = JSON.parse(text);\n if (!object(envelope) || envelope.jsonrpc !== '2.0') throw new WhipError('invalid_response', 'Invalid JSON-RPC envelope');\n if (Object.hasOwn(envelope, 'id')) {\n if (typeof envelope.id !== 'string') throw new WhipError('invalid_response', 'Response ID must match the string request ID');\n const pending = this.pending.get(envelope.id);\n if (!pending) return; // Late reply after cancellation/timeout.\n const hasError = Object.hasOwn(envelope, 'error');\n if (hasError === Object.hasOwn(envelope, 'result')) throw new WhipError('invalid_response', 'Expected one response result or error');\n if (hasError) {\n assertValid('RPCError', envelope.error, 'response');\n pending.reject(new RpcError(envelope.error));\n } else {\n // Older compatible hosts predate MCP import/logo settings. Supply only\n // absent fields for features they do not advertise; malformed values\n // and hosts claiming those features still use strict validation.\n if (rpcOperations[pending.method].result_type === 'RuntimeConfiguration' && object(envelope.result)) {\n if (!Object.hasOwn(envelope.result, 'mcp_import_offered') && !this.supports('rpc', 'mcp.import.candidates'))\n envelope.result.mcp_import_offered = true;\n if (!Object.hasOwn(envelope.result, 'brand_icons') && !this.supports('rpc', 'mcp.brand.icons'))\n envelope.result.brand_icons = false;\n }\n assertValid(rpcOperations[pending.method].result_type, envelope.result, 'response');\n pending.resolve(frozen(envelope.result));\n }\n return;\n }\n if (typeof envelope.method !== 'string') throw new WhipError('invalid_response', 'Notification method is missing');\n if (envelope.method === 'event') {\n assertValid('EventNotification', envelope.params, 'response');\n const event = this.decodeEvent(envelope.params.event);\n const stream = event.subscription_id ? this.streams.get(event.subscription_id) : undefined;\n if (!stream) return;\n stream.push(event);\n notify(this.eventListeners, event);\n this.wakeCommands();\n } else if (envelope.method === 'subscription.failed') {\n assertValid('SubscriptionFailure', envelope.params, 'response');\n const failure = envelope.params;\n this.streams.get(failure.subscription_id)?.fail(failure.error ? new RpcError(failure.error) : new WhipError('resynchronization_required', 'Subscription failed'));\n } else if (Object.hasOwn(manifest.events, envelope.method)) {\n // Connection-addressed notifications are typed by the generated contract; anything else is malformed.\n assertValid(manifest.events[envelope.method]!, envelope.params, 'response');\n const listeners = this.notificationListeners.get(envelope.method as Notification);\n if (listeners) notify(listeners, frozen(envelope.params));\n }\n } catch (error) { this.disconnected(new WhipError('invalid_response', 'Malformed daemon response', { cause: error })); }\n }\n private decodeEvent(input: unknown): SdkEvent {\n const wrapper = { event: input };\n assertValid('EventNotification', wrapper, 'response');\n const value = wrapper.event;\n const schema = Object.hasOwn(manifest.event_payloads, value.kind) ? manifest.event_payloads[value.kind] : undefined;\n if (!schema) return frozen({ ...value, payload: value.payload, unknown: true });\n if (!validate(schema, value.payload, 'response') && !validate('ContentEventPayload', value.payload, 'response')) throw new WhipError('invalid_response', `Invalid ${value.kind} event payload`);\n return frozen({ ...value, unknown: false }) as SdkEvent;\n }\n private scheduleHeartbeat(): void {\n clearTimeout(this.heartbeat);\n if (this.snapshot.state !== 'connected') return;\n const epoch = this.epoch;\n this.heartbeat = setTimeout(() => {\n if (epoch !== this.epoch || this.closed) return;\n void this.call('daemon.ping', {}, { timeoutMs: this.options.heartbeatTimeoutMs ?? 10_000 })\n .then(() => { if (epoch === this.epoch && !this.closed) this.scheduleHeartbeat(); }, error => { if (epoch === this.epoch && !this.closed) this.disconnected(asError(error)); });\n }, this.options.heartbeatIntervalMs ?? 30_000);\n }\n releaseSubscription(id: string): void { this.streams.delete(id); }\n query(operation: O, payload: RuntimeOperations[O]['params'], options: CallOptions & { rootId?: string } = {}): Promise> {\n return this.runtimeCall('query', operation, payload, options);\n }\n invoke(operation: O, payload: RuntimeOperations[O]['params'], options: CallOptions & { rootId?: string } = {}): Promise> {\n return this.runtimeCall('operation.invoke', operation, payload, options);\n }\n private async runtimeCall(method: 'query' | 'operation.invoke', operation: O, payload: RuntimeOperations[O]['params'], options: CallOptions & { rootId?: string }): Promise> {\n const result = await this.call(method, { operation, payload, ...(options.rootId ? { root_id: options.rootId } : {}) }, options);\n if (result.result !== undefined) assertValid(runtimeOperations[operation].result_type, result.result, 'response');\n return result as QueryOutcome;\n }\n submit(operation: O, payload: RuntimeOperations[O]['params'], options: CommandOptions = {}): CommandHandle {\n const info = this.requireConnected();\n if (!Object.hasOwn(runtimeOperations, operation) || runtimeOperations[operation].execution !== 'command') throw new WhipError('invalid_arguments', 'Only durable runtime commands can be submitted');\n if (!this.supports('runtime', operation)) throw new WhipError('unsupported_operation', `Daemon does not support ${operation}`);\n assertValid(runtimeOperations[operation].params_type, payload);\n return CommandHandle.submit(this, info.runtime_id, operation, payload, options);\n }\n recover(record: RecoveryRecord, payload?: RuntimeOperations[O]['params']): CommandHandle {\n if (record.version !== 1 || record.clientId !== this.clientId || !record.commandId || !Object.hasOwn(runtimeOperations, record.operation) || runtimeOperations[record.operation].execution !== 'command') throw new WhipError('invalid_arguments', 'Invalid recovery record or command namespace');\n return CommandHandle.recover(this, record, payload);\n }\n async recoveryRecords(): Promise { return this.recoveryStorage ? this.recoveryStorage.list() : []; }\n async forget(record: RecoveryRecord): Promise {\n if (record.clientId !== this.clientId) throw new WhipError('invalid_arguments', 'Recovery record belongs to a different client namespace');\n await this.recoveryStorage?.delete(record);\n }\n async commandStatus(record: RecoveryRecord, options: CallOptions = {}): Promise> {\n options.signal?.throwIfAborted();\n if (record.clientId !== this.clientId) throw new WhipError('invalid_arguments', 'Command belongs to another client namespace');\n const info = await this.whenConnected(options.signal);\n if (info.runtime_id !== record.runtimeId) throw new WhipError('runtime_changed', 'Command belongs to a different persistent runtime');\n let pending = this.lookups.get(record.commandId);\n if (!pending) {\n pending = this.call('command.status', { command_id: record.commandId });\n this.lookups.set(record.commandId, pending);\n void pending.finally(() => { if (this.lookups.get(record.commandId) === pending) this.lookups.delete(record.commandId); }).catch(() => {});\n }\n const result = await withSignal(pending, options.signal);\n return this.commandOutcome(record, result);\n }\n commandOutcome(record: RecoveryRecord, result: CommandResult): CommandOutcome {\n if (result.command_id !== record.commandId || result.operation !== record.operation || !['queued', 'running', 'waiting', 'succeeded', 'failed', 'cancelled', 'interrupted'].includes(result.status)) throw new WhipError('invalid_response', 'Command response does not match its identity or lifecycle');\n if (result.status === 'succeeded' && result.result !== undefined) assertValid(runtimeOperations[record.operation].result_type, result.result, 'response');\n const outcome = result as CommandOutcome;\n notify(this.commandListeners, outcome);\n return outcome;\n }\n waitForCommandTick(signal?: AbortSignal): Promise {\n if (this.closed) return Promise.reject(new WhipError('closed', 'Client closed'));\n return new Promise((resolve, reject) => {\n const cleanup = () => {\n this.ticks.delete(done); signal?.removeEventListener('abort', abort);\n if (this.ticks.size === 0) { clearTimeout(this.ticker); this.ticker = undefined; }\n };\n const done = () => { cleanup(); resolve(); };\n const abort = () => { cleanup(); reject(abortError(signal)); };\n this.ticks.add(done);\n signal?.addEventListener('abort', abort, { once: true });\n if (signal?.aborted) { abort(); return; }\n if (!this.paused && !this.closed) this.ticker ??= setTimeout(() => this.wakeCommands(), this.options.commandPollMs ?? 250);\n });\n }\n private wakeCommands(): void {\n clearTimeout(this.ticker); this.ticker = undefined;\n for (const done of [...this.ticks]) done();\n }\n content(handle: ConstructorParameters[1], scope: ContentScope): ContentReference { return new ContentReference(this, handle, scope); }\n upload(bytes: Uint8Array, options: UploadOptions): Promise { return upload(this, bytes, options); }\n}\n\nexport function createWhipClient(options: ClientOptions): WhipClient { return new WhipClient(options); }\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/command.d.ts b/vendor/@whip/sdk/dist/command.d.ts new file mode 100644 index 0000000..8afc2ea --- /dev/null +++ b/vendor/@whip/sdk/dist/command.d.ts @@ -0,0 +1,52 @@ +import { type CommandOperation, type CommandResult, type RuntimeOperations } from '@whip/protocol'; +import type { WhipClient, CallOptions } from './client.js'; +export type CommandStatus = 'queued' | 'running' | 'waiting' | 'succeeded' | 'failed' | 'cancelled' | 'interrupted'; +export type CommandOutcome = Omit & { + status: CommandStatus; + result?: RuntimeOperations[O]['result']; +}; +export interface CommandOptions { + rootId?: string; + commandId?: string; +} +export interface RecoveryRecord { + readonly version: 1; + readonly runtimeId: string; + readonly clientId: string; + readonly commandId: string; + readonly operation: O; + readonly rootId?: string; +} +/** Application-owned storage. These records deliberately contain no request body. */ +export interface RecoveryStorage { + list(): Promise; + put(record: RecoveryRecord): Promise; + delete(record: RecoveryRecord): Promise; +} +export declare function isTerminal(status: string): boolean; +export declare class CommandHandle { + readonly client: WhipClient; + readonly record: RecoveryRecord; + private readonly beforeSend?; + private initial?; + private last?; + private knownAccepted; + private encoded?; + private retrying?; + private constructor(); + static submit(client: WhipClient, runtimeId: string, operation: O, payload: RuntimeOperations[O]['params'], options: CommandOptions, beforeSend?: () => Promise): CommandHandle; + static recover(client: WhipClient, record: RecoveryRecord, payload?: RuntimeOperations[O]['params']): CommandHandle; + get commandId(): string; + get operation(): O; + private observe; + private send; + /** Acceptance is committed input, not completion. Aborting only stops this waiter. */ + accepted(options?: Pick): Promise>; + status(options?: CallOptions): Promise>; + /** Resolves a structured terminal outcome; execution failures remain daemon outcomes. */ + result(options?: Pick): Promise>; + /** Explicit retry: identical identity and bytes, only after a definitive missing lookup. */ + retry(options?: Pick): Promise>; + private retryOnce; + cancel(): CommandHandle<'cancel'>; +} diff --git a/vendor/@whip/sdk/dist/command.js b/vendor/@whip/sdk/dist/command.js new file mode 100644 index 0000000..9a6a44e --- /dev/null +++ b/vendor/@whip/sdk/dist/command.js @@ -0,0 +1,154 @@ +import { assertValid, runtimeOperations } from '@whip/protocol'; +import { DeliveryUncertainError, RpcError, WhipError } from './errors.js'; +import { frozen, withSignal } from './util.js'; +export function isTerminal(status) { + return ['succeeded', 'failed', 'cancelled', 'interrupted'].includes(status); +} +export class CommandHandle { + client; + record; + beforeSend; + initial; + last; + knownAccepted = false; + encoded; + retrying; + constructor(client, record, payload, beforeSend) { + this.client = client; + this.record = record; + this.beforeSend = beforeSend; + if (payload !== undefined) { + assertValid(runtimeOperations[record.operation].params_type, payload); + this.encoded = JSON.stringify({ + command_id: record.commandId, operation: record.operation, + scope: record.rootId ? 'root' : 'daemon', + ...(record.rootId ? { root_id: record.rootId } : {}), payload, + }); + } + } + static submit(client, runtimeId, operation, payload, options, beforeSend) { + const record = frozen({ version: 1, runtimeId, clientId: client.clientId, commandId: options.commandId ?? client.createId(), operation, ...(options.rootId ? { rootId: options.rootId } : {}) }); + if (!record.commandId) + throw new TypeError('commandId must not be empty'); + const handle = new CommandHandle(client, record, payload, beforeSend); + handle.initial = handle.send(); + // A UI may render the handle before attaching a waiter; retain rejection for accepted(). + void handle.initial.catch(() => { }); + return handle; + } + static recover(client, record, payload) { + return new CommandHandle(client, frozen({ ...record }), payload); + } + get commandId() { return this.record.commandId; } + get operation() { return this.record.operation; } + observe(result) { + this.knownAccepted = true; + this.last = result; + if (isTerminal(result.status)) + this.encoded = undefined; + return result; + } + async send() { + await this.beforeSend?.(); + if (!this.encoded) + throw new WhipError('recovery_required', 'Retry requires the original request payload'); + const info = this.client.requireConnected(); + if (info.runtime_id !== this.record.runtimeId) + throw new WhipError('runtime_changed', 'Command belongs to another runtime'); + await this.client.recoveryStorage?.put(this.record); + // Storage may be asynchronous. Recheck before the first byte can be sent. + if (this.client.requireConnected().runtime_id !== this.record.runtimeId) + throw new WhipError('runtime_changed', 'Runtime changed before submission'); + try { + const result = await this.client.callEncoded('command.submit', this.encoded); + return this.observe(this.client.commandOutcome(this.record, result)); + } + catch (error) { + if (error instanceof RpcError || error instanceof TypeError || error instanceof WhipError && ['resource_limit', 'unsupported_operation', 'invalid_arguments'].includes(error.kind)) + throw error; + throw new DeliveryUncertainError(this.commandId, error); + } + } + /** Acceptance is committed input, not completion. Aborting only stops this waiter. */ + accepted(options = {}) { + if (this.last) + return withSignal(Promise.resolve(this.last), options.signal); + return withSignal(this.initial ?? this.status(options), options.signal); + } + async status(options = {}) { + return this.observe(await this.client.commandStatus(this.record, options)); + } + /** Resolves a structured terminal outcome; execution failures remain daemon outcomes. */ + async result(options = {}) { + if (this.initial) { + try { + await withSignal(this.initial, options.signal); + } + catch (error) { + if (!(error instanceof DeliveryUncertainError)) + throw error; + } + } + for (;;) { + options.signal?.throwIfAborted(); + if (this.last && isTerminal(this.last.status)) + return this.last; + try { + const result = await this.status(options); + if (isTerminal(result.status)) + return result; + } + catch (error) { + if (!options.signal?.aborted && error instanceof WhipError && error.kind === 'resource_limit') { + await this.client.waitForCommandTick(options.signal); + continue; + } + if (options.signal?.aborted || error instanceof RpcError || !['reconnecting', 'paused'].includes(this.client.getSnapshot().state)) + throw error; + await this.client.whenConnected(options.signal); + continue; + } + await this.client.waitForCommandTick(options.signal); + } + } + /** Explicit retry: identical identity and bytes, only after a definitive missing lookup. */ + retry(options = {}) { + if (options.signal?.aborted) + return Promise.reject(options.signal.reason); + if (!this.retrying) { + this.retrying = this.retryOnce().finally(() => { this.retrying = undefined; }); + } + return withSignal(this.retrying, options.signal); + } + async retryOnce() { + if (this.initial) + await this.initial.catch(error => { if (error instanceof RpcError) + throw error; }); + try { + return await this.status(); + } + catch (error) { + if (!(error instanceof RpcError) || error.kind !== 'command_not_found') + throw error; + if (this.knownAccepted) + throw new WhipError('recovery_required', 'Previously accepted command is missing; it must not be replayed'); + this.initial = this.send(); + return this.initial; + } + } + cancel() { + if (!this.record.rootId || !['submit', 'steer'].includes(this.operation)) + throw new WhipError('unsupported_operation', 'This command has no command-targeted cancellation; use an explicit agent turn ID where supported'); + return CommandHandle.submit(this.client, this.record.runtimeId, 'cancel', { target_command_id: this.commandId }, { rootId: this.record.rootId }, async () => { + try { + await this.accepted(); + } + catch (error) { + if (!(error instanceof DeliveryUncertainError)) + throw error; + await this.status(); + } + }); + } +} +//# sourceMappingURL=command.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/command.js.map b/vendor/@whip/sdk/dist/command.js.map new file mode 100644 index 0000000..e0f9dd7 --- /dev/null +++ b/vendor/@whip/sdk/dist/command.js.map @@ -0,0 +1 @@ +{"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"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","sourcesContent":["import { assertValid, runtimeOperations, type CommandOperation, type CommandResult, type RuntimeOperations } from '@whip/protocol';\nimport type { WhipClient, CallOptions } from './client.js';\nimport { DeliveryUncertainError, RpcError, WhipError } from './errors.js';\nimport { frozen, withSignal } from './util.js';\n\nexport type CommandStatus = 'queued' | 'running' | 'waiting' | 'succeeded' | 'failed' | 'cancelled' | 'interrupted';\nexport type CommandOutcome = Omit & {\n status: CommandStatus;\n result?: RuntimeOperations[O]['result'];\n};\nexport interface CommandOptions { rootId?: string; commandId?: string }\nexport interface RecoveryRecord {\n readonly version: 1;\n readonly runtimeId: string;\n readonly clientId: string;\n readonly commandId: string;\n readonly operation: O;\n readonly rootId?: string;\n}\n/** Application-owned storage. These records deliberately contain no request body. */\nexport interface RecoveryStorage {\n list(): Promise;\n put(record: RecoveryRecord): Promise;\n delete(record: RecoveryRecord): Promise;\n}\nexport function isTerminal(status: string): boolean {\n return ['succeeded', 'failed', 'cancelled', 'interrupted'].includes(status);\n}\n\nexport class CommandHandle {\n private initial?: Promise>;\n private last?: CommandOutcome;\n private knownAccepted = false;\n private encoded?: string;\n private retrying?: Promise>;\n private constructor(readonly client: WhipClient, readonly record: RecoveryRecord, payload?: RuntimeOperations[O]['params'], private readonly beforeSend?: () => Promise) {\n if (payload !== undefined) {\n assertValid(runtimeOperations[record.operation].params_type, payload);\n this.encoded = JSON.stringify({\n command_id: record.commandId, operation: record.operation,\n scope: record.rootId ? 'root' : 'daemon',\n ...(record.rootId ? { root_id: record.rootId } : {}), payload,\n });\n }\n }\n static submit(client: WhipClient, runtimeId: string, operation: O, payload: RuntimeOperations[O]['params'], options: CommandOptions, beforeSend?: () => Promise): CommandHandle {\n const record = frozen({ version: 1 as const, runtimeId, clientId: client.clientId, commandId: options.commandId ?? client.createId(), operation, ...(options.rootId ? { rootId: options.rootId } : {}) });\n if (!record.commandId) throw new TypeError('commandId must not be empty');\n const handle = new CommandHandle(client, record, payload, beforeSend);\n handle.initial = handle.send();\n // A UI may render the handle before attaching a waiter; retain rejection for accepted().\n void handle.initial.catch(() => {});\n return handle;\n }\n static recover(client: WhipClient, record: RecoveryRecord, payload?: RuntimeOperations[O]['params']): CommandHandle {\n return new CommandHandle(client, frozen({ ...record }), payload);\n }\n get commandId(): string { return this.record.commandId; }\n get operation(): O { return this.record.operation; }\n private observe(result: CommandOutcome): CommandOutcome {\n this.knownAccepted = true;\n this.last = result;\n if (isTerminal(result.status)) this.encoded = undefined;\n return result;\n }\n private async send(): Promise> {\n await this.beforeSend?.();\n if (!this.encoded) throw new WhipError('recovery_required', 'Retry requires the original request payload');\n const info = this.client.requireConnected();\n if (info.runtime_id !== this.record.runtimeId) throw new WhipError('runtime_changed', 'Command belongs to another runtime');\n await this.client.recoveryStorage?.put(this.record);\n // Storage may be asynchronous. Recheck before the first byte can be sent.\n if (this.client.requireConnected().runtime_id !== this.record.runtimeId) throw new WhipError('runtime_changed', 'Runtime changed before submission');\n try {\n const result = await this.client.callEncoded('command.submit', this.encoded);\n return this.observe(this.client.commandOutcome(this.record, result));\n } catch (error) {\n if (error instanceof RpcError || error instanceof TypeError || error instanceof WhipError && ['resource_limit', 'unsupported_operation', 'invalid_arguments'].includes(error.kind)) throw error;\n throw new DeliveryUncertainError(this.commandId, error);\n }\n }\n /** Acceptance is committed input, not completion. Aborting only stops this waiter. */\n accepted(options: Pick = {}): Promise> {\n if (this.last) return withSignal(Promise.resolve(this.last), options.signal);\n return withSignal(this.initial ?? this.status(options), options.signal);\n }\n async status(options: CallOptions = {}): Promise> {\n return this.observe(await this.client.commandStatus(this.record, options));\n }\n /** Resolves a structured terminal outcome; execution failures remain daemon outcomes. */\n async result(options: Pick = {}): Promise> {\n if (this.initial) {\n try { await withSignal(this.initial, options.signal); }\n catch (error) { if (!(error instanceof DeliveryUncertainError)) throw error; }\n }\n for (;;) {\n options.signal?.throwIfAborted();\n if (this.last && isTerminal(this.last.status)) return this.last;\n try {\n const result = await this.status(options);\n if (isTerminal(result.status)) return result;\n } catch (error) {\n if (!options.signal?.aborted && error instanceof WhipError && error.kind === 'resource_limit') {\n await this.client.waitForCommandTick(options.signal); continue;\n }\n if (options.signal?.aborted || error instanceof RpcError || !['reconnecting', 'paused'].includes(this.client.getSnapshot().state)) throw error;\n await this.client.whenConnected(options.signal);\n continue;\n }\n await this.client.waitForCommandTick(options.signal);\n }\n }\n /** Explicit retry: identical identity and bytes, only after a definitive missing lookup. */\n retry(options: Pick = {}): Promise> {\n if (options.signal?.aborted) return Promise.reject(options.signal.reason);\n if (!this.retrying) {\n this.retrying = this.retryOnce().finally(() => { this.retrying = undefined; });\n }\n return withSignal(this.retrying, options.signal);\n }\n private async retryOnce(): Promise> {\n if (this.initial) await this.initial.catch(error => { if (error instanceof RpcError) throw error; });\n try { return await this.status(); }\n catch (error) {\n if (!(error instanceof RpcError) || error.kind !== 'command_not_found') throw error;\n if (this.knownAccepted) throw new WhipError('recovery_required', 'Previously accepted command is missing; it must not be replayed');\n this.initial = this.send();\n return this.initial;\n }\n }\n cancel(): CommandHandle<'cancel'> {\n if (!this.record.rootId || !['submit', 'steer'].includes(this.operation)) throw new WhipError('unsupported_operation', 'This command has no command-targeted cancellation; use an explicit agent turn ID where supported');\n return CommandHandle.submit(this.client, this.record.runtimeId, 'cancel', { target_command_id: this.commandId }, { rootId: this.record.rootId }, async () => {\n try { await this.accepted(); }\n catch (error) { if (!(error instanceof DeliveryUncertainError)) throw error; await this.status(); }\n });\n }\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/content.d.ts b/vendor/@whip/sdk/dist/content.d.ts new file mode 100644 index 0000000..e66fbfc --- /dev/null +++ b/vendor/@whip/sdk/dist/content.d.ts @@ -0,0 +1,27 @@ +import { type ContentHandle, type SubmitPayload } from '@whip/protocol'; +import type { CallOptions, WhipClient } from './client.js'; +export interface ContentScope { + rootId: string; + agentId?: string; +} +export type InputAttachment = NonNullable[number]; +export interface ReadContentOptions extends CallOptions { + maxBytes: number; +} +export interface UploadOptions extends ContentScope, CallOptions { + mediaType?: string; + source?: string; +} +/** Immutable scoped reference. Constructing a reference never fetches its body. */ +export declare class ContentReference { + private readonly client; + readonly handle: Readonly; + readonly scope: Readonly; + constructor(client: WhipClient, handle: ContentHandle, scope: ContentScope); + /** An input reference, without downloading or embedding the content body. */ + asAttachment(kind: 'image' | 'text', name?: string): InputAttachment; + readBytes(options: ReadContentOptions): Promise>; + readText(options: ReadContentOptions): Promise; + readJSON(options: ReadContentOptions): Promise; +} +export declare function upload(client: WhipClient, input: Uint8Array, options: UploadOptions, transport?: 'auto' | 'connection'): Promise; diff --git a/vendor/@whip/sdk/dist/content.js b/vendor/@whip/sdk/dist/content.js new file mode 100644 index 0000000..e1f5fe3 --- /dev/null +++ b/vendor/@whip/sdk/dist/content.js @@ -0,0 +1,169 @@ +import { assertValid } from '@whip/protocol'; +import { WhipError } from './errors.js'; +import { decodeBase64, encodeBase64, frozen } from './util.js'; +/** Immutable scoped reference. Constructing a reference never fetches its body. */ +export class ContentReference { + client; + handle; + scope; + constructor(client, handle, scope) { + this.client = client; + assertValid('ContentHandle', handle, 'response'); + if (!scope.rootId || BigInt(handle.size) < 0n || !/^[a-f0-9]{64}$/.test(handle.digest)) + throw new TypeError('Invalid content scope, size or digest'); + this.handle = frozen({ ...handle }); + this.scope = frozen({ rootId: scope.rootId, ...(scope.agentId ? { agentId: scope.agentId } : {}) }); + } + /** An input reference, without downloading or embedding the content body. */ + asAttachment(kind, name) { + return frozen({ kind, content: { ...this.handle }, ...(name ? { name } : {}) }); + } + async readBytes(options) { + options.signal?.throwIfAborted(); + if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0 || BigInt(this.handle.size) > BigInt(options.maxBytes)) + throw new WhipError('resource_limit', 'Content exceeds the requested byte limit'); + const signal = transferSignal(this.client, options); + const size = Number(this.handle.size); // Checked against the caller's safe integer bound. + const bytes = new Uint8Array(size); + if (this.client.transportKind === 'websocket') { + const url = new URL(`/api/v3/content/${encodeURIComponent(this.handle.reference_id)}`, this.client.httpEndpoint); + url.searchParams.set('root_id', this.scope.rootId); + if (this.scope.agentId) + url.searchParams.set('agent_id', this.scope.agentId); + const response = await fetch(url, { signal, cache: 'no-store' }); + if (!response.ok) + throw httpError(response.status); + const length = response.headers.get('Content-Length'); + if (length !== null && length !== this.handle.size) { + await response.body?.cancel(); + throw new WhipError('invalid_response', 'Content length changed'); + } + const reader = response.body?.getReader(); + let offset = 0; + if (reader) { + try { + for (;;) { + const { value, done } = await reader.read(); + if (done) + break; + if (offset + value.byteLength > size) + throw new WhipError('resource_limit', 'Downloaded content exceeds its declared size'); + bytes.set(value, offset); + offset += value.byteLength; + } + } + finally { + await reader.cancel(); + reader.releaseLock(); + } + } + if (offset !== size) + throw new WhipError('unavailable_capability', 'Content transfer was interrupted'); + } + else { + const limit = this.client.requireConnected().limits.content_chunk_bytes; + // Even an empty body must validate its root/agent/reference grant. + for (let offset = 0; offset < size || offset === 0;) { + const result = await this.client.call('content.read', { + root_id: this.scope.rootId, ...(this.scope.agentId ? { agent_id: this.scope.agentId } : {}), + reference_id: this.handle.reference_id, offset: String(offset), limit: Math.max(1, Math.min(limit, size - offset)), + }, { ...options, signal }); + if (result.content.reference_id !== this.handle.reference_id || result.content.digest !== this.handle.digest || result.content.size !== this.handle.size) + throw new WhipError('invalid_response', 'Content reference changed during transfer'); + const chunk = decodeBase64(result.data ?? ''); + if (size === 0 && chunk.byteLength === 0) + break; + if (!chunk.byteLength || chunk.byteLength > Math.min(limit, size - offset)) + throw new WhipError('invalid_response', 'Invalid content chunk length'); + bytes.set(chunk, offset); + offset += chunk.byteLength; + } + } + if (await this.client.digestHex(bytes) !== this.handle.digest) + throw new WhipError('invalid_response', 'Content digest mismatch'); + return bytes; + } + async readText(options) { + return new TextDecoder('utf-8', { fatal: true }).decode(await this.readBytes(options)); + } + async readJSON(options) { return JSON.parse(await this.readText(options)); } +} +function transferSignal(client, options) { + return AbortSignal.any([client.lifetimeSignal, AbortSignal.timeout(options.timeoutMs ?? 30_000), ...(options.signal ? [options.signal] : [])]); +} +function httpError(status) { + return new WhipError(status === 403 ? 'permission_denied' : status === 413 ? 'resource_limit' : 'unavailable_capability', `Content transfer failed (HTTP ${status})`); +} +export async function upload(client, input, options, transport = 'auto') { + options.signal?.throwIfAborted(); + const info = client.requireConnected(); + if (!options.rootId) + throw new WhipError('invalid_arguments', 'Upload requires a root scope'); + if (BigInt(input.byteLength) > BigInt(info.limits.upload_bytes)) + throw new WhipError('resource_limit', 'Upload exceeds the daemon limit'); + const signal = transferSignal(client, options); + // Snapshot once: the digest and transmitted bytes must describe the same input. + const bytes = input.slice(); + const digest = await client.digestHex(bytes); + signal.throwIfAborted(); + let handle; + // Browser results require upload provenance on the selected holder connection. + // HTTP creates a separate principal even when it reaches the same daemon. + if (transport === 'auto' && client.transportKind === 'websocket') { + const url = new URL('/api/v3/content/upload', client.httpEndpoint); + url.searchParams.set('root_id', options.rootId); + if (options.agentId) + url.searchParams.set('agent_id', options.agentId); + const response = await fetch(url, { method: 'POST', signal, headers: { 'Content-Type': options.mediaType ?? 'application/octet-stream', 'X-Content-SHA256': digest }, body: bytes }); + if (!response.ok) + throw httpError(response.status); + // A handle is small, but still bound the response independently of the upload. + const reader = response.body?.getReader(); + const chunks = []; + let size = 0; + if (!reader) + throw new WhipError('invalid_response', 'Upload response is missing'); + try { + for (;;) { + const { done, value } = await reader.read(); + if (done) + break; + size += value.byteLength; + if (size > 64 << 10) + throw new WhipError('resource_limit', 'Upload response exceeds limit'); + chunks.push(value); + } + } + finally { + await reader.cancel(); + reader.releaseLock(); + } + const data = new Uint8Array(size); + let offset = 0; + for (const chunk of chunks) { + data.set(chunk, offset); + offset += chunk.length; + } + const value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(data)); + assertValid('ContentHandle', value, 'response'); + handle = value; + } + else { + const id = client.createId(); + const epoch = info.connection_id; + const check = () => { signal.throwIfAborted(); if (client.requireConnected().connection_id !== epoch) + throw new WhipError('disconnected', 'Upload connection changed; begin a new upload explicitly'); }; + check(); + await client.call('upload.begin', { upload_id: id, root_id: options.rootId, ...(options.agentId ? { agent_id: options.agentId } : {}), expected_digest: digest, size: String(bytes.byteLength), media_type: options.mediaType, source: options.source }, { signal }); + for (let offset = 0; offset < bytes.length; offset += info.limits.content_chunk_bytes) { + check(); + await client.call('upload.chunk', { upload_id: id, offset: String(offset), data: encodeBase64(bytes.subarray(offset, offset + info.limits.content_chunk_bytes)) }, { signal }); + } + check(); + handle = await client.call('upload.finish', { upload_id: id }, { signal }); + } + if (handle.digest !== digest || handle.size !== String(bytes.byteLength)) + throw new WhipError('invalid_response', 'Uploaded content digest or size mismatch'); + return new ContentReference(client, handle, options); +} +//# sourceMappingURL=content.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/content.js.map b/vendor/@whip/sdk/dist/content.js.map new file mode 100644 index 0000000..d2c8a0f --- /dev/null +++ b/vendor/@whip/sdk/dist/content.js.map @@ -0,0 +1 @@ +{"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"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","sourcesContent":["import { assertValid, type ContentHandle, type SubmitPayload } from '@whip/protocol';\nimport type { CallOptions, WhipClient } from './client.js';\nimport { WhipError } from './errors.js';\nimport { decodeBase64, encodeBase64, frozen } from './util.js';\n\nexport interface ContentScope { rootId: string; agentId?: string }\nexport type InputAttachment = NonNullable[number];\nexport interface ReadContentOptions extends CallOptions { maxBytes: number }\nexport interface UploadOptions extends ContentScope, CallOptions { mediaType?: string; source?: string }\n\n/** Immutable scoped reference. Constructing a reference never fetches its body. */\nexport class ContentReference {\n readonly handle: Readonly;\n readonly scope: Readonly;\n constructor(private readonly client: WhipClient, handle: ContentHandle, scope: ContentScope) {\n assertValid('ContentHandle', handle, 'response');\n if (!scope.rootId || BigInt(handle.size) < 0n || !/^[a-f0-9]{64}$/.test(handle.digest)) throw new TypeError('Invalid content scope, size or digest');\n this.handle = frozen({ ...handle });\n this.scope = frozen({ rootId: scope.rootId, ...(scope.agentId ? { agentId: scope.agentId } : {}) });\n }\n /** An input reference, without downloading or embedding the content body. */\n asAttachment(kind: 'image' | 'text', name?: string): InputAttachment {\n return frozen({ kind, content: { ...this.handle }, ...(name ? { name } : {}) });\n }\n async readBytes(options: ReadContentOptions): Promise> {\n options.signal?.throwIfAborted();\n if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0 || BigInt(this.handle.size) > BigInt(options.maxBytes)) throw new WhipError('resource_limit', 'Content exceeds the requested byte limit');\n const signal = transferSignal(this.client, options);\n const size = Number(this.handle.size); // Checked against the caller's safe integer bound.\n const bytes = new Uint8Array(size);\n if (this.client.transportKind === 'websocket') {\n const url = new URL(`/api/v3/content/${encodeURIComponent(this.handle.reference_id)}`, this.client.httpEndpoint);\n url.searchParams.set('root_id', this.scope.rootId);\n if (this.scope.agentId) url.searchParams.set('agent_id', this.scope.agentId);\n const response = await fetch(url, { signal, cache: 'no-store' });\n if (!response.ok) throw httpError(response.status);\n const length = response.headers.get('Content-Length');\n if (length !== null && length !== this.handle.size) { await response.body?.cancel(); throw new WhipError('invalid_response', 'Content length changed'); }\n const reader = response.body?.getReader();\n let offset = 0;\n if (reader) {\n try {\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n if (offset + value.byteLength > size) throw new WhipError('resource_limit', 'Downloaded content exceeds its declared size');\n bytes.set(value, offset); offset += value.byteLength;\n }\n } finally { await reader.cancel(); reader.releaseLock(); }\n }\n if (offset !== size) throw new WhipError('unavailable_capability', 'Content transfer was interrupted');\n } else {\n const limit = this.client.requireConnected().limits.content_chunk_bytes;\n // Even an empty body must validate its root/agent/reference grant.\n for (let offset = 0; offset < size || offset === 0;) {\n const result = await this.client.call('content.read', {\n root_id: this.scope.rootId, ...(this.scope.agentId ? { agent_id: this.scope.agentId } : {}),\n reference_id: this.handle.reference_id, offset: String(offset), limit: Math.max(1, Math.min(limit, size - offset)),\n }, { ...options, signal });\n if (result.content.reference_id !== this.handle.reference_id || result.content.digest !== this.handle.digest || result.content.size !== this.handle.size) throw new WhipError('invalid_response', 'Content reference changed during transfer');\n const chunk = decodeBase64(result.data ?? '');\n if (size === 0 && chunk.byteLength === 0) break;\n if (!chunk.byteLength || chunk.byteLength > Math.min(limit, size - offset)) throw new WhipError('invalid_response', 'Invalid content chunk length');\n bytes.set(chunk, offset); offset += chunk.byteLength;\n }\n }\n if (await this.client.digestHex(bytes) !== this.handle.digest) throw new WhipError('invalid_response', 'Content digest mismatch');\n return bytes;\n }\n async readText(options: ReadContentOptions): Promise {\n return new TextDecoder('utf-8', { fatal: true }).decode(await this.readBytes(options));\n }\n async readJSON(options: ReadContentOptions): Promise { return JSON.parse(await this.readText(options)); }\n}\n\nfunction transferSignal(client: WhipClient, options: CallOptions): AbortSignal {\n return AbortSignal.any([client.lifetimeSignal, AbortSignal.timeout(options.timeoutMs ?? 30_000), ...(options.signal ? [options.signal] : [])]);\n}\nfunction httpError(status: number): WhipError {\n return new WhipError(status === 403 ? 'permission_denied' : status === 413 ? 'resource_limit' : 'unavailable_capability', `Content transfer failed (HTTP ${status})`);\n}\nexport async function upload(client: WhipClient, input: Uint8Array, options: UploadOptions, transport: 'auto' | 'connection' = 'auto'): Promise {\n options.signal?.throwIfAborted();\n const info = client.requireConnected();\n if (!options.rootId) throw new WhipError('invalid_arguments', 'Upload requires a root scope');\n if (BigInt(input.byteLength) > BigInt(info.limits.upload_bytes)) throw new WhipError('resource_limit', 'Upload exceeds the daemon limit');\n const signal = transferSignal(client, options);\n // Snapshot once: the digest and transmitted bytes must describe the same input.\n const bytes = input.slice();\n const digest = await client.digestHex(bytes);\n signal.throwIfAborted();\n let handle: ContentHandle;\n // Browser results require upload provenance on the selected holder connection.\n // HTTP creates a separate principal even when it reaches the same daemon.\n if (transport === 'auto' && client.transportKind === 'websocket') {\n const url = new URL('/api/v3/content/upload', client.httpEndpoint);\n url.searchParams.set('root_id', options.rootId);\n if (options.agentId) url.searchParams.set('agent_id', options.agentId);\n const response = await fetch(url, { method: 'POST', signal, headers: { 'Content-Type': options.mediaType ?? 'application/octet-stream', 'X-Content-SHA256': digest }, body: bytes });\n if (!response.ok) throw httpError(response.status);\n // A handle is small, but still bound the response independently of the upload.\n const reader = response.body?.getReader();\n const chunks: Uint8Array[] = []; let size = 0;\n if (!reader) throw new WhipError('invalid_response', 'Upload response is missing');\n try {\n for (;;) {\n const { done, value } = await reader.read(); if (done) break;\n size += value.byteLength; if (size > 64 << 10) throw new WhipError('resource_limit', 'Upload response exceeds limit');\n chunks.push(value);\n }\n } finally { await reader.cancel(); reader.releaseLock(); }\n const data = new Uint8Array(size); let offset = 0;\n for (const chunk of chunks) { data.set(chunk, offset); offset += chunk.length; }\n const value: unknown = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(data));\n assertValid('ContentHandle', value, 'response'); handle = value;\n } else {\n const id = client.createId();\n const epoch = info.connection_id;\n const check = () => { signal.throwIfAborted(); if (client.requireConnected().connection_id !== epoch) throw new WhipError('disconnected', 'Upload connection changed; begin a new upload explicitly'); };\n check();\n await client.call('upload.begin', { upload_id: id, root_id: options.rootId, ...(options.agentId ? { agent_id: options.agentId } : {}), expected_digest: digest, size: String(bytes.byteLength), media_type: options.mediaType, source: options.source }, { signal });\n for (let offset = 0; offset < bytes.length; offset += info.limits.content_chunk_bytes) {\n check();\n await client.call('upload.chunk', { upload_id: id, offset: String(offset), data: encodeBase64(bytes.subarray(offset, offset + info.limits.content_chunk_bytes)) }, { signal });\n }\n check(); handle = await client.call('upload.finish', { upload_id: id }, { signal });\n }\n if (handle.digest !== digest || handle.size !== String(bytes.byteLength)) throw new WhipError('invalid_response', 'Uploaded content digest or size mismatch');\n return new ContentReference(client, handle, options);\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/errors.d.ts b/vendor/@whip/sdk/dist/errors.d.ts new file mode 100644 index 0000000..1c22c8d --- /dev/null +++ b/vendor/@whip/sdk/dist/errors.d.ts @@ -0,0 +1,17 @@ +import type { RPCError } from '@whip/protocol'; +/** Errors here describe client/protocol failures, never a guessed execution outcome. */ +export declare class WhipError extends Error { + readonly kind: string; + constructor(kind: string, message: string, options?: ErrorOptions); +} +export declare class RpcError extends WhipError { + readonly rpc: RPCError; + readonly code: number; + constructor(rpc: RPCError); +} +export declare class DeliveryUncertainError extends WhipError { + readonly commandId: string; + constructor(commandId: string, cause?: unknown); +} +export declare function asError(value: unknown): Error; +export declare function abortError(signal?: AbortSignal): Error; diff --git a/vendor/@whip/sdk/dist/errors.js b/vendor/@whip/sdk/dist/errors.js new file mode 100644 index 0000000..69e00fd --- /dev/null +++ b/vendor/@whip/sdk/dist/errors.js @@ -0,0 +1,34 @@ +/** Errors here describe client/protocol failures, never a guessed execution outcome. */ +export class WhipError extends Error { + kind; + constructor(kind, message, options) { + super(message, options); + this.kind = kind; + this.name = 'WhipError'; + } +} +export class RpcError extends WhipError { + rpc; + code; + constructor(rpc) { + super(rpc.data?.kind ?? 'execution_failed', rpc.message); + this.rpc = rpc; + this.name = 'RpcError'; + this.code = rpc.code; + } +} +export class DeliveryUncertainError extends WhipError { + commandId; + constructor(commandId, cause) { + super('delivery_uncertain', 'Command acceptance is unknown; reconcile its original identity before retrying.', { cause }); + this.commandId = commandId; + this.name = 'DeliveryUncertainError'; + } +} +export function asError(value) { + return value instanceof Error ? value : new WhipError('client_error', String(value)); +} +export function abortError(signal) { + return signal?.reason instanceof Error ? signal.reason : new DOMException('Operation aborted', 'AbortError'); +} +//# sourceMappingURL=errors.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/errors.js.map b/vendor/@whip/sdk/dist/errors.js.map new file mode 100644 index 0000000..6d74c4b --- /dev/null +++ b/vendor/@whip/sdk/dist/errors.js.map @@ -0,0 +1 @@ +{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,wFAAwF;AACxF,MAAM,OAAO,SAAU,SAAQ,KAAK;IACb;IAArB,YAAqB,IAAY,EAAE,OAAe,EAAE,OAAsB;QACxE,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QADL,SAAI,GAAJ,IAAI,CAAQ;QAE/B,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IAC1B,CAAC;CACF;AAED,MAAM,OAAO,QAAS,SAAQ,SAAS;IAEhB;IADZ,IAAI,CAAS;IACtB,YAAqB,GAAa;QAChC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,kBAAkB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QADtC,QAAG,GAAH,GAAG,CAAU;QAEhC,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;IACvB,CAAC;CACF;AAED,MAAM,OAAO,sBAAuB,SAAQ,SAAS;IAC9B;IAArB,YAAqB,SAAiB,EAAE,KAAe;QACrD,KAAK,CAAC,oBAAoB,EAAE,iFAAiF,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QADvG,cAAS,GAAT,SAAS,CAAQ;QAEpC,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,cAAc,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACvF,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAoB;IAC7C,OAAO,MAAM,EAAE,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,mBAAmB,EAAE,YAAY,CAAC,CAAC;AAC/G,CAAC","sourcesContent":["import type { RPCError } from '@whip/protocol';\n\n/** Errors here describe client/protocol failures, never a guessed execution outcome. */\nexport class WhipError extends Error {\n constructor(readonly kind: string, message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'WhipError';\n }\n}\n\nexport class RpcError extends WhipError {\n readonly code: number;\n constructor(readonly rpc: RPCError) {\n super(rpc.data?.kind ?? 'execution_failed', rpc.message);\n this.name = 'RpcError';\n this.code = rpc.code;\n }\n}\n\nexport class DeliveryUncertainError extends WhipError {\n constructor(readonly commandId: string, cause?: unknown) {\n super('delivery_uncertain', 'Command acceptance is unknown; reconcile its original identity before retrying.', { cause });\n this.name = 'DeliveryUncertainError';\n }\n}\n\nexport function asError(value: unknown): Error {\n return value instanceof Error ? value : new WhipError('client_error', String(value));\n}\n\nexport function abortError(signal?: AbortSignal): Error {\n return signal?.reason instanceof Error ? signal.reason : new DOMException('Operation aborted', 'AbortError');\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/executions.d.ts b/vendor/@whip/sdk/dist/executions.d.ts new file mode 100644 index 0000000..5d44b72 --- /dev/null +++ b/vendor/@whip/sdk/dist/executions.d.ts @@ -0,0 +1,91 @@ +import type { ContentHandle, RootSnapshot, StreamEvent } from '@whip/protocol'; +import type { DeepReadonly, HistoryView, SessionViewSnapshot } from './state.js'; +export interface ExecutionHostCall { + readonly display?: NonNullable; + readonly id: string; + readonly invocationId?: string; + readonly status: 'running' | 'completed' | 'failed' | 'cancelled' | 'interrupted' | 'unknown'; + readonly name: string; + readonly summary: string; + readonly duration: string; + readonly error?: string; +} +export interface ExecutionCell { + readonly partId?: string; + readonly kind: 'cell'; + readonly id: string; + readonly callId: string; + readonly agentId: string; + readonly turnId?: string; + /** First observed event, retained when this cell joins recorded history. */ + readonly eventSeq?: string; + /** Retained tool-event identities for joining bounded presentation prefixes. */ + readonly presentationSeqs?: readonly string[]; + /** Transcript sequence, when a recorded call is available. */ + readonly seq?: number; + readonly code: string; + readonly output: string; + readonly value?: string; + readonly hasValue?: boolean; + readonly executionEngine?: string; + readonly language?: string; + readonly error?: string; + /** Bounded checkpoint warning and omitted-binding details. */ + readonly scratch?: string; + readonly status: 'writing' | 'running' | 'completed' | 'failed' | 'interrupted' | 'cancelled' | 'unknown'; + readonly hosts: readonly ExecutionHostCall[]; + readonly steps?: number; + readonly quickjsJobs?: number; + /** Wall time observed by this client, never inferred from replay. */ + readonly observedStartedAt?: number; + readonly observedEndedAt?: number; + readonly truncated?: boolean; + /** This retained observation could not be safely associated with initial history. */ + readonly historyUnmatched?: boolean; + readonly body?: ContentHandle; + readonly codeBody?: ContentHandle; +} +export interface ExecutionRestart { + readonly kind: 'restart'; + readonly historyUnmatched?: boolean; + readonly id: string; + readonly agentId: string; + readonly text: string; + readonly seq?: number; +} +export type ExecutionRow = ExecutionCell | ExecutionRestart; +type ObservedRow = ExecutionRow & { + readonly eventSeq: string; + readonly turnId?: string; + readonly afterSeq: number; + readonly closed?: boolean; + readonly recordedId?: string; + readonly recordedResultSeq?: number; + readonly historyUnknown?: boolean; + readonly historyUnmatched?: boolean; +}; +/** Supplemental observation only. Recorded transcript bodies stay in HistoryView. */ +export interface ExecutionEvidence { + readonly rootId: string; + readonly revision: string; + readonly cursor: string; + readonly rows: readonly ObservedRow[]; + readonly truncated: boolean; +} +/** Decode the top-level code string while arguments are still arriving. */ +export declare function executionCode(args: string): string; +export declare function emptyExecutionEvidence(rootId: string, revision: string): ExecutionEvidence; +/** Pure folding called only by the existing root stream; no transport or secondary cache. */ +export declare function observeExecution(evidence: ExecutionEvidence, event: { + seq: string; + kind: string; + payload: unknown; +}, activeTurns: Readonly>, history: DeepReadonly>, observedAt?: number): ExecutionEvidence; +export declare function boundExecutionEvidence(evidence: ExecutionEvidence, maxBytes?: number): ExecutionEvidence; +export declare function settleExecutions(evidence: ExecutionEvidence, activeTurns?: Readonly>): ExecutionEvidence; +/** Reconcile loaded history without copying its bodies into the supplemental evidence. */ +export declare function reconcileExecutions(evidence: ExecutionEvidence, history: DeepReadonly>): ExecutionEvidence; +export declare function seedExecutions(previous: ExecutionEvidence | undefined, root: RootSnapshot, history: Record): ExecutionEvidence; +/** Immutable notebook rows derived from the loaded history and bounded observed evidence. */ +export declare function executionRows(snapshot: DeepReadonly, agentId: string): readonly ExecutionRow[]; +export {}; diff --git a/vendor/@whip/sdk/dist/executions.js b/vendor/@whip/sdk/dist/executions.js new file mode 100644 index 0000000..018ca55 --- /dev/null +++ b/vendor/@whip/sdk/dist/executions.js @@ -0,0 +1,511 @@ +const MAX_ENTRIES = 256; +const MAX_HOSTS = 128; +const MAX_BYTES = 1024 * 1024; +const FIELD_BYTES = 64 * 1024; +const PARSE_BYTES = 128 * 1024; +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); +const size = (value) => encoder.encode(JSON.stringify(value)).byteLength; +const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : undefined; +const text = (value) => typeof value === 'string' ? value : ''; +const key = (...parts) => JSON.stringify(parts); +const running = (row) => row.kind === 'cell' && (row.status === 'writing' || row.status === 'running'); +function hasHistoryBoundary(page) { + return !!page && (page.throughSeq > 0 || page.messages.length > 0 || (!page.loading && !page.error && !page.hasMore)); +} +function excerpt(value, limit = FIELD_BYTES, tail = false) { + // Bound the temporary encoder allocation as well as the retained UTF-8 result. + const part = value.length > limit ? tail ? value.slice(-limit) : value.slice(0, limit) : value; + const encoded = encoder.encode(part); + if (encoded.byteLength <= limit) + return part; + let start = tail ? encoded.length - limit : 0; + let end = tail ? encoded.length : limit; + if (tail) + while ((encoded[start] & 0xc0) === 0x80) + start++; + else + while ((encoded[end] & 0xc0) === 0x80) + end--; + return decoder.decode(encoded.subarray(start, end)); +} +/** Decode the top-level code string while arguments are still arriving. */ +export function executionCode(args) { + const input = excerpt(args, PARSE_BYTES); + let index = 0; + const space = () => { while (/\s/.test(input[index] ?? '') && index < input.length) + index++; }; + const string = () => { + let value = ''; + index++; + while (index < input.length) { + const char = input[index++]; + if (char === '"') + return { value, complete: true }; + if (char !== '\\') { + if (char < ' ') + return { value, complete: false }; + value += char; + continue; + } + const escape = input[index++]; + if (!escape) + break; + if (escape === 'u') { + const hex = input.slice(index, index + 4); + if (!/^[\da-f]{4}$/i.test(hex)) + break; + value += String.fromCharCode(Number.parseInt(hex, 16)); + index += 4; + } + else { + const escapes = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' }; + if (!(escape in escapes)) + break; + value += escapes[escape]; + } + } + return { value, complete: false }; + }; + space(); + if (input[index++] !== '{') + return ''; + while (index < input.length) { + space(); + if (input[index] !== '"') + return ''; + const name = string(); + space(); + if (!name.complete || input[index++] !== ':') + return ''; + space(); + if (name.value === 'code') + return input[index] === '"' ? excerpt(string().value) : ''; + // Skip other complete JSON values without mistaking nested or quoted code keys for the top level. + let depth = 0; + while (index < input.length) { + const char = input[index]; + if (char === '"') { + if (!string().complete) + return ''; + continue; + } + if (char === '{' || char === '[') + depth++; + if (char === '}' || char === ']') { + if (depth === 0) + return ''; + depth--; + } + if (char === ',' && depth === 0) { + index++; + break; + } + index++; + } + } + return ''; +} +function result(raw) { + const failed = raw.startsWith('Error:'); + // The dispatcher appends one JSON result after its possibly multiline error. + const boundary = failed ? raw.lastIndexOf('\n{') : -1; + const payload = failed && boundary >= 0 ? raw.slice(boundary + 1) : raw; + const error = failed ? excerpt(raw.slice(6, boundary >= 0 ? boundary : undefined).trim()) : undefined; + const fallback = failed ? { status: 'failed', output: '', error, ...(raw.length > FIELD_BYTES ? { truncated: true } : {}) } : { status: 'unknown', output: '' }; + const bounded = excerpt(payload, PARSE_BYTES); + if (bounded !== payload) + return { ...fallback, truncated: true }; + let parsed; + try { + parsed = record(JSON.parse(bounded)); + } + catch { /* Incomplete or unavailable result. */ } + if (!parsed || (parsed.output !== undefined && typeof parsed.output !== 'string')) + return fallback; + const versioned = parsed.format_version !== undefined; + const metrics = record(parsed.metrics); + if (versioned) { + if (parsed.format_version !== 2 || typeof parsed.has_value !== 'boolean' || (parsed.has_value && !('value' in parsed)) + || !((parsed.execution_engine === 'starlark' && parsed.language === 'starlark') || (parsed.execution_engine === 'quickjs' && parsed.language === 'javascript')) + || (parsed.metrics !== undefined && !metrics)) + return fallback; + if (metrics && Object.values(metrics).some(value => typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0)) + return fallback; + } + else if (!('value' in parsed) || typeof parsed.steps !== 'number' || !Number.isInteger(parsed.steps) || parsed.steps < 0) + return fallback; + const hasValue = versioned ? parsed.has_value === true : parsed.value !== null || typeof parsed.value_preview === 'string'; + const output = text(parsed.output); + const value = hasValue ? typeof parsed.value_preview === 'string' ? parsed.value_preview : JSON.stringify(parsed.value) : ''; + const scratch = record(parsed.scratch); + return { + hasResult: true, status: failed ? 'failed' : 'completed', output: excerpt(output, FIELD_BYTES, true), hasValue, + ...(hasValue ? { value: excerpt(value) } : {}), + executionEngine: versioned ? text(parsed.execution_engine) : 'starlark', language: versioned ? text(parsed.language) : 'starlark', + ...(error ? { error } : {}), + ...(scratch ? { scratch: scratchText(scratch) } : {}), + ...(versioned ? { + ...(parsed.execution_engine === 'starlark' && typeof metrics?.starlark_steps === 'number' ? { steps: metrics.starlark_steps } : {}), + ...(parsed.execution_engine === 'quickjs' && typeof metrics?.quickjs_jobs === 'number' ? { quickjsJobs: metrics.quickjs_jobs } : {}), + } : Number.isSafeInteger(parsed.steps) ? { steps: parsed.steps } : { truncated: true }), + ...(parsed.truncated === true || output !== excerpt(output, FIELD_BYTES, true) || value !== excerpt(value) ? { truncated: true } : {}), + ...(record(parsed.restored) ? { restart: restartText(record(parsed.restored)) } : {}), + }; +} +function scratchText(payload) { + const lines = text(payload.warning) ? [text(payload.warning)] : []; + for (const item of Array.isArray(payload.skipped) ? payload.skipped.slice(0, 30) : []) { + const skipped = record(item); + if (skipped) + lines.push(`${text(skipped.name)}: ${text(skipped.reason)}`); + } + const omitted = count(payload.skipped_omitted); + if (omitted) + lines.push(`${omitted} additional bindings omitted`); + return excerpt(lines.join('\n'), 8192); +} +const count = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : 0; +function restartText(payload) { + const restored = (Array.isArray(payload.restored) ? payload.restored.length : 0) + count(payload.restored_omitted); + const failed = payload.not_restored ?? payload.failed; + const skipped = (Array.isArray(failed) ? failed.length : 0) + count(payload.failed_omitted); + return `Restarted · restored ${restored}${skipped ? ` · ${skipped} skipped` : ''}`; +} +function reference(value) { + return value ? { reference_id: value.reference_id, digest: value.digest, size: value.size, + ...(value.media_type ? { media_type: value.media_type } : {}), ...(value.source ? { source: value.source } : {}) } : undefined; +} +function operationDisplay(value) { + const fields = record(value) ?? {}; + return Object.fromEntries(Object.entries({ target: 4096, command: 512, query: 512, child_id: 256, label: 160 }) + .flatMap(([name, limit]) => typeof fields[name] === 'string' && (!['target', 'child_id'].includes(name) || encoder.encode(fields[name]).length <= limit) ? [[name, excerpt(text(fields[name]), limit)]] : [])); +} +function historyRows(rootId, agentId, history) { + const rows = []; + const pending = new Map(); + for (const item of history?.messages ?? []) { + const presentation = item.message?.presentation ?? item.presentation; + const parts = presentation?.version === 1 ? presentation.parts ?? [] : []; + const resultPart = parts.find(part => part.kind === 'result' && part.call_id); + const message = item.message ?? (resultPart ? { role: 'tool', tool_call_id: resultPart.call_id, name: resultPart.tool_name, content: '' } : undefined); + const calls = message?.tool_calls ?? parts.filter(part => part.kind === 'tool' && part.call_id).map(part => ({ id: part.call_id, function: { name: part.tool_name ?? '', arguments: '' } })); + for (const [index, call] of calls.entries()) { + if (call.function.name !== 'rlm_exec') + continue; + const part = parts.find(part => part.kind === 'tool' && part.call_id === call.id); + const id = part ? key(rootId, agentId, history.revision, 'part', part.id) : key(rootId, agentId, history.revision, 'history', item.seq, index); + const hosts = (part?.hosts ?? []).slice(-MAX_HOSTS).map(host => ({ + id: key(id, host.invocation_id), invocationId: host.invocation_id, name: excerpt(host.name, 1024), + status: ['completed', 'failed', 'cancelled', 'interrupted'].includes(host.status) ? host.status : 'unknown', + summary: excerpt(host.summary ?? '', 2048), duration: excerpt(host.duration ?? '', 128), + ...(host.error ? { error: excerpt(host.error, 2048) } : {}), ...(host.display ? { display: operationDisplay(host.display) } : {}), + })); + pending.set(call.id, rows.length); + rows.push({ kind: 'cell', id, callId: call.id, agentId, + ...(part ? { partId: part.id, turnId: presentation.turn_id } : {}), + seq: item.seq, code: executionCode(call.function.arguments), output: '', status: 'unknown', hosts, + ...(item.body ? { codeBody: reference(item.body), truncated: true } : {}), + ...(call.function.arguments.length > FIELD_BYTES || presentation?.omitted || part?.omitted ? { truncated: true } : {}), }); + } + if (!message) + continue; + if (message.role !== 'tool' || !message.tool_call_id) + continue; + let index = pending.get(message.tool_call_id); + let prior = index === undefined ? undefined : rows[index]; + const unmatched = prior?.kind === 'cell' && prior.turnId && presentation?.turn_id && prior.turnId !== presentation.turn_id; + if (unmatched) { + index = undefined; + prior = undefined; + } + if (prior?.kind !== 'cell' && message.name !== 'rlm_exec') + continue; + const decoded = result(typeof message.content === 'string' ? message.content : ''); + const { restart, hasResult: _hasResult, ...fields } = decoded; + if (!item.message && resultPart && ['completed', 'failed', 'cancelled', 'interrupted'].includes(resultPart.status ?? '')) + fields.status = resultPart.status; + const cell = { + kind: 'cell', id: key(rootId, agentId, history.revision, 'history-result', item.seq), callId: message.tool_call_id, + agentId, seq: item.seq, code: '', hosts: [], ...(prior?.kind === 'cell' ? prior : {}), ...fields, resultSeq: item.seq, + ...(unmatched ? { historyUnmatched: true } : {}), + ...(item.body ? { body: reference(item.body), truncated: true } : {}), + }; + if (index !== undefined) + rows[index] = cell; + else + rows.push(cell); + if (!unmatched) + pending.delete(message.tool_call_id); + if (restart) + rows.push({ kind: 'restart', id: `${cell.id}:restart`, agentId, seq: cell.seq, text: restart }); + } + return rows; +} +export function emptyExecutionEvidence(rootId, revision) { + return { rootId, revision, cursor: '0', rows: [], truncated: false }; +} +/** Pure folding called only by the existing root stream; no transport or secondary cache. */ +export function observeExecution(evidence, event, activeTurns, history, observedAt) { + if (BigInt(event.seq) <= BigInt(evidence.cursor)) + return evidence; + const payload = record(event.payload) ?? {}; + const agentId = text(payload.agent_id) || evidence.rootId; + const rows = [...evidence.rows]; + const cursor = event.seq; + const afterSeq = history[agentId]?.throughSeq ?? 0; + const turnId = text(payload.turn_id) || activeTurns[agentId]; + if (event.kind === 'scratch.restored') { + rows.push({ kind: 'restart', id: key(evidence.rootId, agentId, evidence.revision, 'restart', event.seq), agentId, + text: restartText(payload), eventSeq: event.seq, afterSeq, turnId, historyUnknown: !hasHistoryBoundary(history[agentId]) }); + } + else if (/^(agent\.)?turn\./.test(event.kind)) { + for (let index = 0; index < rows.length; index++) { + const row = rows[index]; + if (row.kind !== 'cell' || !running(row) || row.agentId !== agentId) + continue; + if (event.kind.endsWith('.started') || !row.turnId || row.turnId === payload.turn_id) { + const status = event.kind.endsWith('.cancelled') ? 'cancelled' : event.kind.endsWith('.interrupted') ? 'interrupted' + : event.kind.endsWith('.failed') ? 'failed' : 'unknown'; + rows[index] = { ...row, status, hosts: settleHosts(row.hosts, status), closed: true, ...(observedAt === undefined ? {} : { observedEndedAt: observedAt }), + ...(payload.error ? { error: excerpt(text(payload.error)) } : {}) }; + } + } + } + else if (event.kind === 'stream.discard') { + return { ...evidence, cursor, rows: rows.filter(row => !(row.kind === 'cell' && row.agentId === agentId && row.turnId === turnId && row.status === 'writing')) }; + } + else if (event.kind.startsWith('stream.')) { + const callId = text(payload.id); + const partId = text(payload.part_id); + if (!callId) + return { ...evidence, cursor }; + let index = -1; + const invocationId = event.kind.startsWith('stream.cell.host') ? text(payload.invocation_id) : ''; + if (invocationId) + index = rows.findIndex(row => row.kind === 'cell' && row.agentId === agentId && row.callId === callId + && (!turnId || !row.turnId || row.turnId === turnId) && (!partId || !row.partId || row.partId === partId) && row.hosts.some(host => host.id === key(row.id, invocationId))); + const knownInvocation = index >= 0; + // Once its start has been evicted, an invocation's owner is unknowable. + // A provider can reuse the tool ID, even within the same turn. + if (invocationId && event.kind === 'stream.cell.host' && index < 0) + return { ...evidence, cursor, truncated: true }; + for (let candidate = rows.length - 1; candidate >= 0; candidate--) { + if (index >= 0) + break; + const row = rows[candidate]; + if (row.kind === 'cell' && row.agentId === agentId && row.callId === callId + && (!turnId || !row.turnId || row.turnId === turnId) && (!partId || !row.partId || row.partId === partId)) { + index = candidate; + break; + } + } + let prior = rows[index]; + // A reconnect can lose the next cell's tool prefix. Reused provider IDs do + // not prove that its new invocation belongs to our uncertain prior cell. + if (event.kind === 'stream.cell.host.started' && invocationId && !knownInvocation + && prior?.kind === 'cell' && prior.status === 'unknown') + return { ...evidence, cursor, truncated: true }; + if ((event.kind === 'stream.tool.call' || event.kind === 'stream.tool.started') && prior?.kind === 'cell' + && prior.closed) { + index = -1; + prior = undefined; + } + const hostStart = event.kind === 'stream.cell.host.started' && invocationId && turnId && turnId === activeTurns[agentId]; + if (!prior && !hostStart && (payload.name !== 'rlm_exec' || !['stream.tool.call', 'stream.tool.started', 'stream.tool.completed'].includes(event.kind))) + return { ...evidence, cursor }; + if (payload.name && payload.name !== 'rlm_exec' && !event.kind.startsWith('stream.cell.host')) + return { ...evidence, cursor }; + let cell = prior?.kind === 'cell' ? { ...prior, ...(partId ? { partId } : {}) } : { + kind: 'cell', id: key(evidence.rootId, agentId, evidence.revision, partId ? 'part' : 'event', partId || event.seq), callId, agentId, ...(partId ? { partId } : {}), + eventSeq: event.seq, afterSeq, turnId, historyUnknown: !hasHistoryBoundary(history[agentId]), code: '', output: '', status: 'unknown', hosts: [], + }; + switch (event.kind) { + case 'stream.tool.call': + case 'stream.tool.started': { + const args = text(payload.args); + cell = { ...cell, ...(args ? { code: executionCode(args) } : payload.truncated ? { code: '' } : {}), status: event.kind.endsWith('.started') ? 'running' : 'writing', + ...(event.kind.endsWith('.started') && observedAt !== undefined && cell.observedStartedAt === undefined ? { observedStartedAt: observedAt } : {}), + ...(args.length > FIELD_BYTES ? { truncated: true } : {}) }; + break; + } + case 'stream.tool.output': { + const output = text(payload.text); + const preview = excerpt(output, FIELD_BYTES, true); + cell = { ...cell, output: preview, ...(cell.status === 'unknown' && !cell.closed && turnId ? { status: 'running' } : {}), + ...(output !== preview ? { truncated: true } : {}) }; + break; + } + case 'stream.cell.host.started': + case 'stream.cell.host': { + const started = event.kind.endsWith('.started'); + const invocationId = text(payload.invocation_id); + // Legacy completions have only an event occurrence; never join by name. + const id = key(cell.id, invocationId || event.seq); + const hosts = [...cell.hosts]; + const index = hosts.findIndex(host => host.id === id); + if (started && (cell.closed || index >= 0)) + break; + const host = { id, ...(invocationId ? { invocationId } : {}), name: excerpt(text(payload.name), 1024), + status: started ? 'running' : payload.host_status === 'cancelled' ? 'cancelled' : payload.result || payload.host_status === 'failed' ? 'failed' : payload.truncated ? 'unknown' : 'completed', + ...(payload.display ? { display: operationDisplay(payload.display) } : hosts[index]?.display ? { display: hosts[index].display } : {}), + summary: excerpt(text(payload.args), 2048), duration: started ? '' : excerpt(text(payload.text), 128), + ...(payload.result ? { error: excerpt(text(payload.result), 2048) } : {}) }; + if (index >= 0) + hosts[index] = host; + else + hosts.push(host); + // Keep recent operations (especially the current invocation) at the cap. + const overflow = hosts.length > MAX_HOSTS; + cell = { ...cell, hosts: hosts.slice(-MAX_HOSTS), ...(!cell.closed && started ? { status: 'running' } : {}), + ...(overflow || text(payload.name).length > 1024 || text(payload.args).length > 2048 || text(payload.result).length > 2048 ? { truncated: true } : {}) }; + break; + } + case 'stream.tool.completed': { + const { restart, hasResult, ...fields } = result(text(payload.result)); + cell = { ...cell, ...fields, hosts: settleHosts(cell.hosts, fields.status), closed: true, output: hasResult ? fields.output : cell.output, + ...(observedAt === undefined ? {} : { observedEndedAt: observedAt }) }; + if (restart && !rows.some(row => row.kind === 'restart' && row.agentId === agentId && row.turnId === turnId && row.text === restart && BigInt(row.eventSeq) >= BigInt(cell.eventSeq))) { + rows.push({ kind: 'restart', id: `${cell.id}:restart`, agentId, text: restart, eventSeq: event.seq, afterSeq, turnId, historyUnknown: !hasHistoryBoundary(history[agentId]) }); + } + break; + } + default: return { ...evidence, cursor }; + } + if (payload.truncated) + cell = { ...cell, truncated: true }; + if (event.kind.startsWith('stream.tool.')) + cell = { ...cell, presentationSeqs: [...(cell.presentationSeqs ?? []), event.seq].slice(-128) }; + if (index < 0) + rows.push(cell); + else + rows[index] = cell; + } + return boundExecutionEvidence({ ...evidence, cursor, rows }); +} +export function boundExecutionEvidence(evidence, maxBytes = MAX_BYTES) { + const rows = [...evidence.rows]; + let truncated = evidence.truncated; + const over = () => rows.length > MAX_ENTRIES || size({ ...evidence, rows, truncated }) > maxBytes; + while (rows.length && over()) { + const completed = rows.findIndex(row => !running(row)); + rows.splice(completed < 0 ? 0 : completed, 1); + truncated = true; + } + return { ...evidence, rows, truncated }; +} +export function settleExecutions(evidence, activeTurns) { + return { ...evidence, rows: evidence.rows.map(row => row.kind === 'cell' && !row.closed && (!activeTurns || !activeTurns[row.agentId] + || (row.turnId && row.turnId !== activeTurns[row.agentId])) ? { ...row, status: 'unknown', hosts: settleHosts(row.hosts, 'unknown'), ...(activeTurns ? { closed: true } : {}) } : row) }; +} +function settleHosts(hosts, status) { + // A cell/turn outcome does not prove the host operation succeeded or failed. + return hosts.map(host => host.status === 'running' ? { ...host, + status: status === 'cancelled' || status === 'interrupted' ? status : 'unknown' } : host); +} +/** Reconcile loaded history without copying its bodies into the supplemental evidence. */ +export function reconcileExecutions(evidence, history) { + const recorded = new Map(); + for (const agentId of new Set(evidence.rows.map(row => row.agentId))) { + recorded.set(agentId, historyRows(evidence.rootId, agentId, history[agentId]).filter((row) => row.kind === 'cell')); + } + const used = new Set(); + // Match occurrences in order. There is no turn ID in transcript messages, + // so an agent's first history read cannot prove which already-observed call + // it contains. Establish a boundary and join subsequent commits only. + const rows = evidence.rows.map(row => { + const exact = row.kind === 'cell' && row.partId ? recorded.get(row.agentId)?.find(cell => cell.partId === row.partId && cell.turnId === row.turnId) : undefined; + if (exact) { + used.add(exact.id); + return { ...row, seq: exact.seq, recordedId: exact.id, recordedResultSeq: exact.resultSeq, historyUnknown: false, historyUnmatched: undefined }; + } + if (row.historyUnknown) { + const page = history[row.agentId]; + return hasHistoryBoundary(page) ? { ...row, afterSeq: page.throughSeq, historyUnknown: false, + ...(row.kind === 'restart' || row.closed ? { historyUnmatched: true } : {}) } : row; + } + if (row.kind !== 'cell' || row.historyUnmatched) + return row; + const cells = recorded.get(row.agentId) ?? []; + const match = cells.find(cell => !used.has(cell.id) && cell.callId === row.callId + && (!row.partId || !cell.partId || row.partId === cell.partId) && (!row.turnId || !cell.turnId || row.turnId === cell.turnId) && (row.recordedId + ? cell.id === row.recordedId || (row.recordedResultSeq !== undefined && cell.resultSeq === row.recordedResultSeq) + : (cell.seq ?? 0) > row.afterSeq)); + if (!match) + return row; + used.add(match.id); + return { ...row, seq: match.seq, recordedId: match.id, recordedResultSeq: match.resultSeq, historyUnmatched: undefined }; + }); + return boundExecutionEvidence({ ...evidence, rows }); +} +export function seedExecutions(previous, root, history) { + let evidence = previous?.rootId === root.root_id && previous.revision === root.history_revision + ? previous : emptyExecutionEvidence(root.root_id, root.history_revision); + const events = [...(root.presentation ?? []), ...Object.entries(root.agent_presentations ?? {}).flatMap(([agentId, items]) => (items ?? []).map(event => ({ ...event, payload: { ...record(event.payload), agent_id: agentId } })))] + .sort((a, b) => BigInt(a.seq) < BigInt(b.seq) ? -1 : BigInt(a.seq) > BigInt(b.seq) ? 1 : 0); + if (evidence.rows.some(row => row.kind === 'cell' && row.status === 'unknown' && !row.closed)) { + let replay = emptyExecutionEvidence(root.root_id, root.history_revision); + for (const event of events) + replay = observeExecution(replay, event, root.active_turns, history); + evidence = { ...evidence, rows: evidence.rows.map(row => { + if (row.kind !== 'cell' || row.closed || row.status !== 'unknown' || !row.turnId || root.active_turns[row.agentId] !== row.turnId) + return row; + const confirmed = replay.rows.find((item) => item.kind === 'cell' + && item.agentId === row.agentId && item.turnId === row.turnId && item.callId === row.callId + && (item.eventSeq === row.eventSeq || item.hosts.some(host => host.invocationId && row.hosts.some(old => old.invocationId === host.invocationId)))); + if (!confirmed || confirmed.closed) + return row; + return { ...row, status: confirmed.status, observedStartedAt: undefined, hosts: row.hosts.map(host => { + const current = confirmed.hosts.find(item => host.invocationId && item.invocationId === host.invocationId); + return host.status === 'unknown' && current ? { ...current, id: host.id } : host; + }) }; + }) }; + } + for (const event of events) + evidence = observeExecution(evidence, event, root.active_turns, history); + return reconcileExecutions(settleExecutions({ ...evidence, cursor: root.cursor }, root.active_turns), history); +} +/** Immutable notebook rows derived from the loaded history and bounded observed evidence. */ +export function executionRows(snapshot, agentId) { + const root = snapshot.root; + if (!root) + return []; + const engine = root.meta?.execution_engine ?? 'starlark'; + const language = engine === 'quickjs' ? 'javascript' : engine === 'starlark' ? 'starlark' : undefined; + const history = historyRows(root.root_id, agentId, snapshot.history[agentId]); + const evidence = snapshot.executions; + const observed = evidence?.revision === root.history_revision && evidence.rootId === root.root_id ? evidence.rows.filter(row => row.agentId === agentId) : []; + const recorded = new Map(observed.filter((row) => row.kind === 'cell' && row.recordedId !== undefined) + .map(row => [row.recordedId, row])); + const used = new Set(); + const rows = history.map(row => { + if (row.kind === 'restart') { + const live = observed.find(item => item.kind === 'restart' && !item.historyUnknown && !item.historyUnmatched && !used.has(item.id) && item.text === row.text + && row.seq !== undefined && row.seq > item.afterSeq); + if (!live) + return row; + used.add(live.id); + return { ...row, id: live.id }; + } + const live = recorded.get(row.id); + if (!live) + return row; + used.add(live.id); + return { ...live, ...row, id: live.id, code: row.code || live.code, hosts: row.hosts.length ? row.hosts : live.hosts, + observedStartedAt: live.observedStartedAt, observedEndedAt: live.observedEndedAt, + ...(row.status === 'unknown' ? { status: live.status, output: row.output || live.output, value: row.value ?? live.value, error: row.error ?? live.error, steps: row.steps ?? live.steps } : {}), + ...(live.truncated || row.truncated ? { truncated: true } : {}) }; + }); + for (const row of observed) { + if (used.has(row.id)) + continue; + const following = row.historyUnknown ? -1 : rows.findIndex(item => item.seq !== undefined && item.seq > (row.seq ?? row.afterSeq)); + rows.splice(following < 0 ? rows.length : following, 0, row); + } + return Object.freeze(rows.map(row => Object.freeze(row.kind === 'cell' + ? { ...row, executionEngine: row.executionEngine ?? engine, + language: row.language ?? language, hosts: Object.freeze(row.hosts.map(host => Object.freeze(host))), + ...(row.presentationSeqs ? { presentationSeqs: Object.freeze([...row.presentationSeqs]) } : {}), + ...(row.body ? { body: Object.freeze(row.body) } : {}), ...(row.codeBody ? { codeBody: Object.freeze(row.codeBody) } : {}) } : row))); +} +//# sourceMappingURL=executions.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/executions.js.map b/vendor/@whip/sdk/dist/executions.js.map new file mode 100644 index 0000000..d72d8f3 --- /dev/null +++ b/vendor/@whip/sdk/dist/executions.js.map @@ -0,0 +1 @@ +{"version":3,"file":"executions.js","sourceRoot":"","sources":["../src/executions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentHandle, RootSnapshot, StreamEvent } from '@whip/protocol';\nimport type { DeepReadonly, HistoryView, SessionViewSnapshot } from './state.js';\n\nexport interface ExecutionHostCall {\n readonly display?: NonNullable;\n readonly id: string;\n readonly invocationId?: string;\n readonly status: 'running' | 'completed' | 'failed' | 'cancelled' | 'interrupted' | 'unknown';\n readonly name: string;\n readonly summary: string;\n readonly duration: string;\n readonly error?: string;\n}\n\nexport interface ExecutionCell {\n readonly partId?: string;\n readonly kind: 'cell';\n readonly id: string;\n readonly callId: string;\n readonly agentId: string;\n readonly turnId?: string;\n /** First observed event, retained when this cell joins recorded history. */\n readonly eventSeq?: string;\n /** Retained tool-event identities for joining bounded presentation prefixes. */\n readonly presentationSeqs?: readonly string[];\n /** Transcript sequence, when a recorded call is available. */\n readonly seq?: number;\n readonly code: string;\n readonly output: string;\n readonly value?: string;\n readonly hasValue?: boolean;\n readonly executionEngine?: string;\n readonly language?: string;\n readonly error?: string;\n /** Bounded checkpoint warning and omitted-binding details. */\n readonly scratch?: string;\n readonly status: 'writing' | 'running' | 'completed' | 'failed' | 'interrupted' | 'cancelled' | 'unknown';\n readonly hosts: readonly ExecutionHostCall[];\n readonly steps?: number;\n readonly quickjsJobs?: number;\n /** Wall time observed by this client, never inferred from replay. */\n readonly observedStartedAt?: number;\n readonly observedEndedAt?: number;\n readonly truncated?: boolean;\n /** This retained observation could not be safely associated with initial history. */\n readonly historyUnmatched?: boolean;\n readonly body?: ContentHandle;\n readonly codeBody?: ContentHandle;\n}\n\nexport interface ExecutionRestart {\n readonly kind: 'restart';\n readonly historyUnmatched?: boolean;\n readonly id: string;\n readonly agentId: string;\n readonly text: string;\n readonly seq?: number;\n}\n\nexport type ExecutionRow = ExecutionCell | ExecutionRestart;\ntype ObservedRow = ExecutionRow & {\n readonly eventSeq: string;\n readonly turnId?: string;\n readonly afterSeq: number;\n readonly closed?: boolean;\n readonly recordedId?: string;\n readonly recordedResultSeq?: number;\n readonly historyUnknown?: boolean;\n readonly historyUnmatched?: boolean;\n};\n\n/** Supplemental observation only. Recorded transcript bodies stay in HistoryView. */\nexport interface ExecutionEvidence {\n readonly rootId: string;\n readonly revision: string;\n readonly cursor: string;\n readonly rows: readonly ObservedRow[];\n readonly truncated: boolean;\n}\n\nconst MAX_ENTRIES = 256;\nconst MAX_HOSTS = 128;\nconst MAX_BYTES = 1024 * 1024;\nconst FIELD_BYTES = 64 * 1024;\nconst PARSE_BYTES = 128 * 1024;\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\nconst size = (value: unknown): number => encoder.encode(JSON.stringify(value)).byteLength;\nconst record = (value: unknown): Record | undefined => value && typeof value === 'object' && !Array.isArray(value) ? value as Record : undefined;\nconst text = (value: unknown): string => typeof value === 'string' ? value : '';\nconst key = (...parts: (string | number)[]): string => JSON.stringify(parts);\nconst running = (row: ExecutionRow): boolean => row.kind === 'cell' && (row.status === 'writing' || row.status === 'running');\n\nfunction hasHistoryBoundary(page: DeepReadonly | undefined): boolean {\n return !!page && (page.throughSeq > 0 || page.messages.length > 0 || (!page.loading && !page.error && !page.hasMore));\n}\n\nfunction excerpt(value: string, limit = FIELD_BYTES, tail = false): string {\n // Bound the temporary encoder allocation as well as the retained UTF-8 result.\n const part = value.length > limit ? tail ? value.slice(-limit) : value.slice(0, limit) : value;\n const encoded = encoder.encode(part);\n if (encoded.byteLength <= limit) return part;\n let start = tail ? encoded.length - limit : 0;\n let end = tail ? encoded.length : limit;\n if (tail) while ((encoded[start]! & 0xc0) === 0x80) start++;\n else while ((encoded[end]! & 0xc0) === 0x80) end--;\n return decoder.decode(encoded.subarray(start, end));\n}\n\n/** Decode the top-level code string while arguments are still arriving. */\nexport function executionCode(args: string): string {\n const input = excerpt(args, PARSE_BYTES);\n let index = 0;\n const space = () => { while (/\\s/.test(input[index] ?? '') && index < input.length) index++; };\n const string = (): { value: string; complete: boolean } => {\n let value = '';\n index++;\n while (index < input.length) {\n const char = input[index++]!;\n if (char === '\"') return { value, complete: true };\n if (char !== '\\\\') {\n if (char < ' ') return { value, complete: false };\n value += char;\n continue;\n }\n const escape = input[index++];\n if (!escape) break;\n if (escape === 'u') {\n const hex = input.slice(index, index + 4);\n if (!/^[\\da-f]{4}$/i.test(hex)) break;\n value += String.fromCharCode(Number.parseInt(hex, 16));\n index += 4;\n } else {\n const escapes: Record = { '\"': '\"', '\\\\': '\\\\', '/': '/', b: '\\b', f: '\\f', n: '\\n', r: '\\r', t: '\\t' };\n if (!(escape in escapes)) break;\n value += escapes[escape];\n }\n }\n return { value, complete: false };\n };\n space();\n if (input[index++] !== '{') return '';\n while (index < input.length) {\n space();\n if (input[index] !== '\"') return '';\n const name = string();\n space();\n if (!name.complete || input[index++] !== ':') return '';\n space();\n if (name.value === 'code') return input[index] === '\"' ? excerpt(string().value) : '';\n // Skip other complete JSON values without mistaking nested or quoted code keys for the top level.\n let depth = 0;\n while (index < input.length) {\n const char = input[index];\n if (char === '\"') { if (!string().complete) return ''; continue; }\n if (char === '{' || char === '[') depth++;\n if (char === '}' || char === ']') { if (depth === 0) return ''; depth--; }\n if (char === ',' && depth === 0) { index++; break; }\n index++;\n }\n }\n return '';\n}\n\ntype Result = Pick & { restart?: string; hasResult?: boolean };\nfunction result(raw: string): Result {\n const failed = raw.startsWith('Error:');\n // The dispatcher appends one JSON result after its possibly multiline error.\n const boundary = failed ? raw.lastIndexOf('\\n{') : -1;\n const payload = failed && boundary >= 0 ? raw.slice(boundary + 1) : raw;\n const error = failed ? excerpt(raw.slice(6, boundary >= 0 ? boundary : undefined).trim()) : undefined;\n const fallback: Result = failed ? { status: 'failed', output: '', error, ...(raw.length > FIELD_BYTES ? { truncated: true } : {}) } : { status: 'unknown', output: '' };\n const bounded = excerpt(payload, PARSE_BYTES);\n if (bounded !== payload) return { ...fallback, truncated: true };\n let parsed: Record | undefined;\n try { parsed = record(JSON.parse(bounded)); } catch { /* Incomplete or unavailable result. */ }\n if (!parsed || (parsed.output !== undefined && typeof parsed.output !== 'string')) return fallback;\n const versioned = parsed.format_version !== undefined;\n const metrics = record(parsed.metrics);\n if (versioned) {\n if (parsed.format_version !== 2 || typeof parsed.has_value !== 'boolean' || (parsed.has_value && !('value' in parsed))\n || !((parsed.execution_engine === 'starlark' && parsed.language === 'starlark') || (parsed.execution_engine === 'quickjs' && parsed.language === 'javascript'))\n || (parsed.metrics !== undefined && !metrics)) return fallback;\n if (metrics && Object.values(metrics).some(value => typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0)) return fallback;\n } else if (!('value' in parsed) || typeof parsed.steps !== 'number' || !Number.isInteger(parsed.steps) || parsed.steps < 0) return fallback;\n const hasValue = versioned ? parsed.has_value === true : parsed.value !== null || typeof parsed.value_preview === 'string';\n const output = text(parsed.output);\n const value = hasValue ? typeof parsed.value_preview === 'string' ? parsed.value_preview : JSON.stringify(parsed.value) : '';\n const scratch = record(parsed.scratch);\n return {\n hasResult: true, status: failed ? 'failed' : 'completed', output: excerpt(output, FIELD_BYTES, true), hasValue,\n ...(hasValue ? { value: excerpt(value) } : {}),\n executionEngine: versioned ? text(parsed.execution_engine) : 'starlark', language: versioned ? text(parsed.language) : 'starlark',\n ...(error ? { error } : {}),\n ...(scratch ? { scratch: scratchText(scratch) } : {}),\n ...(versioned ? {\n ...(parsed.execution_engine === 'starlark' && typeof metrics?.starlark_steps === 'number' ? { steps: metrics.starlark_steps } : {}),\n ...(parsed.execution_engine === 'quickjs' && typeof metrics?.quickjs_jobs === 'number' ? { quickjsJobs: metrics.quickjs_jobs } : {}),\n } : Number.isSafeInteger(parsed.steps) ? { steps: parsed.steps as number } : { truncated: true }),\n ...(parsed.truncated === true || output !== excerpt(output, FIELD_BYTES, true) || value !== excerpt(value) ? { truncated: true } : {}),\n ...(record(parsed.restored) ? { restart: restartText(record(parsed.restored)!) } : {}),\n };\n}\n\nfunction scratchText(payload: Record): string {\n const lines = text(payload.warning) ? [text(payload.warning)] : [];\n for (const item of Array.isArray(payload.skipped) ? payload.skipped.slice(0, 30) : []) {\n const skipped = record(item);\n if (skipped) lines.push(`${text(skipped.name)}: ${text(skipped.reason)}`);\n }\n const omitted = count(payload.skipped_omitted);\n if (omitted) lines.push(`${omitted} additional bindings omitted`);\n return excerpt(lines.join('\\n'), 8192);\n}\n\nconst count = (value: unknown): number => typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : 0;\n\nfunction restartText(payload: Record): string {\n const restored = (Array.isArray(payload.restored) ? payload.restored.length : 0) + count(payload.restored_omitted);\n const failed = payload.not_restored ?? payload.failed;\n const skipped = (Array.isArray(failed) ? failed.length : 0) + count(payload.failed_omitted);\n return `Restarted · restored ${restored}${skipped ? ` · ${skipped} skipped` : ''}`;\n}\n\nfunction reference(value: DeepReadonly | null | undefined): ContentHandle | undefined {\n return value ? { reference_id: value.reference_id, digest: value.digest, size: value.size,\n ...(value.media_type ? { media_type: value.media_type } : {}), ...(value.source ? { source: value.source } : {}) } : undefined;\n}\n\ntype RecordedRow = ExecutionRow & { readonly resultSeq?: number };\ntype RecordedCell = ExecutionCell & { readonly resultSeq?: number };\n\nfunction operationDisplay(value: unknown): NonNullable {\n const fields = record(value) ?? {};\n return Object.fromEntries(Object.entries({ target: 4096, command: 512, query: 512, child_id: 256, label: 160 })\n .flatMap(([name, limit]) => typeof fields[name] === 'string' && (!['target', 'child_id'].includes(name) || encoder.encode(fields[name]).length <= limit) ? [[name, excerpt(text(fields[name]), limit)]] : []));\n}\n\nfunction historyRows(rootId: string, agentId: string, history?: DeepReadonly): RecordedRow[] {\n const rows: RecordedRow[] = [];\n const pending = new Map();\n for (const item of history?.messages ?? []) {\n const presentation = item.message?.presentation ?? item.presentation;\n const parts = presentation?.version === 1 ? presentation.parts ?? [] : [];\n const resultPart = parts.find(part => part.kind === 'result' && part.call_id);\n const message: typeof item.message = item.message ?? (resultPart ? { role: 'tool', tool_call_id: resultPart.call_id, name: resultPart.tool_name, content: '' } : undefined);\n const calls = message?.tool_calls ?? parts.filter(part => part.kind === 'tool' && part.call_id).map(part => ({ id: part.call_id!, function: { name: part.tool_name ?? '', arguments: '' } }));\n for (const [index, call] of calls.entries()) {\n if (call.function.name !== 'rlm_exec') continue;\n const part = parts.find(part => part.kind === 'tool' && part.call_id === call.id);\n const id = part ? key(rootId, agentId, history!.revision, 'part', part.id) : key(rootId, agentId, history!.revision, 'history', item.seq, index);\n const hosts: ExecutionHostCall[] = (part?.hosts ?? []).slice(-MAX_HOSTS).map(host => ({\n id: key(id, host.invocation_id), invocationId: host.invocation_id, name: excerpt(host.name, 1024),\n status: ['completed', 'failed', 'cancelled', 'interrupted'].includes(host.status) ? host.status as ExecutionHostCall['status'] : 'unknown',\n summary: excerpt(host.summary ?? '', 2048), duration: excerpt(host.duration ?? '', 128),\n ...(host.error ? { error: excerpt(host.error, 2048) } : {}), ...(host.display ? { display: operationDisplay(host.display) } : {}),\n }));\n pending.set(call.id, rows.length);\n rows.push({ kind: 'cell', id, callId: call.id, agentId,\n ...(part ? { partId: part.id, turnId: presentation!.turn_id } : {}),\n seq: item.seq, code: executionCode(call.function.arguments), output: '', status: 'unknown', hosts,\n ...(item.body ? { codeBody: reference(item.body), truncated: true } : {}),\n ...(call.function.arguments.length > FIELD_BYTES || presentation?.omitted || part?.omitted ? { truncated: true } : {}),\n });\n }\n if (!message) continue;\n if (message.role !== 'tool' || !message.tool_call_id) continue;\n let index = pending.get(message.tool_call_id);\n let prior = index === undefined ? undefined : rows[index];\n const unmatched = prior?.kind === 'cell' && prior.turnId && presentation?.turn_id && prior.turnId !== presentation.turn_id;\n if (unmatched) { index = undefined; prior = undefined; }\n if (prior?.kind !== 'cell' && message.name !== 'rlm_exec') continue;\n const decoded = result(typeof message.content === 'string' ? message.content : '');\n const { restart, hasResult: _hasResult, ...fields } = decoded;\n if (!item.message && resultPart && ['completed', 'failed', 'cancelled', 'interrupted'].includes(resultPart.status ?? '')) fields.status = resultPart.status as ExecutionCell['status'];\n const cell: RecordedCell = {\n kind: 'cell', id: key(rootId, agentId, history!.revision, 'history-result', item.seq), callId: message.tool_call_id,\n agentId, seq: item.seq, code: '', hosts: [], ...(prior?.kind === 'cell' ? prior : {}), ...fields, resultSeq: item.seq,\n ...(unmatched ? { historyUnmatched: true } : {}),\n ...(item.body ? { body: reference(item.body), truncated: true } : {}),\n };\n if (index !== undefined) rows[index] = cell;\n else rows.push(cell);\n if (!unmatched) pending.delete(message.tool_call_id);\n if (restart) rows.push({ kind: 'restart', id: `${cell.id}:restart`, agentId, seq: cell.seq, text: restart });\n }\n return rows;\n}\n\nexport function emptyExecutionEvidence(rootId: string, revision: string): ExecutionEvidence {\n return { rootId, revision, cursor: '0', rows: [], truncated: false };\n}\n\n/** Pure folding called only by the existing root stream; no transport or secondary cache. */\nexport function observeExecution(\n evidence: ExecutionEvidence, event: { seq: string; kind: string; payload: unknown },\n activeTurns: Readonly>, history: DeepReadonly>,\n observedAt?: number,\n): ExecutionEvidence {\n if (BigInt(event.seq) <= BigInt(evidence.cursor)) return evidence;\n const payload = record(event.payload) ?? {};\n const agentId = text(payload.agent_id) || evidence.rootId;\n const rows = [...evidence.rows];\n const cursor = event.seq;\n const afterSeq = history[agentId]?.throughSeq ?? 0;\n const turnId = text(payload.turn_id) || activeTurns[agentId];\n if (event.kind === 'scratch.restored') {\n rows.push({ kind: 'restart', id: key(evidence.rootId, agentId, evidence.revision, 'restart', event.seq), agentId,\n text: restartText(payload), eventSeq: event.seq, afterSeq, turnId, historyUnknown: !hasHistoryBoundary(history[agentId]) });\n } else if (/^(agent\\.)?turn\\./.test(event.kind)) {\n for (let index = 0; index < rows.length; index++) {\n const row = rows[index]!;\n if (row.kind !== 'cell' || !running(row) || row.agentId !== agentId) continue;\n if (event.kind.endsWith('.started') || !row.turnId || row.turnId === payload.turn_id) {\n const status = event.kind.endsWith('.cancelled') ? 'cancelled' : event.kind.endsWith('.interrupted') ? 'interrupted'\n : event.kind.endsWith('.failed') ? 'failed' : 'unknown';\n rows[index] = { ...row, status, hosts: settleHosts(row.hosts, status), closed: true, ...(observedAt === undefined ? {} : { observedEndedAt: observedAt }),\n ...(payload.error ? { error: excerpt(text(payload.error)) } : {}) };\n }\n }\n } else if (event.kind === 'stream.discard') {\n return { ...evidence, cursor, rows: rows.filter(row => !(row.kind === 'cell' && row.agentId === agentId && row.turnId === turnId && row.status === 'writing')) };\n } else if (event.kind.startsWith('stream.')) {\n const callId = text(payload.id);\n const partId = text(payload.part_id);\n if (!callId) return { ...evidence, cursor };\n let index = -1;\n const invocationId = event.kind.startsWith('stream.cell.host') ? text(payload.invocation_id) : '';\n if (invocationId) index = rows.findIndex(row => row.kind === 'cell' && row.agentId === agentId && row.callId === callId\n && (!turnId || !row.turnId || row.turnId === turnId) && (!partId || !row.partId || row.partId === partId) && row.hosts.some(host => host.id === key(row.id, invocationId)));\n const knownInvocation = index >= 0;\n // Once its start has been evicted, an invocation's owner is unknowable.\n // A provider can reuse the tool ID, even within the same turn.\n if (invocationId && event.kind === 'stream.cell.host' && index < 0) return { ...evidence, cursor, truncated: true };\n for (let candidate = rows.length - 1; candidate >= 0; candidate--) {\n if (index >= 0) break;\n const row = rows[candidate]!;\n if (row.kind === 'cell' && row.agentId === agentId && row.callId === callId\n && (!turnId || !row.turnId || row.turnId === turnId) && (!partId || !row.partId || row.partId === partId)) { index = candidate; break; }\n }\n let prior = rows[index];\n // A reconnect can lose the next cell's tool prefix. Reused provider IDs do\n // not prove that its new invocation belongs to our uncertain prior cell.\n if (event.kind === 'stream.cell.host.started' && invocationId && !knownInvocation\n && prior?.kind === 'cell' && prior.status === 'unknown') return { ...evidence, cursor, truncated: true };\n if ((event.kind === 'stream.tool.call' || event.kind === 'stream.tool.started') && prior?.kind === 'cell'\n && prior.closed) { index = -1; prior = undefined; }\n const hostStart = event.kind === 'stream.cell.host.started' && invocationId && turnId && turnId === activeTurns[agentId];\n if (!prior && !hostStart && (payload.name !== 'rlm_exec' || !['stream.tool.call', 'stream.tool.started', 'stream.tool.completed'].includes(event.kind))) return { ...evidence, cursor };\n if (payload.name && payload.name !== 'rlm_exec' && !event.kind.startsWith('stream.cell.host')) return { ...evidence, cursor };\n let cell: ObservedRow & ExecutionCell = prior?.kind === 'cell' ? { ...prior, ...(partId ? { partId } : {}) } : {\n kind: 'cell', id: key(evidence.rootId, agentId, evidence.revision, partId ? 'part' : 'event', partId || event.seq), callId, agentId, ...(partId ? { partId } : {}),\n eventSeq: event.seq, afterSeq, turnId, historyUnknown: !hasHistoryBoundary(history[agentId]), code: '', output: '', status: 'unknown', hosts: [],\n };\n switch (event.kind) {\n case 'stream.tool.call':\n case 'stream.tool.started': {\n const args = text(payload.args);\n cell = { ...cell, ...(args ? { code: executionCode(args) } : payload.truncated ? { code: '' } : {}), status: event.kind.endsWith('.started') ? 'running' : 'writing',\n ...(event.kind.endsWith('.started') && observedAt !== undefined && cell.observedStartedAt === undefined ? { observedStartedAt: observedAt } : {}),\n ...(args.length > FIELD_BYTES ? { truncated: true } : {}) };\n break;\n }\n case 'stream.tool.output': {\n const output = text(payload.text);\n const preview = excerpt(output, FIELD_BYTES, true);\n cell = { ...cell, output: preview, ...(cell.status === 'unknown' && !cell.closed && turnId ? { status: 'running' } : {}),\n ...(output !== preview ? { truncated: true } : {}) };\n break;\n }\n case 'stream.cell.host.started':\n case 'stream.cell.host': {\n const started = event.kind.endsWith('.started');\n const invocationId = text(payload.invocation_id);\n // Legacy completions have only an event occurrence; never join by name.\n const id = key(cell.id, invocationId || event.seq);\n const hosts = [...cell.hosts];\n const index = hosts.findIndex(host => host.id === id);\n if (started && (cell.closed || index >= 0)) break;\n const host: ExecutionHostCall = { id, ...(invocationId ? { invocationId } : {}), name: excerpt(text(payload.name), 1024),\n status: started ? 'running' : payload.host_status === 'cancelled' ? 'cancelled' : payload.result || payload.host_status === 'failed' ? 'failed' : payload.truncated ? 'unknown' : 'completed',\n ...(payload.display ? { display: operationDisplay(payload.display) } : hosts[index]?.display ? { display: hosts[index]!.display } : {}),\n summary: excerpt(text(payload.args), 2048), duration: started ? '' : excerpt(text(payload.text), 128),\n ...(payload.result ? { error: excerpt(text(payload.result), 2048) } : {}) };\n if (index >= 0) hosts[index] = host;\n else hosts.push(host);\n // Keep recent operations (especially the current invocation) at the cap.\n const overflow = hosts.length > MAX_HOSTS;\n cell = { ...cell, hosts: hosts.slice(-MAX_HOSTS), ...(!cell.closed && started ? { status: 'running' as const } : {}),\n ...(overflow || text(payload.name).length > 1024 || text(payload.args).length > 2048 || text(payload.result).length > 2048 ? { truncated: true } : {}) };\n break;\n }\n case 'stream.tool.completed': {\n const { restart, hasResult, ...fields } = result(text(payload.result));\n cell = { ...cell, ...fields, hosts: settleHosts(cell.hosts, fields.status), closed: true, output: hasResult ? fields.output : cell.output,\n ...(observedAt === undefined ? {} : { observedEndedAt: observedAt }) };\n if (restart && !rows.some(row => row.kind === 'restart' && row.agentId === agentId && row.turnId === turnId && row.text === restart && BigInt(row.eventSeq) >= BigInt(cell.eventSeq))) {\n rows.push({ kind: 'restart', id: `${cell.id}:restart`, agentId, text: restart, eventSeq: event.seq, afterSeq, turnId, historyUnknown: !hasHistoryBoundary(history[agentId]) });\n }\n break;\n }\n default: return { ...evidence, cursor };\n }\n if (payload.truncated) cell = { ...cell, truncated: true };\n if (event.kind.startsWith('stream.tool.')) cell = { ...cell, presentationSeqs: [...(cell.presentationSeqs ?? []), event.seq].slice(-128) };\n if (index < 0) rows.push(cell); else rows[index] = cell;\n }\n return boundExecutionEvidence({ ...evidence, cursor, rows });\n}\n\nexport function boundExecutionEvidence(evidence: ExecutionEvidence, maxBytes = MAX_BYTES): ExecutionEvidence {\n const rows = [...evidence.rows];\n let truncated = evidence.truncated;\n const over = () => rows.length > MAX_ENTRIES || size({ ...evidence, rows, truncated }) > maxBytes;\n while (rows.length && over()) {\n const completed = rows.findIndex(row => !running(row));\n rows.splice(completed < 0 ? 0 : completed, 1);\n truncated = true;\n }\n return { ...evidence, rows, truncated };\n}\n\nexport function settleExecutions(evidence: ExecutionEvidence, activeTurns?: Readonly>): ExecutionEvidence {\n return { ...evidence, rows: evidence.rows.map(row => row.kind === 'cell' && !row.closed && (!activeTurns || !activeTurns[row.agentId]\n || (row.turnId && row.turnId !== activeTurns[row.agentId])) ? { ...row, status: 'unknown', hosts: settleHosts(row.hosts, 'unknown'), ...(activeTurns ? { closed: true } : {}) } : row) };\n}\n\nfunction settleHosts(hosts: readonly ExecutionHostCall[], status: ExecutionCell['status']): readonly ExecutionHostCall[] {\n // A cell/turn outcome does not prove the host operation succeeded or failed.\n return hosts.map(host => host.status === 'running' ? { ...host,\n status: status === 'cancelled' || status === 'interrupted' ? status : 'unknown' } : host);\n}\n\n/** Reconcile loaded history without copying its bodies into the supplemental evidence. */\nexport function reconcileExecutions(evidence: ExecutionEvidence, history: DeepReadonly>): ExecutionEvidence {\n const recorded = new Map();\n for (const agentId of new Set(evidence.rows.map(row => row.agentId))) {\n recorded.set(agentId, historyRows(evidence.rootId, agentId, history[agentId]).filter((row): row is RecordedCell => row.kind === 'cell'));\n }\n const used = new Set();\n // Match occurrences in order. There is no turn ID in transcript messages,\n // so an agent's first history read cannot prove which already-observed call\n // it contains. Establish a boundary and join subsequent commits only.\n const rows = evidence.rows.map(row => {\n const exact = row.kind === 'cell' && row.partId ? recorded.get(row.agentId)?.find(cell => cell.partId === row.partId && cell.turnId === row.turnId) : undefined;\n if (exact) { used.add(exact.id); return { ...row, seq: exact.seq, recordedId: exact.id, recordedResultSeq: exact.resultSeq, historyUnknown: false, historyUnmatched: undefined }; }\n if (row.historyUnknown) {\n const page = history[row.agentId];\n return hasHistoryBoundary(page) ? { ...row, afterSeq: page!.throughSeq, historyUnknown: false,\n ...(row.kind === 'restart' || row.closed ? { historyUnmatched: true } : {}) } : row;\n }\n if (row.kind !== 'cell' || row.historyUnmatched) return row;\n const cells = recorded.get(row.agentId) ?? [];\n const match = cells.find(cell => !used.has(cell.id) && cell.callId === row.callId\n && (!row.partId || !cell.partId || row.partId === cell.partId) && (!row.turnId || !cell.turnId || row.turnId === cell.turnId) && (row.recordedId\n ? cell.id === row.recordedId || (row.recordedResultSeq !== undefined && cell.resultSeq === row.recordedResultSeq)\n : (cell.seq ?? 0) > row.afterSeq));\n if (!match) return row;\n used.add(match.id);\n return { ...row, seq: match.seq, recordedId: match.id, recordedResultSeq: match.resultSeq, historyUnmatched: undefined };\n });\n return boundExecutionEvidence({ ...evidence, rows });\n}\n\nexport function seedExecutions(previous: ExecutionEvidence | undefined, root: RootSnapshot, history: Record): ExecutionEvidence {\n let evidence = previous?.rootId === root.root_id && previous.revision === root.history_revision\n ? previous : emptyExecutionEvidence(root.root_id, root.history_revision);\n const events = [...(root.presentation ?? []), ...Object.entries(root.agent_presentations ?? {}).flatMap(([agentId, items]) =>\n (items ?? []).map(event => ({ ...event, payload: { ...record(event.payload), agent_id: agentId } })))]\n .sort((a, b) => BigInt(a.seq) < BigInt(b.seq) ? -1 : BigInt(a.seq) > BigInt(b.seq) ? 1 : 0);\n if (evidence.rows.some(row => row.kind === 'cell' && row.status === 'unknown' && !row.closed)) {\n let replay = emptyExecutionEvidence(root.root_id, root.history_revision);\n for (const event of events) replay = observeExecution(replay, event, root.active_turns, history);\n evidence = { ...evidence, rows: evidence.rows.map(row => {\n if (row.kind !== 'cell' || row.closed || row.status !== 'unknown' || !row.turnId || root.active_turns[row.agentId] !== row.turnId) return row;\n const confirmed = replay.rows.find((item): item is ObservedRow & ExecutionCell => item.kind === 'cell'\n && item.agentId === row.agentId && item.turnId === row.turnId && item.callId === row.callId\n && (item.eventSeq === row.eventSeq || item.hosts.some(host => host.invocationId && row.hosts.some(old => old.invocationId === host.invocationId))));\n if (!confirmed || confirmed.closed) return row;\n return { ...row, status: confirmed.status, observedStartedAt: undefined, hosts: row.hosts.map(host => {\n const current = confirmed.hosts.find(item => host.invocationId && item.invocationId === host.invocationId);\n return host.status === 'unknown' && current ? { ...current, id: host.id } : host;\n }) };\n }) };\n }\n for (const event of events) evidence = observeExecution(evidence, event, root.active_turns, history);\n return reconcileExecutions(settleExecutions({ ...evidence, cursor: root.cursor }, root.active_turns), history);\n}\n\n/** Immutable notebook rows derived from the loaded history and bounded observed evidence. */\nexport function executionRows(snapshot: DeepReadonly, agentId: string): readonly ExecutionRow[] {\n const root = snapshot.root;\n if (!root) return [];\n const engine = root.meta?.execution_engine ?? 'starlark';\n const language = engine === 'quickjs' ? 'javascript' : engine === 'starlark' ? 'starlark' : undefined;\n const history = historyRows(root.root_id, agentId, snapshot.history[agentId]);\n const evidence = snapshot.executions;\n const observed = evidence?.revision === root.history_revision && evidence.rootId === root.root_id ? evidence.rows.filter(row => row.agentId === agentId) : [];\n const recorded = new Map(observed.filter((row): row is ObservedRow & ExecutionCell => row.kind === 'cell' && row.recordedId !== undefined)\n .map(row => [row.recordedId, row]));\n const used = new Set();\n const rows: ExecutionRow[] = history.map(row => {\n if (row.kind === 'restart') {\n const live = observed.find(item => item.kind === 'restart' && !item.historyUnknown && !item.historyUnmatched && !used.has(item.id) && item.text === row.text\n && row.seq !== undefined && row.seq > item.afterSeq);\n if (!live) return row;\n used.add(live.id);\n return { ...row, id: live.id };\n }\n const live = recorded.get(row.id);\n if (!live) return row;\n used.add(live.id);\n return { ...live, ...row, id: live.id, code: row.code || live.code, hosts: row.hosts.length ? row.hosts : live.hosts,\n observedStartedAt: live.observedStartedAt, observedEndedAt: live.observedEndedAt,\n ...(row.status === 'unknown' ? { status: live.status, output: row.output || live.output, value: row.value ?? live.value, error: row.error ?? live.error, steps: row.steps ?? live.steps } : {}),\n ...(live.truncated || row.truncated ? { truncated: true } : {}) };\n });\n for (const row of observed) {\n if (used.has(row.id)) continue;\n const following = row.historyUnknown ? -1 : rows.findIndex(item => item.seq !== undefined && item.seq > (row.seq ?? row.afterSeq));\n rows.splice(following < 0 ? rows.length : following, 0, row);\n }\n return Object.freeze(rows.map(row => Object.freeze(row.kind === 'cell'\n ? { ...row, executionEngine: row.executionEngine ?? engine,\n language: row.language ?? language, hosts: Object.freeze(row.hosts.map(host => Object.freeze(host))),\n ...(row.presentationSeqs ? { presentationSeqs: Object.freeze([...row.presentationSeqs]) } : {}),\n ...(row.body ? { body: Object.freeze(row.body) } : {}), ...(row.codeBody ? { codeBody: Object.freeze(row.codeBody) } : {}) } : row)));\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/index.d.ts b/vendor/@whip/sdk/dist/index.d.ts new file mode 100644 index 0000000..b6157f4 --- /dev/null +++ b/vendor/@whip/sdk/dist/index.d.ts @@ -0,0 +1,23 @@ +export { WhipClient, createWhipClient } from './client.js'; +export type { ClientOptions, ConnectionSnapshot, ConnectionState, CallOptions, SdkEvent, QueryOutcome, ExecutorNotification, ExecutorNotifications, Notification, Notifications } from './client.js'; +export { BrowserProviders } from './browser.js'; +export type { BrowserProviderBridge, BrowserSelection, BrowserSelectionOptions } from './browser.js'; +export { Terminals, MAX_TERMINAL_WRITE_BYTES } from './terminals.js'; +export type { TerminalAttachment, TerminalOutput, TerminalExit } from './terminals.js'; +export { CommandHandle, isTerminal } from './command.js'; +export type { CommandOptions, CommandOutcome, CommandStatus, RecoveryRecord, RecoveryStorage } from './command.js'; +export { Session, Sessions } from './session.js'; +export { Turn } from './turn.js'; +export type { RunOptions, TurnEvent, TurnEventBase, TurnResult, TurnStatus, TurnFailure, TurnUsage, QuestionOption, QuestionSet, QuestionAnswer, QuestionEvent, PermissionEvent } from './turn.js'; +export { Subscription } from './subscription.js'; +export type { SubscriptionOptions } from './subscription.js'; +export { ContentReference } from './content.js'; +export type { ContentScope, ReadContentOptions, UploadOptions, InputAttachment } from './content.js'; +export { webSocket } from './transport.js'; +export type { Transport, TransportFactory, TransportHandlers } from './transport.js'; +export { WhipError, RpcError, DeliveryUncertainError } from './errors.js'; +export type { RpcMethods, RuntimeOperations, RpcMethod, RuntimeOperation, CommandOperation, QueryOperation, EphemeralOperation } from '@whip/protocol'; +export type { PermissionDecisionStatus } from './services.js'; +export { Agents, AgentSession } from './agents.js'; +export type { Definition, AgentDefinition, AgentOutput, ToolDefinition, ToolHandler, ToolContext, Executor, AgentRuntime, RuntimeSessionParams, ServeOptions, HookName, HookContext, HookHandler, HookOptions, HooksInput, HookHandlers, BeforeToolEvent, BeforeToolResult, BeforeSpawnEvent, BeforeSpawnResult, TurnStartEvent, TurnStartResult, SpawnRequest, ResolvedChild, ToolOptions, ToolReturn, } from './agents.js'; +export type { Schema, JsonSchema, StandardSchemaWithJSON, InferInput, InferOutput } from './schema.js'; diff --git a/vendor/@whip/sdk/dist/index.js b/vendor/@whip/sdk/dist/index.js new file mode 100644 index 0000000..3ee31f3 --- /dev/null +++ b/vendor/@whip/sdk/dist/index.js @@ -0,0 +1,12 @@ +export { WhipClient, createWhipClient } from './client.js'; +export { BrowserProviders } from './browser.js'; +export { Terminals, MAX_TERMINAL_WRITE_BYTES } from './terminals.js'; +export { CommandHandle, isTerminal } from './command.js'; +export { Session, Sessions } from './session.js'; +export { Turn } from './turn.js'; +export { Subscription } from './subscription.js'; +export { ContentReference } from './content.js'; +export { webSocket } from './transport.js'; +export { WhipError, RpcError, DeliveryUncertainError } from './errors.js'; +export { Agents, AgentSession } from './agents.js'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/index.js.map b/vendor/@whip/sdk/dist/index.js.map new file mode 100644 index 0000000..8d9027e --- /dev/null +++ b/vendor/@whip/sdk/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,SAAS,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAErE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAI1E,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export { WhipClient, createWhipClient } from './client.js';\nexport type { ClientOptions, ConnectionSnapshot, ConnectionState, CallOptions, SdkEvent, QueryOutcome, ExecutorNotification, ExecutorNotifications, Notification, Notifications } from './client.js';\nexport { BrowserProviders } from './browser.js';\nexport type { BrowserProviderBridge, BrowserSelection, BrowserSelectionOptions } from './browser.js';\nexport { Terminals, MAX_TERMINAL_WRITE_BYTES } from './terminals.js';\nexport type { TerminalAttachment, TerminalOutput, TerminalExit } from './terminals.js';\nexport { CommandHandle, isTerminal } from './command.js';\nexport type { CommandOptions, CommandOutcome, CommandStatus, RecoveryRecord, RecoveryStorage } from './command.js';\nexport { Session, Sessions } from './session.js';\nexport { Turn } from './turn.js';\nexport type { RunOptions, TurnEvent, TurnEventBase, TurnResult, TurnStatus, TurnFailure, TurnUsage, QuestionOption, QuestionSet, QuestionAnswer, QuestionEvent, PermissionEvent } from './turn.js';\nexport { Subscription } from './subscription.js';\nexport type { SubscriptionOptions } from './subscription.js';\nexport { ContentReference } from './content.js';\nexport type { ContentScope, ReadContentOptions, UploadOptions, InputAttachment } from './content.js';\nexport { webSocket } from './transport.js';\nexport type { Transport, TransportFactory, TransportHandlers } from './transport.js';\nexport { WhipError, RpcError, DeliveryUncertainError } from './errors.js';\nexport type { RpcMethods, RuntimeOperations, RpcMethod, RuntimeOperation, CommandOperation, QueryOperation, EphemeralOperation } from '@whip/protocol';\n\nexport type { PermissionDecisionStatus } from './services.js';\nexport { Agents, AgentSession } from './agents.js';\nexport type {\n Definition, AgentDefinition, AgentOutput, ToolDefinition, ToolHandler, ToolContext, Executor, AgentRuntime, RuntimeSessionParams, ServeOptions, HookName, HookContext, HookHandler, HookOptions, HooksInput, HookHandlers, BeforeToolEvent, BeforeToolResult, BeforeSpawnEvent, BeforeSpawnResult, TurnStartEvent, TurnStartResult, SpawnRequest, ResolvedChild, ToolOptions, ToolReturn,\n} from './agents.js';\nexport type { Schema, JsonSchema, StandardSchemaWithJSON, InferInput, InferOutput } from './schema.js';\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/node.d.ts b/vendor/@whip/sdk/dist/node.d.ts new file mode 100644 index 0000000..d563208 --- /dev/null +++ b/vendor/@whip/sdk/dist/node.d.ts @@ -0,0 +1,4 @@ +import type { TransportFactory } from './transport.js'; +export { createWhipClient, WhipClient } from './client.js'; +/** Attach to an existing daemon without enabling its network listener. */ +export declare function unixSocket(path: string, maxFrameBytes?: number): TransportFactory; diff --git a/vendor/@whip/sdk/dist/node.js b/vendor/@whip/sdk/dist/node.js new file mode 100644 index 0000000..ff11aa7 --- /dev/null +++ b/vendor/@whip/sdk/dist/node.js @@ -0,0 +1,73 @@ +import { connect } from 'node:net'; +import { WhipError, abortError } from './errors.js'; +export { createWhipClient, WhipClient } from './client.js'; +/** Attach to an existing daemon without enabling its network listener. */ +export function unixSocket(path, maxFrameBytes = 1 << 20) { + if (!path || !Number.isSafeInteger(maxFrameBytes) || maxFrameBytes < 1) + throw new TypeError('Invalid Unix transport options'); + return (handlers, signal) => new Promise((resolve, reject) => { + signal.throwIfAborted(); + const socket = connect(path); + let opened = false; + let closed = false; + let parts = []; + let size = 0; + const finish = (error) => { + if (closed) + return; + closed = true; + signal.removeEventListener('abort', abort); + parts = []; + size = 0; + socket.destroy(); + if (!opened) + reject(error); + else + handlers.close(error); + }; + const abort = () => finish(abortError(signal)); + signal.addEventListener('abort', abort, { once: true }); + socket.on('error', error => finish(error)); + socket.on('close', () => finish(new WhipError('disconnected', 'Unix socket closed'))); + socket.on('data', (bytes) => { + let start = 0; + while (start < bytes.length && !closed) { + const end = bytes.indexOf(10, start); + const part = bytes.subarray(start, end < 0 ? bytes.length : end); + size += part.length; + if (size > maxFrameBytes) { + finish(new WhipError('resource_limit', 'Unix message exceeds the frame limit')); + return; + } + parts.push(part); + if (end < 0) + break; + try { + const text = new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(parts, size)); + parts = []; + size = 0; + handlers.message(text); + } + catch { + finish(new WhipError('invalid_response', 'Invalid UTF-8 socket message')); + return; + } + start = end + 1; + } + }); + socket.once('connect', () => { + opened = true; + resolve({ + kind: 'unix', + get bufferedAmount() { return socket.writableLength; }, + send(message) { + if (closed) + throw new WhipError('disconnected', 'Unix socket is disconnected'); + socket.write(message + '\n'); + }, + close() { finish(new WhipError('disconnected', 'Unix socket closed')); }, + }); + }); + }); +} +//# sourceMappingURL=node.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/node.js.map b/vendor/@whip/sdk/dist/node.js.map new file mode 100644 index 0000000..ef3d381 --- /dev/null +++ b/vendor/@whip/sdk/dist/node.js.map @@ -0,0 +1 @@ +{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"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","sourcesContent":["import { connect } from 'node:net';\nimport { WhipError, abortError } from './errors.js';\nimport type { TransportFactory } from './transport.js';\nexport { createWhipClient, WhipClient } from './client.js';\n\n/** Attach to an existing daemon without enabling its network listener. */\nexport function unixSocket(path: string, maxFrameBytes = 1 << 20): TransportFactory {\n if (!path || !Number.isSafeInteger(maxFrameBytes) || maxFrameBytes < 1) throw new TypeError('Invalid Unix transport options');\n return (handlers, signal) => new Promise((resolve, reject) => {\n signal.throwIfAborted();\n const socket = connect(path);\n let opened = false;\n let closed = false;\n let parts: Buffer[] = [];\n let size = 0;\n const finish = (error: Error) => {\n if (closed) return;\n closed = true;\n signal.removeEventListener('abort', abort);\n parts = []; size = 0;\n socket.destroy();\n if (!opened) reject(error);\n else handlers.close(error);\n };\n const abort = () => finish(abortError(signal));\n signal.addEventListener('abort', abort, { once: true });\n socket.on('error', error => finish(error));\n socket.on('close', () => finish(new WhipError('disconnected', 'Unix socket closed')));\n socket.on('data', (bytes: Buffer) => {\n let start = 0;\n while (start < bytes.length && !closed) {\n const end = bytes.indexOf(10, start);\n const part = bytes.subarray(start, end < 0 ? bytes.length : end);\n size += part.length;\n if (size > maxFrameBytes) { finish(new WhipError('resource_limit', 'Unix message exceeds the frame limit')); return; }\n parts.push(part);\n if (end < 0) break;\n try {\n const text = new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(parts, size));\n parts = []; size = 0;\n handlers.message(text);\n } catch { finish(new WhipError('invalid_response', 'Invalid UTF-8 socket message')); return; }\n start = end + 1;\n }\n });\n socket.once('connect', () => {\n opened = true;\n resolve({\n kind: 'unix',\n get bufferedAmount() { return socket.writableLength; },\n send(message) {\n if (closed) throw new WhipError('disconnected', 'Unix socket is disconnected');\n socket.write(message + '\\n');\n },\n close() { finish(new WhipError('disconnected', 'Unix socket closed')); },\n });\n });\n });\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/react.d.ts b/vendor/@whip/sdk/dist/react.d.ts new file mode 100644 index 0000000..360e106 --- /dev/null +++ b/vendor/@whip/sdk/dist/react.d.ts @@ -0,0 +1,2385 @@ +import type { WhipClient } from './client.js'; +import type { SessionListView, SessionView } from './state.js'; +/** The application owns start/dispose, including across StrictMode remounts. */ +export declare function useSessionView(view: SessionView): { + readonly status: "error" | "closed" | "idle" | "loading" | "live" | "stale"; + readonly root?: { + readonly collection_revision?: string | undefined; + readonly active_turns: { + readonly [x: string]: string; + }; + readonly omitted?: { + readonly [x: string]: boolean; + } | undefined; + readonly message_seqs: readonly number[] | null; + readonly first_message_seq?: number | undefined; + readonly history_revision: string; + readonly root_id: string; + readonly cursor: string; + readonly meta: { + readonly execution_engine: string; + readonly definition: string; + readonly definition_revision: string; + readonly id: string; + readonly kind: string; + readonly title: string; + readonly model: string; + readonly provider: string; + readonly cwd: string; + readonly goal: string; + readonly forked_from: string; + readonly fork_seq: number; + readonly tags: readonly string[] | null; + readonly archived: boolean; + readonly pinned: boolean; + readonly effort: string; + readonly usage_in: number; + readonly usage_cached: number; + readonly usage_out: number; + readonly updated_at: string; + }; + readonly messages: readonly { + readonly presentation?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + readonly context_part_index: number; + readonly screenshot_part_index?: null | number | undefined; + } | null | undefined; + readonly version: number; + readonly turn_id?: string | undefined; + readonly parts?: readonly { + readonly id: string; + readonly kind: string; + readonly start?: number | undefined; + readonly end?: number | undefined; + readonly text?: string | undefined; + readonly tool_name?: string | undefined; + readonly status?: string | undefined; + readonly call_id?: string | undefined; + readonly hosts?: readonly { + readonly invocation_id: string; + readonly name: string; + readonly summary?: string | undefined; + readonly status: string; + readonly duration?: string | undefined; + readonly error?: string | undefined; + readonly display?: { + readonly target?: string | undefined; + readonly command?: string | undefined; + readonly query?: string | undefined; + readonly child_id?: string | undefined; + readonly label?: string | undefined; + } | null | undefined; + }[] | null | undefined; + readonly omitted?: number | undefined; + }[] | null | undefined; + readonly omitted?: number | undefined; + } | null | undefined; + readonly role: string; + readonly content: string | readonly { + readonly type: string; + readonly text?: string | undefined; + readonly image_url?: { + readonly url: string; + } | null | undefined; + readonly w?: number | undefined; + readonly h?: number | undefined; + }[]; + readonly tool_calls?: readonly { + readonly id: string; + readonly type: string; + readonly function: { + readonly name: string; + readonly arguments: string; + }; + readonly duration_ms?: number | undefined; + readonly exit_code?: number | undefined; + }[] | null | undefined; + readonly tool_call_id?: string | undefined; + readonly name?: string | undefined; + readonly authored?: boolean | undefined; + readonly sent_at?: null | string | undefined; + readonly usage?: { + readonly reported?: boolean | undefined; + readonly cost?: null | number | undefined; + readonly prompt_tokens: number; + readonly completion_tokens: number; + readonly prompt_tokens_details?: { + readonly cached_tokens: number; + } | null | undefined; + readonly completion_tokens_details?: { + readonly reasoning_tokens: number; + } | null | undefined; + } | null | undefined; + readonly model?: string | undefined; + readonly rewound_from?: string | undefined; + readonly call_id?: string | undefined; + }[] | null; + readonly presentation: readonly { + readonly seq: string; + readonly kind: string; + readonly payload: unknown; + }[] | null; + readonly agent_presentations: { + readonly [x: string]: readonly { + readonly seq: string; + readonly kind: string; + readonly payload: unknown; + }[] | null; + }; + readonly agents: readonly { + readonly execution_engine: string; + readonly last_turn?: { + readonly turn_id?: string | undefined; + readonly status: string; + readonly started_at?: string | undefined; + readonly finished_at?: string | undefined; + readonly event_seq: string; + readonly error?: string | undefined; + readonly error_truncated?: boolean | undefined; + readonly error_details?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + readonly model_calls?: number | undefined; + readonly compactions?: number | undefined; + readonly last_activity_at?: string | undefined; + } | null | undefined; + readonly id: string; + readonly root_id: string; + readonly parent_id: string; + readonly name: string; + readonly model: string; + readonly provider: string; + readonly effort: string; + readonly cwd: string; + readonly report: string; + readonly status: string; + readonly pending_mail: number; + readonly lifecycle_phase: string; + readonly blocking_reason: string; + readonly terminal_cause: string; + readonly allowed_controls: readonly string[] | null; + }[] | null; + readonly inbox: readonly { + readonly delivery_seq?: string | undefined; + readonly origin?: string | undefined; + readonly command_client_id?: string | undefined; + readonly command_id?: string | undefined; + readonly steer_turn_id?: string | undefined; + readonly preview?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + } | null | undefined; + readonly text: string; + readonly truncated?: boolean | undefined; + readonly attachments?: readonly { + readonly kind: string; + readonly name?: string | undefined; + readonly content: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] | null | undefined; + readonly attachment_count?: number | undefined; + } | null | undefined; + readonly root_id: string; + readonly agent_id: string; + readonly seq: string; + readonly kind: string; + readonly status: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] | null; + readonly blackboard: readonly { + readonly key: string; + readonly version: string; + readonly author_agent_id: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] | null; + readonly budgets: readonly { + readonly agent_id: string; + readonly state: { + readonly kind: string; + readonly limit: string | null; + readonly used: string; + readonly reserved: string; + readonly remaining: string | null; + readonly uncertain: string; + readonly incomplete: boolean; + }; + }[] | null; + readonly accounting?: { + readonly root_id: string; + readonly agent_id: string; + readonly scope: string; + readonly revision: string; + readonly reported_cost_micros: string; + readonly estimated_cost_micros: string; + readonly reported_cost_calls: string; + readonly estimated_cost_calls: string; + readonly unknown_cost_calls: string; + readonly reported_calls: string; + readonly estimated_calls: string; + readonly pending_calls: string; + } | undefined; + readonly capabilities: readonly { + readonly id: string; + readonly root_id: string; + readonly agent_id: string; + readonly issuer_agent_id: string; + readonly operations: readonly string[] | null; + readonly scopes: readonly string[] | null; + readonly file_scope?: string | undefined; + readonly file_issuer_id?: string | undefined; + readonly file_issuer_generation?: string | undefined; + readonly mcp: readonly { + readonly server: string; + readonly tool: string; + readonly definition: string; + }[] | null; + readonly mcp_all: boolean; + readonly browser?: { + readonly provider_id: string; + readonly provider_epoch: string; + readonly tab_id: string; + readonly tab_generation: string; + readonly profile_id: string; + readonly attachment_id?: string | undefined; + readonly attachment_generation?: string | undefined; + readonly rights: readonly string[] | null; + readonly preview?: { + readonly host_id: string; + readonly host_identity: string; + readonly connection_generation: string; + readonly environment_id: string; + readonly loopback: string; + readonly ports: readonly number[] | null; + } | null | undefined; + } | null | undefined; + readonly browser_issuer_id?: string | undefined; + readonly browser_issuer_generation?: number | undefined; + readonly browser_delegation_only?: boolean | undefined; + readonly generation: string; + readonly status: string; + readonly expires_at: string; + readonly created_at: string; + readonly updated_at: string; + }[] | null; + readonly schedules: readonly { + readonly id: number; + readonly schedule: string; + readonly prompt: string; + readonly anchor: string; + readonly last_fire: string; + }[] | null; + readonly upcoming_schedules?: readonly { + readonly id: number; + readonly next_fire: string; + readonly prompt: string; + readonly prompt_truncated?: boolean | undefined; + }[] | null | undefined; + readonly upcoming_schedule_count?: null | number | undefined; + readonly permissions: readonly { + readonly id: string; + readonly agent_id: string; + readonly operation_id: string; + readonly operation: string; + readonly canonical_path: string; + readonly request_digest: string; + readonly capability_id: string; + readonly capability_generation: string; + readonly status: string; + readonly command: string; + readonly rule: string; + }[] | null; + readonly questions: readonly { + readonly turn_id?: string | undefined; + readonly root_id?: string | undefined; + readonly agent_id?: string | undefined; + readonly sender_agent_id?: string | undefined; + readonly inbox_seq?: string | undefined; + readonly inbox_kind?: string | undefined; + readonly delivery?: string | undefined; + readonly message_id?: string | undefined; + readonly phase?: string | undefined; + readonly status?: string | undefined; + readonly terminal_cause?: string | undefined; + readonly command_client_id?: string | undefined; + readonly command_id?: string | undefined; + readonly operation_id?: string | undefined; + readonly trace_id?: string | undefined; + readonly schedule_id?: number | undefined; + readonly slot?: string | undefined; + readonly error?: string | undefined; + readonly model_call?: { + readonly id: string; + readonly logical_id: string; + readonly number: number; + readonly purpose: string; + readonly usage_source: string; + readonly cost_source: string; + readonly tokens: string; + readonly cost_micros: string; + readonly elapsed_millis: string; + readonly exhausted: boolean; + } | null | undefined; + readonly acknowledged_inbox?: readonly string[] | undefined; + readonly subscription_id?: string | undefined; + readonly key?: string | undefined; + readonly version?: string | undefined; + readonly expected_version?: string | undefined; + readonly restored?: readonly string[] | null | undefined; + readonly not_restored?: readonly { + readonly name: string; + readonly reason: string; + }[] | null | undefined; + readonly attempt?: string | undefined; + readonly budget_kind?: string | undefined; + readonly amount?: string | undefined; + readonly limit?: string | undefined; + readonly used?: string | undefined; + readonly reserved?: string | undefined; + readonly capability_id?: string | undefined; + readonly generation?: string | undefined; + readonly permission_id?: string | undefined; + readonly operation?: string | undefined; + readonly canonical_path?: string | undefined; + readonly request_digest?: string | undefined; + readonly command?: string | undefined; + readonly rule?: string | undefined; + readonly rule_source?: string | undefined; + readonly question_id?: string | undefined; + readonly question?: string | undefined; + readonly options?: readonly { + readonly label: string; + readonly description?: string | undefined; + readonly recommended?: boolean | undefined; + }[] | null | undefined; + readonly multiple?: boolean | undefined; + readonly questions?: readonly { + readonly question: string; + readonly options?: readonly { + readonly label: string; + readonly description?: string | undefined; + readonly recommended?: boolean | undefined; + }[] | null | undefined; + readonly multiple?: boolean | undefined; + }[] | null | undefined; + readonly answer?: readonly string[] | null | undefined; + readonly dismissed?: boolean | undefined; + readonly answers?: readonly { + readonly answer?: readonly string[] | null | undefined; + readonly dismissed?: boolean | undefined; + }[] | null | undefined; + }[] | null; + readonly permission_mode?: string | undefined; + } | undefined; + readonly history: { + readonly [x: string]: { + readonly revision: string; + readonly throughSeq: number; + readonly nextSeq: number; + readonly hasMore: boolean; + readonly loading: boolean; + readonly messages: readonly { + readonly presentation?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + readonly context_part_index: number; + readonly screenshot_part_index?: null | number | undefined; + } | null | undefined; + readonly version: number; + readonly turn_id?: string | undefined; + readonly parts?: readonly { + readonly id: string; + readonly kind: string; + readonly start?: number | undefined; + readonly end?: number | undefined; + readonly text?: string | undefined; + readonly tool_name?: string | undefined; + readonly status?: string | undefined; + readonly call_id?: string | undefined; + readonly hosts?: readonly { + readonly invocation_id: string; + readonly name: string; + readonly summary?: string | undefined; + readonly status: string; + readonly duration?: string | undefined; + readonly error?: string | undefined; + readonly display?: { + readonly target?: string | undefined; + readonly command?: string | undefined; + readonly query?: string | undefined; + readonly child_id?: string | undefined; + readonly label?: string | undefined; + } | null | undefined; + }[] | null | undefined; + readonly omitted?: number | undefined; + }[] | null | undefined; + readonly omitted?: number | undefined; + } | null | undefined; + readonly role?: string | undefined; + readonly authored?: boolean | undefined; + readonly sent_at?: null | string | undefined; + readonly seq: number; + readonly message?: { + readonly presentation?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + readonly context_part_index: number; + readonly screenshot_part_index?: null | number | undefined; + } | null | undefined; + readonly version: number; + readonly turn_id?: string | undefined; + readonly parts?: readonly { + readonly id: string; + readonly kind: string; + readonly start?: number | undefined; + readonly end?: number | undefined; + readonly text?: string | undefined; + readonly tool_name?: string | undefined; + readonly status?: string | undefined; + readonly call_id?: string | undefined; + readonly hosts?: readonly { + readonly invocation_id: string; + readonly name: string; + readonly summary?: string | undefined; + readonly status: string; + readonly duration?: string | undefined; + readonly error?: string | undefined; + readonly display?: { + readonly target?: string | undefined; + readonly command?: string | undefined; + readonly query?: string | undefined; + readonly child_id?: string | undefined; + readonly label?: string | undefined; + } | null | undefined; + }[] | null | undefined; + readonly omitted?: number | undefined; + }[] | null | undefined; + readonly omitted?: number | undefined; + } | null | undefined; + readonly role: string; + readonly content: string | readonly { + readonly type: string; + readonly text?: string | undefined; + readonly image_url?: { + readonly url: string; + } | null | undefined; + readonly w?: number | undefined; + readonly h?: number | undefined; + }[]; + readonly tool_calls?: readonly { + readonly id: string; + readonly type: string; + readonly function: { + readonly name: string; + readonly arguments: string; + }; + readonly duration_ms?: number | undefined; + readonly exit_code?: number | undefined; + }[] | null | undefined; + readonly tool_call_id?: string | undefined; + readonly name?: string | undefined; + readonly authored?: boolean | undefined; + readonly sent_at?: null | string | undefined; + readonly usage?: { + readonly reported?: boolean | undefined; + readonly cost?: null | number | undefined; + readonly prompt_tokens: number; + readonly completion_tokens: number; + readonly prompt_tokens_details?: { + readonly cached_tokens: number; + } | null | undefined; + readonly completion_tokens_details?: { + readonly reasoning_tokens: number; + } | null | undefined; + } | null | undefined; + readonly model?: string | undefined; + readonly rewound_from?: string | undefined; + readonly call_id?: string | undefined; + } | null | undefined; + readonly body?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + }[]; + readonly truncated: boolean; + readonly error?: { + readonly name: string; + readonly message: string; + readonly stack?: string | undefined; + readonly cause?: unknown; + } | undefined; + readonly gaps?: readonly { + readonly fromSeq: number; + readonly toSeq: number; + readonly status: "pending" | "loading" | "paused" | "error"; + readonly error?: string | undefined; + }[] | undefined; + readonly latestMissing?: boolean | undefined; + }; + }; + readonly collections: { + readonly [x: string]: { + readonly root_id: string; + readonly collection: string; + readonly revision: string; + readonly event_cursor: string; + readonly items: readonly ({ + readonly agent: { + readonly execution_engine: string; + readonly last_turn?: { + readonly turn_id?: string | undefined; + readonly status: string; + readonly started_at?: string | undefined; + readonly finished_at?: string | undefined; + readonly event_seq: string; + readonly error?: string | undefined; + readonly error_truncated?: boolean | undefined; + readonly error_details?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + readonly model_calls?: number | undefined; + readonly compactions?: number | undefined; + readonly last_activity_at?: string | undefined; + } | null | undefined; + readonly id: string; + readonly root_id: string; + readonly parent_id: string; + readonly name: string; + readonly model: string; + readonly provider: string; + readonly effort: string; + readonly cwd: string; + readonly report: string; + readonly status: string; + readonly pending_mail: number; + readonly lifecycle_phase: string; + readonly blocking_reason: string; + readonly terminal_cause: string; + readonly allowed_controls: readonly string[] | null; + } | null; + readonly inbox?: { + readonly delivery_seq?: string | undefined; + readonly origin?: string | undefined; + readonly command_client_id?: string | undefined; + readonly command_id?: string | undefined; + readonly steer_turn_id?: string | undefined; + readonly preview?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + } | null | undefined; + readonly text: string; + readonly truncated?: boolean | undefined; + readonly attachments?: readonly { + readonly kind: string; + readonly name?: string | undefined; + readonly content: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] | null | undefined; + readonly attachment_count?: number | undefined; + } | null | undefined; + readonly root_id: string; + readonly agent_id: string; + readonly seq: string; + readonly kind: string; + readonly status: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly blackboard?: { + readonly key: string; + readonly version: string; + readonly author_agent_id: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly budget?: { + readonly agent_id: string; + readonly state: { + readonly kind: string; + readonly limit: string | null; + readonly used: string; + readonly reserved: string; + readonly remaining: string | null; + readonly uncertain: string; + readonly incomplete: boolean; + }; + } | null | undefined; + readonly capability?: { + readonly id: string; + readonly root_id: string; + readonly agent_id: string; + readonly issuer_agent_id: string; + readonly operations: readonly string[] | null; + readonly scopes: readonly string[] | null; + readonly file_scope?: string | undefined; + readonly file_issuer_id?: string | undefined; + readonly file_issuer_generation?: string | undefined; + readonly mcp: readonly { + readonly server: string; + readonly tool: string; + readonly definition: string; + }[] | null; + readonly mcp_all: boolean; + readonly browser?: { + readonly provider_id: string; + readonly provider_epoch: string; + readonly tab_id: string; + readonly tab_generation: string; + readonly profile_id: string; + readonly attachment_id?: string | undefined; + readonly attachment_generation?: string | undefined; + readonly rights: readonly string[] | null; + readonly preview?: { + readonly host_id: string; + readonly host_identity: string; + readonly connection_generation: string; + readonly environment_id: string; + readonly loopback: string; + readonly ports: readonly number[] | null; + } | null | undefined; + } | null | undefined; + readonly browser_issuer_id?: string | undefined; + readonly browser_issuer_generation?: number | undefined; + readonly browser_delegation_only?: boolean | undefined; + readonly generation: string; + readonly status: string; + readonly expires_at: string; + readonly created_at: string; + readonly updated_at: string; + } | null | undefined; + readonly schedule?: { + readonly id: number; + readonly schedule: string; + readonly prompt: string; + readonly anchor: string; + readonly last_fire: string; + } | null | undefined; + readonly permission?: { + readonly id: string; + readonly agent_id: string; + readonly operation_id: string; + readonly operation: string; + readonly canonical_path: string; + readonly request_digest: string; + readonly capability_id: string; + readonly capability_generation: string; + readonly status: string; + readonly command: string; + readonly rule: string; + } | null | undefined; + readonly body?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + } | { + readonly agent?: { + readonly execution_engine: string; + readonly last_turn?: { + readonly turn_id?: string | undefined; + readonly status: string; + readonly started_at?: string | undefined; + readonly finished_at?: string | undefined; + readonly event_seq: string; + readonly error?: string | undefined; + readonly error_truncated?: boolean | undefined; + readonly error_details?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + readonly model_calls?: number | undefined; + readonly compactions?: number | undefined; + readonly last_activity_at?: string | undefined; + } | null | undefined; + readonly id: string; + readonly root_id: string; + readonly parent_id: string; + readonly name: string; + readonly model: string; + readonly provider: string; + readonly effort: string; + readonly cwd: string; + readonly report: string; + readonly status: string; + readonly pending_mail: number; + readonly lifecycle_phase: string; + readonly blocking_reason: string; + readonly terminal_cause: string; + readonly allowed_controls: readonly string[] | null; + } | null | undefined; + readonly inbox: { + readonly delivery_seq?: string | undefined; + readonly origin?: string | undefined; + readonly command_client_id?: string | undefined; + readonly command_id?: string | undefined; + readonly steer_turn_id?: string | undefined; + readonly preview?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: (readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] & readonly { + readonly label: string; + readonly selector?: string | undefined; + }[]) | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + } | null | undefined; + readonly text: string; + readonly truncated?: boolean | undefined; + readonly attachments?: (readonly { + readonly kind: string; + readonly name?: string | undefined; + readonly content: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] & readonly { + readonly kind: string; + readonly name?: string | undefined; + readonly content: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[]) | null | undefined; + readonly attachment_count?: number | undefined; + } | null | undefined; + readonly root_id: string; + readonly agent_id: string; + readonly seq: string; + readonly kind: string; + readonly status: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null; + readonly blackboard?: { + readonly key: string; + readonly version: string; + readonly author_agent_id: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly budget?: { + readonly agent_id: string; + readonly state: { + readonly kind: string; + readonly limit: string | null; + readonly used: string; + readonly reserved: string; + readonly remaining: string | null; + readonly uncertain: string; + readonly incomplete: boolean; + }; + } | null | undefined; + readonly capability?: { + readonly id: string; + readonly root_id: string; + readonly agent_id: string; + readonly issuer_agent_id: string; + readonly operations: readonly string[] | null; + readonly scopes: readonly string[] | null; + readonly file_scope?: string | undefined; + readonly file_issuer_id?: string | undefined; + readonly file_issuer_generation?: string | undefined; + readonly mcp: readonly { + readonly server: string; + readonly tool: string; + readonly definition: string; + }[] | null; + readonly mcp_all: boolean; + readonly browser?: { + readonly provider_id: string; + readonly provider_epoch: string; + readonly tab_id: string; + readonly tab_generation: string; + readonly profile_id: string; + readonly attachment_id?: string | undefined; + readonly attachment_generation?: string | undefined; + readonly rights: readonly string[] | null; + readonly preview?: { + readonly host_id: string; + readonly host_identity: string; + readonly connection_generation: string; + readonly environment_id: string; + readonly loopback: string; + readonly ports: readonly number[] | null; + } | null | undefined; + } | null | undefined; + readonly browser_issuer_id?: string | undefined; + readonly browser_issuer_generation?: number | undefined; + readonly browser_delegation_only?: boolean | undefined; + readonly generation: string; + readonly status: string; + readonly expires_at: string; + readonly created_at: string; + readonly updated_at: string; + } | null | undefined; + readonly schedule?: { + readonly id: number; + readonly schedule: string; + readonly prompt: string; + readonly anchor: string; + readonly last_fire: string; + } | null | undefined; + readonly permission?: { + readonly id: string; + readonly agent_id: string; + readonly operation_id: string; + readonly operation: string; + readonly canonical_path: string; + readonly request_digest: string; + readonly capability_id: string; + readonly capability_generation: string; + readonly status: string; + readonly command: string; + readonly rule: string; + } | null | undefined; + readonly body?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + } | { + readonly agent?: { + readonly execution_engine: string; + readonly last_turn?: { + readonly turn_id?: string | undefined; + readonly status: string; + readonly started_at?: string | undefined; + readonly finished_at?: string | undefined; + readonly event_seq: string; + readonly error?: string | undefined; + readonly error_truncated?: boolean | undefined; + readonly error_details?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + readonly model_calls?: number | undefined; + readonly compactions?: number | undefined; + readonly last_activity_at?: string | undefined; + } | null | undefined; + readonly id: string; + readonly root_id: string; + readonly parent_id: string; + readonly name: string; + readonly model: string; + readonly provider: string; + readonly effort: string; + readonly cwd: string; + readonly report: string; + readonly status: string; + readonly pending_mail: number; + readonly lifecycle_phase: string; + readonly blocking_reason: string; + readonly terminal_cause: string; + readonly allowed_controls: readonly string[] | null; + } | null | undefined; + readonly inbox?: { + readonly delivery_seq?: string | undefined; + readonly origin?: string | undefined; + readonly command_client_id?: string | undefined; + readonly command_id?: string | undefined; + readonly steer_turn_id?: string | undefined; + readonly preview?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + } | null | undefined; + readonly text: string; + readonly truncated?: boolean | undefined; + readonly attachments?: readonly { + readonly kind: string; + readonly name?: string | undefined; + readonly content: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] | null | undefined; + readonly attachment_count?: number | undefined; + } | null | undefined; + readonly root_id: string; + readonly agent_id: string; + readonly seq: string; + readonly kind: string; + readonly status: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly blackboard: { + readonly key: string; + readonly version: string; + readonly author_agent_id: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null; + readonly budget?: { + readonly agent_id: string; + readonly state: { + readonly kind: string; + readonly limit: string | null; + readonly used: string; + readonly reserved: string; + readonly remaining: string | null; + readonly uncertain: string; + readonly incomplete: boolean; + }; + } | null | undefined; + readonly capability?: { + readonly id: string; + readonly root_id: string; + readonly agent_id: string; + readonly issuer_agent_id: string; + readonly operations: readonly string[] | null; + readonly scopes: readonly string[] | null; + readonly file_scope?: string | undefined; + readonly file_issuer_id?: string | undefined; + readonly file_issuer_generation?: string | undefined; + readonly mcp: readonly { + readonly server: string; + readonly tool: string; + readonly definition: string; + }[] | null; + readonly mcp_all: boolean; + readonly browser?: { + readonly provider_id: string; + readonly provider_epoch: string; + readonly tab_id: string; + readonly tab_generation: string; + readonly profile_id: string; + readonly attachment_id?: string | undefined; + readonly attachment_generation?: string | undefined; + readonly rights: readonly string[] | null; + readonly preview?: { + readonly host_id: string; + readonly host_identity: string; + readonly connection_generation: string; + readonly environment_id: string; + readonly loopback: string; + readonly ports: readonly number[] | null; + } | null | undefined; + } | null | undefined; + readonly browser_issuer_id?: string | undefined; + readonly browser_issuer_generation?: number | undefined; + readonly browser_delegation_only?: boolean | undefined; + readonly generation: string; + readonly status: string; + readonly expires_at: string; + readonly created_at: string; + readonly updated_at: string; + } | null | undefined; + readonly schedule?: { + readonly id: number; + readonly schedule: string; + readonly prompt: string; + readonly anchor: string; + readonly last_fire: string; + } | null | undefined; + readonly permission?: { + readonly id: string; + readonly agent_id: string; + readonly operation_id: string; + readonly operation: string; + readonly canonical_path: string; + readonly request_digest: string; + readonly capability_id: string; + readonly capability_generation: string; + readonly status: string; + readonly command: string; + readonly rule: string; + } | null | undefined; + readonly body?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + } | { + readonly agent?: { + readonly execution_engine: string; + readonly last_turn?: { + readonly turn_id?: string | undefined; + readonly status: string; + readonly started_at?: string | undefined; + readonly finished_at?: string | undefined; + readonly event_seq: string; + readonly error?: string | undefined; + readonly error_truncated?: boolean | undefined; + readonly error_details?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + readonly model_calls?: number | undefined; + readonly compactions?: number | undefined; + readonly last_activity_at?: string | undefined; + } | null | undefined; + readonly id: string; + readonly root_id: string; + readonly parent_id: string; + readonly name: string; + readonly model: string; + readonly provider: string; + readonly effort: string; + readonly cwd: string; + readonly report: string; + readonly status: string; + readonly pending_mail: number; + readonly lifecycle_phase: string; + readonly blocking_reason: string; + readonly terminal_cause: string; + readonly allowed_controls: readonly string[] | null; + } | null | undefined; + readonly inbox?: { + readonly delivery_seq?: string | undefined; + readonly origin?: string | undefined; + readonly command_client_id?: string | undefined; + readonly command_id?: string | undefined; + readonly steer_turn_id?: string | undefined; + readonly preview?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + } | null | undefined; + readonly text: string; + readonly truncated?: boolean | undefined; + readonly attachments?: readonly { + readonly kind: string; + readonly name?: string | undefined; + readonly content: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] | null | undefined; + readonly attachment_count?: number | undefined; + } | null | undefined; + readonly root_id: string; + readonly agent_id: string; + readonly seq: string; + readonly kind: string; + readonly status: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly blackboard?: { + readonly key: string; + readonly version: string; + readonly author_agent_id: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly budget: { + readonly agent_id: string; + readonly state: { + readonly kind: string; + readonly limit: string | null; + readonly used: string; + readonly reserved: string; + readonly remaining: string | null; + readonly uncertain: string; + readonly incomplete: boolean; + }; + } | null; + readonly capability?: { + readonly id: string; + readonly root_id: string; + readonly agent_id: string; + readonly issuer_agent_id: string; + readonly operations: readonly string[] | null; + readonly scopes: readonly string[] | null; + readonly file_scope?: string | undefined; + readonly file_issuer_id?: string | undefined; + readonly file_issuer_generation?: string | undefined; + readonly mcp: readonly { + readonly server: string; + readonly tool: string; + readonly definition: string; + }[] | null; + readonly mcp_all: boolean; + readonly browser?: { + readonly provider_id: string; + readonly provider_epoch: string; + readonly tab_id: string; + readonly tab_generation: string; + readonly profile_id: string; + readonly attachment_id?: string | undefined; + readonly attachment_generation?: string | undefined; + readonly rights: readonly string[] | null; + readonly preview?: { + readonly host_id: string; + readonly host_identity: string; + readonly connection_generation: string; + readonly environment_id: string; + readonly loopback: string; + readonly ports: readonly number[] | null; + } | null | undefined; + } | null | undefined; + readonly browser_issuer_id?: string | undefined; + readonly browser_issuer_generation?: number | undefined; + readonly browser_delegation_only?: boolean | undefined; + readonly generation: string; + readonly status: string; + readonly expires_at: string; + readonly created_at: string; + readonly updated_at: string; + } | null | undefined; + readonly schedule?: { + readonly id: number; + readonly schedule: string; + readonly prompt: string; + readonly anchor: string; + readonly last_fire: string; + } | null | undefined; + readonly permission?: { + readonly id: string; + readonly agent_id: string; + readonly operation_id: string; + readonly operation: string; + readonly canonical_path: string; + readonly request_digest: string; + readonly capability_id: string; + readonly capability_generation: string; + readonly status: string; + readonly command: string; + readonly rule: string; + } | null | undefined; + readonly body?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + } | { + readonly agent?: { + readonly execution_engine: string; + readonly last_turn?: { + readonly turn_id?: string | undefined; + readonly status: string; + readonly started_at?: string | undefined; + readonly finished_at?: string | undefined; + readonly event_seq: string; + readonly error?: string | undefined; + readonly error_truncated?: boolean | undefined; + readonly error_details?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + readonly model_calls?: number | undefined; + readonly compactions?: number | undefined; + readonly last_activity_at?: string | undefined; + } | null | undefined; + readonly id: string; + readonly root_id: string; + readonly parent_id: string; + readonly name: string; + readonly model: string; + readonly provider: string; + readonly effort: string; + readonly cwd: string; + readonly report: string; + readonly status: string; + readonly pending_mail: number; + readonly lifecycle_phase: string; + readonly blocking_reason: string; + readonly terminal_cause: string; + readonly allowed_controls: readonly string[] | null; + } | null | undefined; + readonly inbox?: { + readonly delivery_seq?: string | undefined; + readonly origin?: string | undefined; + readonly command_client_id?: string | undefined; + readonly command_id?: string | undefined; + readonly steer_turn_id?: string | undefined; + readonly preview?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + } | null | undefined; + readonly text: string; + readonly truncated?: boolean | undefined; + readonly attachments?: readonly { + readonly kind: string; + readonly name?: string | undefined; + readonly content: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] | null | undefined; + readonly attachment_count?: number | undefined; + } | null | undefined; + readonly root_id: string; + readonly agent_id: string; + readonly seq: string; + readonly kind: string; + readonly status: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly blackboard?: { + readonly key: string; + readonly version: string; + readonly author_agent_id: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly budget?: { + readonly agent_id: string; + readonly state: { + readonly kind: string; + readonly limit: string | null; + readonly used: string; + readonly reserved: string; + readonly remaining: string | null; + readonly uncertain: string; + readonly incomplete: boolean; + }; + } | null | undefined; + readonly capability: { + readonly id: string; + readonly root_id: string; + readonly agent_id: string; + readonly issuer_agent_id: string; + readonly operations: readonly string[] | null; + readonly scopes: readonly string[] | null; + readonly file_scope?: string | undefined; + readonly file_issuer_id?: string | undefined; + readonly file_issuer_generation?: string | undefined; + readonly mcp: (readonly { + readonly server: string; + readonly tool: string; + readonly definition: string; + }[] & readonly { + readonly server: string; + readonly tool: string; + readonly definition: string; + }[]) | null; + readonly mcp_all: boolean; + readonly browser?: { + readonly provider_id: string; + readonly provider_epoch: string; + readonly tab_id: string; + readonly tab_generation: string; + readonly profile_id: string; + readonly attachment_id?: string | undefined; + readonly attachment_generation?: string | undefined; + readonly rights: readonly string[] | null; + readonly preview?: { + readonly host_id: string; + readonly host_identity: string; + readonly connection_generation: string; + readonly environment_id: string; + readonly loopback: string; + readonly ports: readonly number[] | null; + } | null | undefined; + } | null | undefined; + readonly browser_issuer_id?: string | undefined; + readonly browser_issuer_generation?: number | undefined; + readonly browser_delegation_only?: boolean | undefined; + readonly generation: string; + readonly status: string; + readonly expires_at: string; + readonly created_at: string; + readonly updated_at: string; + } | null; + readonly schedule?: { + readonly id: number; + readonly schedule: string; + readonly prompt: string; + readonly anchor: string; + readonly last_fire: string; + } | null | undefined; + readonly permission?: { + readonly id: string; + readonly agent_id: string; + readonly operation_id: string; + readonly operation: string; + readonly canonical_path: string; + readonly request_digest: string; + readonly capability_id: string; + readonly capability_generation: string; + readonly status: string; + readonly command: string; + readonly rule: string; + } | null | undefined; + readonly body?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + } | { + readonly agent?: { + readonly execution_engine: string; + readonly last_turn?: { + readonly turn_id?: string | undefined; + readonly status: string; + readonly started_at?: string | undefined; + readonly finished_at?: string | undefined; + readonly event_seq: string; + readonly error?: string | undefined; + readonly error_truncated?: boolean | undefined; + readonly error_details?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + readonly model_calls?: number | undefined; + readonly compactions?: number | undefined; + readonly last_activity_at?: string | undefined; + } | null | undefined; + readonly id: string; + readonly root_id: string; + readonly parent_id: string; + readonly name: string; + readonly model: string; + readonly provider: string; + readonly effort: string; + readonly cwd: string; + readonly report: string; + readonly status: string; + readonly pending_mail: number; + readonly lifecycle_phase: string; + readonly blocking_reason: string; + readonly terminal_cause: string; + readonly allowed_controls: readonly string[] | null; + } | null | undefined; + readonly inbox?: { + readonly delivery_seq?: string | undefined; + readonly origin?: string | undefined; + readonly command_client_id?: string | undefined; + readonly command_id?: string | undefined; + readonly steer_turn_id?: string | undefined; + readonly preview?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + } | null | undefined; + readonly text: string; + readonly truncated?: boolean | undefined; + readonly attachments?: readonly { + readonly kind: string; + readonly name?: string | undefined; + readonly content: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] | null | undefined; + readonly attachment_count?: number | undefined; + } | null | undefined; + readonly root_id: string; + readonly agent_id: string; + readonly seq: string; + readonly kind: string; + readonly status: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly blackboard?: { + readonly key: string; + readonly version: string; + readonly author_agent_id: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly budget?: { + readonly agent_id: string; + readonly state: { + readonly kind: string; + readonly limit: string | null; + readonly used: string; + readonly reserved: string; + readonly remaining: string | null; + readonly uncertain: string; + readonly incomplete: boolean; + }; + } | null | undefined; + readonly capability?: { + readonly id: string; + readonly root_id: string; + readonly agent_id: string; + readonly issuer_agent_id: string; + readonly operations: readonly string[] | null; + readonly scopes: readonly string[] | null; + readonly file_scope?: string | undefined; + readonly file_issuer_id?: string | undefined; + readonly file_issuer_generation?: string | undefined; + readonly mcp: readonly { + readonly server: string; + readonly tool: string; + readonly definition: string; + }[] | null; + readonly mcp_all: boolean; + readonly browser?: { + readonly provider_id: string; + readonly provider_epoch: string; + readonly tab_id: string; + readonly tab_generation: string; + readonly profile_id: string; + readonly attachment_id?: string | undefined; + readonly attachment_generation?: string | undefined; + readonly rights: readonly string[] | null; + readonly preview?: { + readonly host_id: string; + readonly host_identity: string; + readonly connection_generation: string; + readonly environment_id: string; + readonly loopback: string; + readonly ports: readonly number[] | null; + } | null | undefined; + } | null | undefined; + readonly browser_issuer_id?: string | undefined; + readonly browser_issuer_generation?: number | undefined; + readonly browser_delegation_only?: boolean | undefined; + readonly generation: string; + readonly status: string; + readonly expires_at: string; + readonly created_at: string; + readonly updated_at: string; + } | null | undefined; + readonly schedule: { + readonly id: number; + readonly schedule: string; + readonly prompt: string; + readonly anchor: string; + readonly last_fire: string; + } | null; + readonly permission?: { + readonly id: string; + readonly agent_id: string; + readonly operation_id: string; + readonly operation: string; + readonly canonical_path: string; + readonly request_digest: string; + readonly capability_id: string; + readonly capability_generation: string; + readonly status: string; + readonly command: string; + readonly rule: string; + } | null | undefined; + readonly body?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + } | { + readonly agent?: { + readonly execution_engine: string; + readonly last_turn?: { + readonly turn_id?: string | undefined; + readonly status: string; + readonly started_at?: string | undefined; + readonly finished_at?: string | undefined; + readonly event_seq: string; + readonly error?: string | undefined; + readonly error_truncated?: boolean | undefined; + readonly error_details?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + readonly model_calls?: number | undefined; + readonly compactions?: number | undefined; + readonly last_activity_at?: string | undefined; + } | null | undefined; + readonly id: string; + readonly root_id: string; + readonly parent_id: string; + readonly name: string; + readonly model: string; + readonly provider: string; + readonly effort: string; + readonly cwd: string; + readonly report: string; + readonly status: string; + readonly pending_mail: number; + readonly lifecycle_phase: string; + readonly blocking_reason: string; + readonly terminal_cause: string; + readonly allowed_controls: readonly string[] | null; + } | null | undefined; + readonly inbox?: { + readonly delivery_seq?: string | undefined; + readonly origin?: string | undefined; + readonly command_client_id?: string | undefined; + readonly command_id?: string | undefined; + readonly steer_turn_id?: string | undefined; + readonly preview?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + } | null | undefined; + readonly text: string; + readonly truncated?: boolean | undefined; + readonly attachments?: readonly { + readonly kind: string; + readonly name?: string | undefined; + readonly content: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] | null | undefined; + readonly attachment_count?: number | undefined; + } | null | undefined; + readonly root_id: string; + readonly agent_id: string; + readonly seq: string; + readonly kind: string; + readonly status: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly blackboard?: { + readonly key: string; + readonly version: string; + readonly author_agent_id: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly budget?: { + readonly agent_id: string; + readonly state: { + readonly kind: string; + readonly limit: string | null; + readonly used: string; + readonly reserved: string; + readonly remaining: string | null; + readonly uncertain: string; + readonly incomplete: boolean; + }; + } | null | undefined; + readonly capability?: { + readonly id: string; + readonly root_id: string; + readonly agent_id: string; + readonly issuer_agent_id: string; + readonly operations: readonly string[] | null; + readonly scopes: readonly string[] | null; + readonly file_scope?: string | undefined; + readonly file_issuer_id?: string | undefined; + readonly file_issuer_generation?: string | undefined; + readonly mcp: readonly { + readonly server: string; + readonly tool: string; + readonly definition: string; + }[] | null; + readonly mcp_all: boolean; + readonly browser?: { + readonly provider_id: string; + readonly provider_epoch: string; + readonly tab_id: string; + readonly tab_generation: string; + readonly profile_id: string; + readonly attachment_id?: string | undefined; + readonly attachment_generation?: string | undefined; + readonly rights: readonly string[] | null; + readonly preview?: { + readonly host_id: string; + readonly host_identity: string; + readonly connection_generation: string; + readonly environment_id: string; + readonly loopback: string; + readonly ports: readonly number[] | null; + } | null | undefined; + } | null | undefined; + readonly browser_issuer_id?: string | undefined; + readonly browser_issuer_generation?: number | undefined; + readonly browser_delegation_only?: boolean | undefined; + readonly generation: string; + readonly status: string; + readonly expires_at: string; + readonly created_at: string; + readonly updated_at: string; + } | null | undefined; + readonly schedule?: { + readonly id: number; + readonly schedule: string; + readonly prompt: string; + readonly anchor: string; + readonly last_fire: string; + } | null | undefined; + readonly permission: { + readonly id: string; + readonly agent_id: string; + readonly operation_id: string; + readonly operation: string; + readonly canonical_path: string; + readonly request_digest: string; + readonly capability_id: string; + readonly capability_generation: string; + readonly status: string; + readonly command: string; + readonly rule: string; + } | null; + readonly body?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + } | { + readonly agent?: { + readonly execution_engine: string; + readonly last_turn?: { + readonly turn_id?: string | undefined; + readonly status: string; + readonly started_at?: string | undefined; + readonly finished_at?: string | undefined; + readonly event_seq: string; + readonly error?: string | undefined; + readonly error_truncated?: boolean | undefined; + readonly error_details?: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null | undefined; + readonly model_calls?: number | undefined; + readonly compactions?: number | undefined; + readonly last_activity_at?: string | undefined; + } | null | undefined; + readonly id: string; + readonly root_id: string; + readonly parent_id: string; + readonly name: string; + readonly model: string; + readonly provider: string; + readonly effort: string; + readonly cwd: string; + readonly report: string; + readonly status: string; + readonly pending_mail: number; + readonly lifecycle_phase: string; + readonly blocking_reason: string; + readonly terminal_cause: string; + readonly allowed_controls: readonly string[] | null; + } | null | undefined; + readonly inbox?: { + readonly delivery_seq?: string | undefined; + readonly origin?: string | undefined; + readonly command_client_id?: string | undefined; + readonly command_id?: string | undefined; + readonly steer_turn_id?: string | undefined; + readonly preview?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + } | null | undefined; + readonly text: string; + readonly truncated?: boolean | undefined; + readonly attachments?: readonly { + readonly kind: string; + readonly name?: string | undefined; + readonly content: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] | null | undefined; + readonly attachment_count?: number | undefined; + } | null | undefined; + readonly root_id: string; + readonly agent_id: string; + readonly seq: string; + readonly kind: string; + readonly status: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly blackboard?: { + readonly key: string; + readonly version: string; + readonly author_agent_id: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + } | null | undefined; + readonly budget?: { + readonly agent_id: string; + readonly state: { + readonly kind: string; + readonly limit: string | null; + readonly used: string; + readonly reserved: string; + readonly remaining: string | null; + readonly uncertain: string; + readonly incomplete: boolean; + }; + } | null | undefined; + readonly capability?: { + readonly id: string; + readonly root_id: string; + readonly agent_id: string; + readonly issuer_agent_id: string; + readonly operations: readonly string[] | null; + readonly scopes: readonly string[] | null; + readonly file_scope?: string | undefined; + readonly file_issuer_id?: string | undefined; + readonly file_issuer_generation?: string | undefined; + readonly mcp: readonly { + readonly server: string; + readonly tool: string; + readonly definition: string; + }[] | null; + readonly mcp_all: boolean; + readonly browser?: { + readonly provider_id: string; + readonly provider_epoch: string; + readonly tab_id: string; + readonly tab_generation: string; + readonly profile_id: string; + readonly attachment_id?: string | undefined; + readonly attachment_generation?: string | undefined; + readonly rights: readonly string[] | null; + readonly preview?: { + readonly host_id: string; + readonly host_identity: string; + readonly connection_generation: string; + readonly environment_id: string; + readonly loopback: string; + readonly ports: readonly number[] | null; + } | null | undefined; + } | null | undefined; + readonly browser_issuer_id?: string | undefined; + readonly browser_issuer_generation?: number | undefined; + readonly browser_delegation_only?: boolean | undefined; + readonly generation: string; + readonly status: string; + readonly expires_at: string; + readonly created_at: string; + readonly updated_at: string; + } | null | undefined; + readonly schedule?: { + readonly id: number; + readonly schedule: string; + readonly prompt: string; + readonly anchor: string; + readonly last_fire: string; + } | null | undefined; + readonly permission?: { + readonly id: string; + readonly agent_id: string; + readonly operation_id: string; + readonly operation: string; + readonly canonical_path: string; + readonly request_digest: string; + readonly capability_id: string; + readonly capability_generation: string; + readonly status: string; + readonly command: string; + readonly rule: string; + } | null | undefined; + readonly body: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + } | null; + })[] | null; + readonly next_cursor?: { + readonly root_id: string; + readonly collection: string; + readonly revision: string; + readonly offset: string; + } | null | undefined; + readonly has_more: boolean; + }; + }; + readonly unverifiedInbox?: readonly { + readonly delivery_seq?: string | undefined; + readonly origin?: string | undefined; + readonly command_client_id?: string | undefined; + readonly command_id?: string | undefined; + readonly steer_turn_id?: string | undefined; + readonly preview?: { + readonly design_context?: { + readonly context_attachment_id: string; + readonly screenshot_attachment_id?: string | undefined; + readonly elements: readonly { + readonly label: string; + readonly selector?: string | undefined; + }[] | null; + readonly element_count: number; + readonly page_url?: string | undefined; + readonly page_title?: string | undefined; + } | null | undefined; + readonly text: string; + readonly truncated?: boolean | undefined; + readonly attachments?: readonly { + readonly kind: string; + readonly name?: string | undefined; + readonly content: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] | null | undefined; + readonly attachment_count?: number | undefined; + } | null | undefined; + readonly root_id: string; + readonly agent_id: string; + readonly seq: string; + readonly kind: string; + readonly status: string; + readonly payload: { + readonly inline?: unknown; + readonly text?: null | string | undefined; + readonly binary?: string | null | undefined; + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type: string; + readonly source: string; + }; + }[] | undefined; + readonly retainedBytes: number; + readonly executions?: { + readonly rootId: string; + readonly revision: string; + readonly cursor: string; + readonly rows: readonly ({ + readonly partId?: string | undefined; + readonly kind: "cell"; + readonly id: string; + readonly callId: string; + readonly agentId: string; + readonly turnId?: string | undefined; + readonly eventSeq: string; + readonly presentationSeqs?: readonly string[] | undefined; + readonly seq?: number | undefined; + readonly code: string; + readonly output: string; + readonly value?: string | undefined; + readonly hasValue?: boolean | undefined; + readonly executionEngine?: string | undefined; + readonly language?: string | undefined; + readonly error?: string | undefined; + readonly scratch?: string | undefined; + readonly status: "writing" | "running" | "completed" | "failed" | "interrupted" | "cancelled" | "unknown"; + readonly hosts: readonly { + readonly display?: { + readonly target?: string | undefined; + readonly command?: string | undefined; + readonly query?: string | undefined; + readonly child_id?: string | undefined; + readonly label?: string | undefined; + } | undefined; + readonly id: string; + readonly invocationId?: string | undefined; + readonly status: "running" | "completed" | "failed" | "cancelled" | "interrupted" | "unknown"; + readonly name: string; + readonly summary: string; + readonly duration: string; + readonly error?: string | undefined; + }[]; + readonly steps?: number | undefined; + readonly quickjsJobs?: number | undefined; + readonly observedStartedAt?: number | undefined; + readonly observedEndedAt?: number | undefined; + readonly truncated?: boolean | undefined; + readonly historyUnmatched?: boolean | undefined; + readonly body?: { + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type?: string | undefined; + readonly source?: string | undefined; + } | undefined; + readonly codeBody?: { + readonly reference_id: string; + readonly digest: string; + readonly size: string; + readonly media_type?: string | undefined; + readonly source?: string | undefined; + } | undefined; + readonly afterSeq: number; + readonly closed?: boolean | undefined; + readonly recordedId?: string | undefined; + readonly recordedResultSeq?: number | undefined; + readonly historyUnknown?: boolean | undefined; + } | { + readonly kind: "restart"; + readonly historyUnmatched?: boolean | undefined; + readonly id: string; + readonly agentId: string; + readonly text: string; + readonly seq?: number | undefined; + readonly eventSeq: string; + readonly turnId?: string | undefined; + readonly afterSeq: number; + readonly closed?: boolean | undefined; + readonly recordedId?: string | undefined; + readonly recordedResultSeq?: number | undefined; + readonly historyUnknown?: boolean | undefined; + })[]; + readonly truncated: boolean; + } | undefined; + readonly trace?: { + readonly rootId: string; + readonly pageCursor: string; + readonly spans: { + readonly [x: string]: { + readonly id: string; + readonly traceId: string; + readonly parentId: string; + readonly rootId: string; + readonly agentId: string; + readonly turnId: string; + readonly kind: import("./trace.js").TraceSpanKind; + readonly name: string; + readonly status: import("./trace.js").TraceSpanStatus; + readonly startMs: number; + readonly endMs: number; + readonly attrs: { + readonly [x: string]: unknown; + }; + readonly links: readonly { + readonly traceId: string; + readonly spanId: string; + }[]; + readonly updatedSeq: string; + }; + }; + readonly loaded: boolean; + readonly loading: boolean; + readonly hasMore: boolean; + readonly truncated: boolean; + readonly clockOffsetMs: number; + readonly error?: { + readonly name: string; + readonly message: string; + readonly stack?: string | undefined; + readonly cause?: unknown; + } | undefined; + } | undefined; + readonly truncated: boolean; + readonly unavailable: boolean; + readonly error?: { + readonly name: string; + readonly message: string; + readonly stack?: string | undefined; + readonly cause?: unknown; + } | undefined; +}; +export declare function useSessionListView(view: SessionListView): { + readonly status: "error" | "closed" | "idle" | "loading" | "live" | "stale"; + readonly page?: { + readonly revision: string; + readonly items: readonly { + readonly id: string; + readonly kind: string; + readonly title: string; + readonly model: string; + readonly provider: string; + readonly cwd: string; + readonly workspace_id?: string | undefined; + readonly pinned: boolean; + readonly archived: boolean; + readonly updated_at: string; + readonly truncated: boolean; + }[] | null; + readonly next_cursor?: { + readonly revision: string; + readonly offset: string; + readonly search?: string | undefined; + readonly status: "active" | "archived" | "all"; + } | null | undefined; + readonly has_more: boolean; + } | undefined; + readonly error?: { + readonly name: string; + readonly message: string; + readonly stack?: string | undefined; + readonly cause?: unknown; + } | undefined; + readonly truncated: boolean; +}; +export declare function useWhipConnection(client: WhipClient): import("./client.js").ConnectionSnapshot; diff --git a/vendor/@whip/sdk/dist/react.js b/vendor/@whip/sdk/dist/react.js new file mode 100644 index 0000000..80688bc --- /dev/null +++ b/vendor/@whip/sdk/dist/react.js @@ -0,0 +1,12 @@ +import { useSyncExternalStore } from 'react'; +/** The application owns start/dispose, including across StrictMode remounts. */ +export function useSessionView(view) { + return useSyncExternalStore(view.subscribe, view.getSnapshot, view.getSnapshot); +} +export function useSessionListView(view) { + return useSyncExternalStore(view.subscribe, view.getSnapshot, view.getSnapshot); +} +export function useWhipConnection(client) { + return useSyncExternalStore(client.subscribe, client.getSnapshot, client.getSnapshot); +} +//# sourceMappingURL=react.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/react.js.map b/vendor/@whip/sdk/dist/react.js.map new file mode 100644 index 0000000..d6bea2a --- /dev/null +++ b/vendor/@whip/sdk/dist/react.js.map @@ -0,0 +1 @@ +{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAI7C,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAAC,IAAiB;IAC9C,OAAO,oBAAoB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAqB;IACtD,OAAO,oBAAoB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAkB;IAClD,OAAO,oBAAoB,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;AACxF,CAAC","sourcesContent":["import { useSyncExternalStore } from 'react';\nimport type { WhipClient } from './client.js';\nimport type { SessionListView, SessionView } from './state.js';\n\n/** The application owns start/dispose, including across StrictMode remounts. */\nexport function useSessionView(view: SessionView) {\n return useSyncExternalStore(view.subscribe, view.getSnapshot, view.getSnapshot);\n}\n\nexport function useSessionListView(view: SessionListView) {\n return useSyncExternalStore(view.subscribe, view.getSnapshot, view.getSnapshot);\n}\n\nexport function useWhipConnection(client: WhipClient) {\n return useSyncExternalStore(client.subscribe, client.getSnapshot, client.getSnapshot);\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/schema.d.ts b/vendor/@whip/sdk/dist/schema.d.ts new file mode 100644 index 0000000..51a3a14 --- /dev/null +++ b/vendor/@whip/sdk/dist/schema.d.ts @@ -0,0 +1,86 @@ +/** + * Standard Schema and Standard JSON Schema, mirrored as types so the SDK adds + * no dependency on any schema library. Zod 4.2+, ArkType 2.1.28+, and Valibot + * (through @valibot/to-json-schema) implement both. https://standardschema.dev + */ +export interface StandardIssue { + readonly message: string; + readonly path?: ReadonlyArray | undefined; +} +export type StandardResult = { + readonly value: Output; + readonly issues?: undefined; +} | { + readonly issues: ReadonlyArray; +}; +export interface StandardTypes { + readonly input: Input; + readonly output: Output; +} +export interface StandardSchemaProps { + readonly version: 1; + readonly vendor: string; + readonly validate: (value: unknown) => StandardResult | Promise>; + readonly types?: StandardTypes | undefined; +} +export type JsonSchemaTarget = 'draft-2020-12' | 'draft-07' | 'openapi-3.0' | (string & {}); +export interface JsonSchemaOptions { + readonly target: JsonSchemaTarget; + readonly libraryOptions?: Record | undefined; +} +export interface JsonSchemaConverter { + readonly input: (options: JsonSchemaOptions) => Record; + readonly output: (options: JsonSchemaOptions) => Record; +} +export interface StandardJSONSchemaProps { + readonly version: 1; + readonly vendor: string; + readonly types?: StandardTypes | undefined; + readonly jsonSchema: JsonSchemaConverter; +} +export interface StandardSchemaV1 { + readonly '~standard': StandardSchemaProps; +} +export interface StandardJSONSchemaV1 { + readonly '~standard': StandardJSONSchemaProps; +} +/** A schema that both validates and serializes: what tool() accepts for typed inputs and outputs. */ +export interface StandardSchemaWithJSON { + readonly '~standard': StandardSchemaProps & StandardJSONSchemaProps; +} +/** Raw JSON Schema, sent as written and typed unknown. */ +export type JsonSchema = Record; +/** Anything tool() and defineAgent() accept as a schema. */ +export type Schema = StandardSchemaWithJSON | JsonSchema; +type Typed = { + readonly '~standard': { + readonly types?: StandardTypes | undefined; + }; +}; +/** The library's input type for a Standard Schema; unknown for raw JSON Schema. */ +export type InferInput = S extends Typed ? NonNullable['input'] : unknown; +/** The library's output type for a Standard Schema; unknown for raw JSON Schema. */ +export type InferOutput = S extends Typed ? NonNullable['output'] : unknown; +/** + * The draft derived schemas target. It is the spec's first-class target and + * the daemon's validator accepts it beside draft-07, which hand-written raw + * schemas may still use. + */ +export declare const JSON_SCHEMA_TARGET: JsonSchemaTarget; +export declare function isStandardSchema(schema: unknown): schema is StandardSchemaWithJSON; +/** + * Derive the wire JSON Schema for a tool input or output. Raw JSON Schema + * passes through compacted (undefined members dropped). A library that cannot + * produce the target is a definition-time error: the document's bytes are its + * revision, so there is no silent fallback to another draft. + */ +export declare function toJsonSchema(schema: Schema, io: 'input' | 'output', subject: string): JsonSchema; +/** Whether a JSON Schema describes an object, which keyword arguments require. A schema without a type is left to the daemon. */ +export declare function describesObject(schema: JsonSchema): boolean; +/** Run a Standard Schema's own validation. Raw JSON Schema has none and passes the value through. */ +export declare function validateWith(schema: Schema | undefined, value: unknown): Promise>; +/** One bounded line for an error message. */ +export declare function formatIssues(issues: ReadonlyArray, limit?: number): string; +export {}; diff --git a/vendor/@whip/sdk/dist/schema.js b/vendor/@whip/sdk/dist/schema.js new file mode 100644 index 0000000..51e7d47 --- /dev/null +++ b/vendor/@whip/sdk/dist/schema.js @@ -0,0 +1,60 @@ +import { object } from './util.js'; +/** + * The draft derived schemas target. It is the spec's first-class target and + * the daemon's validator accepts it beside draft-07, which hand-written raw + * schemas may still use. + */ +export const JSON_SCHEMA_TARGET = 'draft-2020-12'; +export function isStandardSchema(schema) { + if (!object(schema)) + return false; + const props = schema['~standard']; + return object(props) && typeof props.validate === 'function' && object(props.jsonSchema) && typeof props.jsonSchema.input === 'function'; +} +/** + * Derive the wire JSON Schema for a tool input or output. Raw JSON Schema + * passes through compacted (undefined members dropped). A library that cannot + * produce the target is a definition-time error: the document's bytes are its + * revision, so there is no silent fallback to another draft. + */ +export function toJsonSchema(schema, io, subject) { + if (isStandardSchema(schema)) { + let derived; + try { + derived = schema['~standard'].jsonSchema[io]({ target: JSON_SCHEMA_TARGET }); + } + catch (error) { + throw new TypeError(`${subject}: ${schema['~standard'].vendor} schema cannot produce ${JSON_SCHEMA_TARGET} JSON Schema`, { cause: error }); + } + if (!object(derived)) + throw new TypeError(`${subject}: ${schema['~standard'].vendor} schema produced no JSON Schema object`); + return JSON.parse(JSON.stringify(derived)); + } + if (!object(schema)) + throw new TypeError(`${subject}: schema must be a Standard JSON Schema or a JSON Schema object`); + return JSON.parse(JSON.stringify(schema)); +} +/** Whether a JSON Schema describes an object, which keyword arguments require. A schema without a type is left to the daemon. */ +export function describesObject(schema) { + const type = schema.type; + if (type === undefined) + return true; + return type === 'object' || (Array.isArray(type) && type.includes('object')); +} +/** Run a Standard Schema's own validation. Raw JSON Schema has none and passes the value through. */ +export async function validateWith(schema, value) { + if (!isStandardSchema(schema)) + return { value }; + return schema['~standard'].validate(value); +} +/** One bounded line for an error message. */ +export function formatIssues(issues, limit = 5) { + const lines = issues.slice(0, limit).map(issue => { + const path = (issue.path ?? []).map(segment => String(typeof segment === 'object' ? segment.key : segment)).join('.'); + return path ? `${path}: ${issue.message}` : issue.message; + }); + if (issues.length > limit) + lines.push(`and ${issues.length - limit} more`); + return lines.join('; '); +} +//# sourceMappingURL=schema.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/schema.js.map b/vendor/@whip/sdk/dist/schema.js.map new file mode 100644 index 0000000..0a30c4a --- /dev/null +++ b/vendor/@whip/sdk/dist/schema.js.map @@ -0,0 +1 @@ +{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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","sourcesContent":["import { object } from './util.js';\n\n/**\n * Standard Schema and Standard JSON Schema, mirrored as types so the SDK adds\n * no dependency on any schema library. Zod 4.2+, ArkType 2.1.28+, and Valibot\n * (through @valibot/to-json-schema) implement both. https://standardschema.dev\n */\nexport interface StandardIssue {\n readonly message: string;\n readonly path?: ReadonlyArray | undefined;\n}\nexport type StandardResult =\n | { readonly value: Output; readonly issues?: undefined }\n | { readonly issues: ReadonlyArray };\nexport interface StandardTypes { readonly input: Input; readonly output: Output }\nexport interface StandardSchemaProps {\n readonly version: 1;\n readonly vendor: string;\n readonly validate: (value: unknown) => StandardResult | Promise>;\n readonly types?: StandardTypes | undefined;\n}\nexport type JsonSchemaTarget = 'draft-2020-12' | 'draft-07' | 'openapi-3.0' | (string & {});\nexport interface JsonSchemaOptions { readonly target: JsonSchemaTarget; readonly libraryOptions?: Record | undefined }\nexport interface JsonSchemaConverter {\n readonly input: (options: JsonSchemaOptions) => Record;\n readonly output: (options: JsonSchemaOptions) => Record;\n}\nexport interface StandardJSONSchemaProps {\n readonly version: 1;\n readonly vendor: string;\n readonly types?: StandardTypes | undefined;\n readonly jsonSchema: JsonSchemaConverter;\n}\nexport interface StandardSchemaV1 { readonly '~standard': StandardSchemaProps }\nexport interface StandardJSONSchemaV1 { readonly '~standard': StandardJSONSchemaProps }\n/** A schema that both validates and serializes: what tool() accepts for typed inputs and outputs. */\nexport interface StandardSchemaWithJSON {\n readonly '~standard': StandardSchemaProps & StandardJSONSchemaProps;\n}\n\n/** Raw JSON Schema, sent as written and typed unknown. */\nexport type JsonSchema = Record;\n/** Anything tool() and defineAgent() accept as a schema. */\nexport type Schema = StandardSchemaWithJSON | JsonSchema;\n\ntype Typed = { readonly '~standard': { readonly types?: StandardTypes | undefined } };\n/** The library's input type for a Standard Schema; unknown for raw JSON Schema. */\nexport type InferInput = S extends Typed ? NonNullable['input'] : unknown;\n/** The library's output type for a Standard Schema; unknown for raw JSON Schema. */\nexport type InferOutput = S extends Typed ? NonNullable['output'] : unknown;\n\n/**\n * The draft derived schemas target. It is the spec's first-class target and\n * the daemon's validator accepts it beside draft-07, which hand-written raw\n * schemas may still use.\n */\nexport const JSON_SCHEMA_TARGET: JsonSchemaTarget = 'draft-2020-12';\n\nexport function isStandardSchema(schema: unknown): schema is StandardSchemaWithJSON {\n if (!object(schema)) return false;\n const props = (schema as { '~standard'?: unknown })['~standard'];\n return object(props) && typeof props.validate === 'function' && object(props.jsonSchema) && typeof props.jsonSchema.input === 'function';\n}\n\n/**\n * Derive the wire JSON Schema for a tool input or output. Raw JSON Schema\n * passes through compacted (undefined members dropped). A library that cannot\n * produce the target is a definition-time error: the document's bytes are its\n * revision, so there is no silent fallback to another draft.\n */\nexport function toJsonSchema(schema: Schema, io: 'input' | 'output', subject: string): JsonSchema {\n if (isStandardSchema(schema)) {\n let derived: unknown;\n try { derived = schema['~standard'].jsonSchema[io]({ target: JSON_SCHEMA_TARGET }); }\n catch (error) { throw new TypeError(`${subject}: ${schema['~standard'].vendor} schema cannot produce ${JSON_SCHEMA_TARGET} JSON Schema`, { cause: error }); }\n if (!object(derived)) throw new TypeError(`${subject}: ${schema['~standard'].vendor} schema produced no JSON Schema object`);\n return JSON.parse(JSON.stringify(derived)) as JsonSchema;\n }\n if (!object(schema)) throw new TypeError(`${subject}: schema must be a Standard JSON Schema or a JSON Schema object`);\n return JSON.parse(JSON.stringify(schema)) as JsonSchema;\n}\n\n/** Whether a JSON Schema describes an object, which keyword arguments require. A schema without a type is left to the daemon. */\nexport function describesObject(schema: JsonSchema): boolean {\n const type = schema.type;\n if (type === undefined) return true;\n return type === 'object' || (Array.isArray(type) && type.includes('object'));\n}\n\n/** Run a Standard Schema's own validation. Raw JSON Schema has none and passes the value through. */\nexport async function validateWith(schema: Schema | undefined, value: unknown): Promise> {\n if (!isStandardSchema(schema)) return { value };\n return schema['~standard'].validate(value);\n}\n\n/** One bounded line for an error message. */\nexport function formatIssues(issues: ReadonlyArray, limit = 5): string {\n const lines = issues.slice(0, limit).map(issue => {\n const path = (issue.path ?? []).map(segment => String(typeof segment === 'object' ? segment.key : segment)).join('.');\n return path ? `${path}: ${issue.message}` : issue.message;\n });\n if (issues.length > limit) lines.push(`and ${issues.length - limit} more`);\n return lines.join('; ');\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/services.d.ts b/vendor/@whip/sdk/dist/services.d.ts new file mode 100644 index 0000000..1db734e --- /dev/null +++ b/vendor/@whip/sdk/dist/services.d.ts @@ -0,0 +1,101 @@ +import { type CommandResult, type ConfigurationUpdate, type HostAttentionParams, type HostDirectoryParams, type HostDirectoryPickParams, type MCPBrandIconsParams, type MCPImportApplyParams, type MCPImportCandidatesParams, type PermissionDecision, type PermissionDecisionResult, type ProviderCreateParams, type ProviderDisconnectParams, type ProviderKeySetup, type ProviderLoginBeginParams, type ProviderRemoveParams, type ProviderUpdateParams, type ProviderValidateParams } from '@whip/protocol'; +import type { CallOptions, WhipClient } from './client.js'; +import type { CommandOptions } from './command.js'; +/** Host reads do not construct session actors or change client preferences. */ +export declare class Host { + private readonly client; + constructor(client: WhipClient); + directories(params?: Partial, options?: CallOptions): Promise; + /** Opens the OS folder chooser on the execution host; rejects where the host has no desktop picker. */ + pickDirectory(params?: HostDirectoryPickParams, options?: CallOptions): Promise; + /** Advisory paged attention index. Refresh its first page to see newly active roots. */ + attention(params?: Partial, options?: CallOptions): Promise; + readonly themes: { + list: (options?: CallOptions) => Promise; + resolve: (name: string, options?: CallOptions) => Promise; + resolveJSON: (json: string, options?: CallOptions) => Promise; + }; +} +export type PermissionDecisionStatus = Pick & { + operation: 'permission.decide'; +} & ({ + status: 'queued' | 'running' | 'waiting'; + result?: never; + failure?: never; +} | { + status: 'succeeded'; + result: PermissionDecisionResult; + failure?: never; +} | { + status: 'failed' | 'cancelled' | 'interrupted'; + result?: never; + failure: NonNullable; +}); +export declare class Permissions { + private readonly client; + constructor(client: WhipClient); + /** A decision is sent once. After an uncertain reply, inspect pending state before retrying. */ + decide(decision: Omit & { + command_id?: string; + }, options?: CallOptions): Promise; + /** Inspect the original client-scoped decision identity without resending it. */ + status(commandId: string, options?: CallOptions): Promise; + setMode(rootId: string, externalPermissions: boolean, options?: Pick): import("./command.js").CommandHandle<"permission.mode">; +} +export declare class Configuration { + private readonly client; + constructor(client: WhipClient); + get(options?: CallOptions): Promise; + update(patch: ConfigurationUpdate, options?: CallOptions): Promise; +} +/** MCP servers other agents configured on the execution host, and the import that makes them native. */ +export declare class MCPImport { + private readonly client; + constructor(client: WhipClient); + /** Reads config files only: nothing is dialed or launched, and no candidate carries a secret. */ + candidates(params?: MCPImportCandidatesParams, options?: CallOptions): Promise; + /** Copies the named candidates into the host's native configuration; an empty list only records that the offer was seen. */ + apply(params: MCPImportApplyParams, options?: CallOptions): Promise; + /** Small data: URIs for registrable domains the app has no bundled mark for; empty when the host keeps lookups local. */ + brandIcons(params: MCPBrandIconsParams, options?: CallOptions): Promise; +} +export declare class Providers { + private readonly client; + constructor(client: WhipClient); + /** Persist missing routes using named keys on the execution host; never sends credential values. */ + discover({ model, provider, ...options }?: CallOptions & { + model?: string; + provider?: string; + }): Promise; + list({ model, provider, ...options }?: CallOptions & { + model?: string; + provider?: string; + }): Promise; + /** Read an execution host's editable definition without retrieving its secret. */ + get(provider: string, options?: CallOptions): Promise; + /** Sent once without recovery storage. After an uncertain result, reread the provider before retrying. */ + create(params: ProviderCreateParams, options?: CallOptions): Promise; + /** Omitted fields are retained; credentials remain host-owned and are never journaled. */ + update(params: ProviderUpdateParams, options?: CallOptions): Promise; + /** Remove an unreferenced custom definition using its current configuration revision. */ + remove(params: ProviderRemoveParams, options?: CallOptions): Promise; + disconnect(params: ProviderDisconnectParams, options?: CallOptions): Promise; + catalogs({ refresh, provider, ...options }?: CallOptions & { + refresh?: boolean; + provider?: string; + }): Promise>; + status(name: string, options?: CallOptions): Promise; + setKey(params: ProviderKeySetup, options?: CallOptions): Promise; + validate(params: ProviderValidateParams, options?: CallOptions): Promise; + rotateKey(name: string, options?: CallOptions): Promise; + logout(name: string, options?: CallOptions): Promise; + readonly login: { + begin: (options?: CallOptions & ProviderLoginBeginParams) => Promise; + list: (options?: CallOptions) => Promise; + status: (flowId: string, options?: CallOptions) => Promise; + cancel: (flowId: string, options?: CallOptions) => Promise; + selectTeam: (flowId: string, teamId: string, options?: CallOptions) => Promise; + selectProject: (flowId: string, projectId: string, options?: CallOptions) => Promise; + createProject: (flowId: string, name: string, options?: CallOptions) => Promise; + }; +} diff --git a/vendor/@whip/sdk/dist/services.js b/vendor/@whip/sdk/dist/services.js new file mode 100644 index 0000000..38eedb6 --- /dev/null +++ b/vendor/@whip/sdk/dist/services.js @@ -0,0 +1,134 @@ +import { validate } from '@whip/protocol'; +import { WhipError } from './errors.js'; +/** Host reads do not construct session actors or change client preferences. */ +export class Host { + client; + constructor(client) { + this.client = client; + } + directories(params = {}, options = {}) { + return this.client.call('host.directories.list', { limit: 64, ...params }, options); + } + /** Opens the OS folder chooser on the execution host; rejects where the host has no desktop picker. */ + pickDirectory(params = {}, options = {}) { + return this.client.call('host.directory.pick', params, options); + } + /** Advisory paged attention index. Refresh its first page to see newly active roots. */ + attention(params = {}, options = {}) { + return this.client.call('host.attention', { limit: 64, max_bytes: 256 << 10, ...params }, options); + } + themes = { + list: (options = {}) => this.client.call('host.themes.list', {}, options), + resolve: (name, options = {}) => this.client.call('host.themes.resolve', { name }, options), + resolveJSON: (json, options = {}) => this.client.call('host.themes.resolve', { json }, options), + }; +} +export class Permissions { + client; + constructor(client) { + this.client = client; + } + /** A decision is sent once. After an uncertain reply, inspect pending state before retrying. */ + decide(decision, options = {}) { + return this.client.call('permission.decide', { decision: { ...decision, command_id: decision.command_id ?? this.client.createId() } }, options); + } + /** Inspect the original client-scoped decision identity without resending it. */ + async status(commandId, options = {}) { + if (!commandId.trim()) + throw new TypeError('commandId must not be empty'); + const outcome = await this.client.call('command.status', { command_id: commandId }, options); + if (outcome.command_id !== commandId || outcome.operation !== 'permission.decide' || outcome.content != null) { + throw new WhipError('invalid_response', 'Permission decision status does not match its identity or operation'); + } + switch (outcome.status) { + case 'succeeded': + if (outcome.failure != null) + throw new WhipError('invalid_response', 'Successful permission decision contains a failure'); + if (!validate('PermissionDecisionResult', outcome.result, 'response')) + throw new WhipError('invalid_response', 'Invalid successful permission decision result'); + break; + case 'failed': + case 'cancelled': + case 'interrupted': + if (outcome.result !== undefined || outcome.failure == null) + throw new WhipError('invalid_response', 'Failed permission decision is missing its failure outcome'); + break; + case 'queued': + case 'running': + case 'waiting': + if (outcome.result !== undefined || outcome.failure != null) + throw new WhipError('invalid_response', 'Pending permission decision contains a terminal outcome'); + break; + default: + throw new WhipError('invalid_response', 'Unknown permission decision status'); + } + return outcome; + } + setMode(rootId, externalPermissions, options = {}) { + return this.client.submit('permission.mode', { external_permissions: externalPermissions }, { ...options, rootId }); + } +} +export class Configuration { + client; + constructor(client) { + this.client = client; + } + get(options = {}) { return this.client.call('config.get', {}, options); } + update(patch, options = {}) { return this.client.call('config.update', patch, options); } +} +/** MCP servers other agents configured on the execution host, and the import that makes them native. */ +export class MCPImport { + client; + constructor(client) { + this.client = client; + } + /** Reads config files only: nothing is dialed or launched, and no candidate carries a secret. */ + candidates(params = {}, options = {}) { return this.client.call('mcp.import.candidates', params, options); } + /** Copies the named candidates into the host's native configuration; an empty list only records that the offer was seen. */ + apply(params, options = {}) { return this.client.call('mcp.import.apply', params, options); } + /** Small data: URIs for registrable domains the app has no bundled mark for; empty when the host keeps lookups local. */ + brandIcons(params, options = {}) { return this.client.call('mcp.brand.icons', params, options); } +} +export class Providers { + client; + constructor(client) { + this.client = client; + } + /** Persist missing routes using named keys on the execution host; never sends credential values. */ + discover({ model, provider, ...options } = {}) { + return this.client.call('provider.discover', { ...(model ? { model } : {}), ...(provider ? { provider } : {}) }, options); + } + list({ model, provider, ...options } = {}) { + return this.client.call('provider.list', { ...(model ? { model } : {}), ...(provider ? { provider } : {}) }, options); + } + /** Read an execution host's editable definition without retrieving its secret. */ + get(provider, options = {}) { return this.client.call('provider.get', { provider }, options); } + /** Sent once without recovery storage. After an uncertain result, reread the provider before retrying. */ + create(params, options = {}) { return this.client.call('provider.create', params, options); } + /** Omitted fields are retained; credentials remain host-owned and are never journaled. */ + update(params, options = {}) { return this.client.call('provider.update', params, options); } + /** Remove an unreferenced custom definition using its current configuration revision. */ + remove(params, options = {}) { return this.client.call('provider.remove', params, options); } + disconnect(params, options = {}) { return this.client.call('provider.disconnect', params, options); } + catalogs({ refresh, provider, ...options } = {}) { + return this.client.query('provider.catalogs', { ...(refresh ? { refresh } : {}), ...(provider ? { provider } : {}) }, options); + } + status(name, options = {}) { return this.client.call('provider.status', { provider: name }, options); } + setKey(params, options = {}) { return this.client.call('provider.key.set', params, options); } + validate(params, options = {}) { return this.client.call('provider.validate', params, options); } + rotateKey(name, options = {}) { return this.client.call('provider.key.rotate', { provider: name }, options); } + logout(name, options = {}) { return this.client.call('provider.logout', { provider: name }, options); } + login = { + begin: (options = {}) => { + const { provider, ...callOptions } = options; + return this.client.call('provider.login.begin', provider ? { provider } : {}, callOptions); + }, + list: (options = {}) => this.client.call('provider.login.list', {}, options), + status: (flowId, options = {}) => this.client.call('provider.login.status', { flow_id: flowId }, options), + cancel: (flowId, options = {}) => this.client.call('provider.login.cancel', { flow_id: flowId }, options), + selectTeam: (flowId, teamId, options = {}) => this.client.call('provider.login.team.select', { flow_id: flowId, team_id: teamId }, options), + selectProject: (flowId, projectId, options = {}) => this.client.call('provider.login.project.select', { flow_id: flowId, project_id: projectId }, options), + createProject: (flowId, name, options = {}) => this.client.call('provider.login.project.create', { flow_id: flowId, name }, options), + }; +} +//# sourceMappingURL=services.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/services.js.map b/vendor/@whip/sdk/dist/services.js.map new file mode 100644 index 0000000..c433433 --- /dev/null +++ b/vendor/@whip/sdk/dist/services.js.map @@ -0,0 +1 @@ +{"version":3,"file":"services.js","sourceRoot":"","sources":["../src/services.ts"],"names":[],"mappings":"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","sourcesContent":["import { validate, type CommandResult, type ConfigurationUpdate, type HostAttentionParams, type HostDirectoryParams, type HostDirectoryPickParams, type MCPBrandIconsParams, type MCPImportApplyParams, type MCPImportCandidatesParams, type PermissionDecision, type PermissionDecisionResult, type ProviderCreateParams, type ProviderDisconnectParams, type ProviderKeySetup, type ProviderLoginBeginParams, type ProviderRemoveParams, type ProviderUpdateParams, type ProviderValidateParams } from '@whip/protocol';\nimport type { CallOptions, WhipClient } from './client.js';\nimport type { CommandOptions } from './command.js';\nimport { WhipError } from './errors.js';\n\n/** Host reads do not construct session actors or change client preferences. */\nexport class Host {\n constructor(private readonly client: WhipClient) {}\n directories(params: Partial = {}, options: CallOptions = {}) {\n return this.client.call('host.directories.list', { limit: 64, ...params }, options);\n }\n /** Opens the OS folder chooser on the execution host; rejects where the host has no desktop picker. */\n pickDirectory(params: HostDirectoryPickParams = {}, options: CallOptions = {}) {\n return this.client.call('host.directory.pick', params, options);\n }\n /** Advisory paged attention index. Refresh its first page to see newly active roots. */\n attention(params: Partial = {}, options: CallOptions = {}) {\n return this.client.call('host.attention', { limit: 64, max_bytes: 256 << 10, ...params }, options);\n }\n readonly themes = {\n list: (options: CallOptions = {}) => this.client.call('host.themes.list', {}, options),\n resolve: (name: string, options: CallOptions = {}) => this.client.call('host.themes.resolve', { name }, options),\n resolveJSON: (json: string, options: CallOptions = {}) => this.client.call('host.themes.resolve', { json }, options),\n };\n}\n\nexport type PermissionDecisionStatus = Pick & { operation: 'permission.decide' } & (\n | { status: 'queued' | 'running' | 'waiting'; result?: never; failure?: never }\n | { status: 'succeeded'; result: PermissionDecisionResult; failure?: never }\n | { status: 'failed' | 'cancelled' | 'interrupted'; result?: never; failure: NonNullable }\n);\n\nexport class Permissions {\n constructor(private readonly client: WhipClient) {}\n /** A decision is sent once. After an uncertain reply, inspect pending state before retrying. */\n decide(decision: Omit & { command_id?: string }, options: CallOptions = {}) {\n return this.client.call('permission.decide', { decision: { ...decision, command_id: decision.command_id ?? this.client.createId() } }, options);\n }\n /** Inspect the original client-scoped decision identity without resending it. */\n async status(commandId: string, options: CallOptions = {}): Promise {\n if (!commandId.trim()) throw new TypeError('commandId must not be empty');\n const outcome = await this.client.call('command.status', { command_id: commandId }, options);\n if (outcome.command_id !== commandId || outcome.operation !== 'permission.decide' || outcome.content != null) {\n throw new WhipError('invalid_response', 'Permission decision status does not match its identity or operation');\n }\n switch (outcome.status) {\n case 'succeeded':\n if (outcome.failure != null) throw new WhipError('invalid_response', 'Successful permission decision contains a failure');\n if (!validate('PermissionDecisionResult', outcome.result, 'response')) throw new WhipError('invalid_response', 'Invalid successful permission decision result');\n break;\n case 'failed': case 'cancelled': case 'interrupted':\n if (outcome.result !== undefined || outcome.failure == null) throw new WhipError('invalid_response', 'Failed permission decision is missing its failure outcome');\n break;\n case 'queued': case 'running': case 'waiting':\n if (outcome.result !== undefined || outcome.failure != null) throw new WhipError('invalid_response', 'Pending permission decision contains a terminal outcome');\n break;\n default:\n throw new WhipError('invalid_response', 'Unknown permission decision status');\n }\n return outcome as PermissionDecisionStatus;\n }\n setMode(rootId: string, externalPermissions: boolean, options: Pick = {}) {\n return this.client.submit('permission.mode', { external_permissions: externalPermissions }, { ...options, rootId });\n }\n}\n\nexport class Configuration {\n constructor(private readonly client: WhipClient) {}\n get(options: CallOptions = {}) { return this.client.call('config.get', {}, options); }\n update(patch: ConfigurationUpdate, options: CallOptions = {}) { return this.client.call('config.update', patch, options); }\n}\n/** MCP servers other agents configured on the execution host, and the import that makes them native. */\nexport class MCPImport {\n constructor(private readonly client: WhipClient) {}\n /** Reads config files only: nothing is dialed or launched, and no candidate carries a secret. */\n candidates(params: MCPImportCandidatesParams = {}, options: CallOptions = {}) { return this.client.call('mcp.import.candidates', params, options); }\n /** Copies the named candidates into the host's native configuration; an empty list only records that the offer was seen. */\n apply(params: MCPImportApplyParams, options: CallOptions = {}) { return this.client.call('mcp.import.apply', params, options); }\n /** Small data: URIs for registrable domains the app has no bundled mark for; empty when the host keeps lookups local. */\n brandIcons(params: MCPBrandIconsParams, options: CallOptions = {}) { return this.client.call('mcp.brand.icons', params, options); }\n}\nexport class Providers {\n constructor(private readonly client: WhipClient) {}\n /** Persist missing routes using named keys on the execution host; never sends credential values. */\n discover({ model, provider, ...options }: CallOptions & { model?: string; provider?: string } = {}) {\n return this.client.call('provider.discover', { ...(model ? { model } : {}), ...(provider ? { provider } : {}) }, options);\n }\n list({ model, provider, ...options }: CallOptions & { model?: string; provider?: string } = {}) {\n return this.client.call('provider.list', { ...(model ? { model } : {}), ...(provider ? { provider } : {}) }, options);\n }\n /** Read an execution host's editable definition without retrieving its secret. */\n get(provider: string, options: CallOptions = {}) { return this.client.call('provider.get', { provider }, options); }\n /** Sent once without recovery storage. After an uncertain result, reread the provider before retrying. */\n create(params: ProviderCreateParams, options: CallOptions = {}) { return this.client.call('provider.create', params, options); }\n /** Omitted fields are retained; credentials remain host-owned and are never journaled. */\n update(params: ProviderUpdateParams, options: CallOptions = {}) { return this.client.call('provider.update', params, options); }\n /** Remove an unreferenced custom definition using its current configuration revision. */\n remove(params: ProviderRemoveParams, options: CallOptions = {}) { return this.client.call('provider.remove', params, options); }\n disconnect(params: ProviderDisconnectParams, options: CallOptions = {}) { return this.client.call('provider.disconnect', params, options); }\n catalogs({ refresh, provider, ...options }: CallOptions & { refresh?: boolean; provider?: string } = {}) {\n return this.client.query('provider.catalogs', { ...(refresh ? { refresh } : {}), ...(provider ? { provider } : {}) }, options);\n }\n status(name: string, options: CallOptions = {}) { return this.client.call('provider.status', { provider: name }, options); }\n setKey(params: ProviderKeySetup, options: CallOptions = {}) { return this.client.call('provider.key.set', params, options); }\n validate(params: ProviderValidateParams, options: CallOptions = {}) { return this.client.call('provider.validate', params, options); }\n rotateKey(name: string, options: CallOptions = {}) { return this.client.call('provider.key.rotate', { provider: name }, options); }\n logout(name: string, options: CallOptions = {}) { return this.client.call('provider.logout', { provider: name }, options); }\n readonly login = {\n begin: (options: CallOptions & ProviderLoginBeginParams = {}) => {\n const { provider, ...callOptions } = options;\n return this.client.call('provider.login.begin', provider ? { provider } : {}, callOptions);\n },\n list: (options: CallOptions = {}) => this.client.call('provider.login.list', {}, options),\n status: (flowId: string, options: CallOptions = {}) => this.client.call('provider.login.status', { flow_id: flowId }, options),\n cancel: (flowId: string, options: CallOptions = {}) => this.client.call('provider.login.cancel', { flow_id: flowId }, options),\n selectTeam: (flowId: string, teamId: string, options: CallOptions = {}) => this.client.call('provider.login.team.select', { flow_id: flowId, team_id: teamId }, options),\n selectProject: (flowId: string, projectId: string, options: CallOptions = {}) => this.client.call('provider.login.project.select', { flow_id: flowId, project_id: projectId }, options),\n createProject: (flowId: string, name: string, options: CallOptions = {}) => this.client.call('provider.login.project.create', { flow_id: flowId, name }, options),\n };\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/session.d.ts b/vendor/@whip/sdk/dist/session.d.ts new file mode 100644 index 0000000..befd83a --- /dev/null +++ b/vendor/@whip/sdk/dist/session.d.ts @@ -0,0 +1,76 @@ +import type { CommandOperation, RuntimeOperations, QueryOperation, EphemeralOperation, CreateSessionParams, SessionCatalogParams, SubmitPayload, HistoryPageParams, MailboxPageParams } from '@whip/protocol'; +import type { WhipClient, CallOptions } from './client.js'; +import type { CommandOptions } from './command.js'; +import { Turn, type PermissionEvent, type QuestionEvent, type RunOptions } from './turn.js'; +/** Root-bound handle. Merely constructing it causes no I/O. */ +export declare class Session { + readonly client: WhipClient; + readonly rootId: string; + constructor(client: WhipClient, rootId: string); + command(operation: O, payload: RuntimeOperations[O]['params'], options?: Omit): import("./command.js").CommandHandle; + query(operation: O, payload: RuntimeOperations[O]['params'], options?: CallOptions): Promise>; + invoke(operation: O, payload: RuntimeOperations[O]['params'], options?: CallOptions): Promise>; + submit(payload: SubmitPayload, options?: Omit): import("./command.js").CommandHandle<"submit">; + /** Submit one turn and observe it: an async iterable of typed events plus a typed result. */ + run(input: string | SubmitPayload, options?: RunOptions): Turn; + /** The open questions and permission prompts from a fresh snapshot, with the same reply methods turn events carry. */ + prompts(options?: CallOptions): Promise<(QuestionEvent | PermissionEvent)[]>; + steer(payload: SubmitPayload, options?: Omit): import("./command.js").CommandHandle<"steer">; + readonly inbox: { + steer: (agentId: string, inboxSeq: string, turnId: string) => import("./command.js").CommandHandle<"inbox.steer">; + remove: (agentId: string, inboxSeq: string) => import("./command.js").CommandHandle<"inbox.remove">; + }; + rename(title: string): import("./command.js").CommandHandle<"session.rename">; + archive(archived: boolean): import("./command.js").CommandHandle<"session.archive">; + fork(params: RuntimeOperations['session.fork']['params']): import("./command.js").CommandHandle<"session.fork">; + delete(): import("./command.js").CommandHandle<"session.delete">; + snapshot(options?: CallOptions): Promise; + configure(params: RuntimeOperations['run.configure']['params']): import("./command.js").CommandHandle<"run.configure">; + setModel(model: string, provider?: string, persistDefault?: boolean): import("./command.js").CommandHandle<"session.model">; + setEffort(effort: string, persistDefault?: boolean): import("./command.js").CommandHandle<"session.effort">; + /** externalPermissions = prompt connected clients; false = approve automatically. */ + setPermissionMode(externalPermissions: boolean): import("./command.js").CommandHandle<"permission.mode">; + cancelTurn(turnId: string): import("./command.js").CommandHandle<"cancel">; + /** Reload this session's MCP configuration from its execution host. */ + readonly mcp: { + refresh: (options?: Omit) => import("./command.js").CommandHandle<"mcp.refresh">; + }; + readonly history: { + page: (params?: Partial>, options?: CallOptions) => Promise; + clear: (expectedRevision?: string) => import("./command.js").CommandHandle<"history.clear">; + rewind: (cut: number, expectedRevision: string) => import("./command.js").CommandHandle<"history.rewind">; + compact: () => import("./command.js").CommandHandle<"history.compact">; + }; + readonly agents: { + list: (options?: CallOptions) => Promise>; + inspect: (id: string, options?: CallOptions) => Promise>; + submit: (id: string, input: string | SubmitPayload, delivery?: string) => import("./command.js").CommandHandle<"agent.submit">; + cancelTurn: (id: string, turnId: string) => import("./command.js").CommandHandle<"agent.turn.cancel">; + control: (id: string) => import("./command.js").CommandHandle<"agent.control">; + delete: (id: string) => import("./command.js").CommandHandle<"agent.delete">; + }; + /** Human inspection does not acknowledge, deliver, or complete agent mail. */ + readonly mailbox: { + list: (params?: Partial>, options?: CallOptions) => Promise; + read: (id: string, agentId?: string, options?: CallOptions) => Promise; + }; + answerQuestion(id: string, answer: string[], dismissed?: boolean): import("./command.js").CommandHandle<"question.answer">; + /** Answer a batched user.ask: one entry per question, null for skipped. */ + answerQuestions(id: string, answers: ({ + answer: string[]; + dismissed?: boolean; + } | null)[]): import("./command.js").CommandHandle<"question.answer">; + terminalInput(id: string, bytes: Uint8Array, options?: CallOptions): Promise>; +} +export declare class Sessions { + private readonly client; + constructor(client: WhipClient); + create(params: Pick & Partial>, options?: CommandOptions): import("./command.js").CommandHandle<"session.create">; + list(params?: Partial, options?: CallOptions): Promise; + /** Full metadata for one root, without opening its transcript or a subscription. */ + get(rootId: string, options?: CallOptions): Promise; + /** Advisory metadata for up to 32 roots, without opening their views. Check missing before using counts. */ + summaries(rootIds: readonly string[], options?: CallOptions): Promise; + open(rootId: string, options?: CallOptions): Promise>; + delete(rootId: string, options?: Omit): import("./command.js").CommandHandle<"session.delete">; +} diff --git a/vendor/@whip/sdk/dist/session.js b/vendor/@whip/sdk/dist/session.js new file mode 100644 index 0000000..2e76ae8 --- /dev/null +++ b/vendor/@whip/sdk/dist/session.js @@ -0,0 +1,108 @@ +import { Turn, permissionEvent, questionEvent } from './turn.js'; +import { encodeBase64, object } from './util.js'; +/** Root-bound handle. Merely constructing it causes no I/O. */ +export class Session { + client; + rootId; + constructor(client, rootId) { + this.client = client; + this.rootId = rootId; + if (!rootId) + throw new TypeError('rootId is required'); + } + command(operation, payload, options = {}) { + return this.client.submit(operation, payload, { ...options, rootId: this.rootId }); + } + query(operation, payload, options = {}) { + return this.client.query(operation, payload, { ...options, rootId: this.rootId }); + } + invoke(operation, payload, options = {}) { + return this.client.invoke(operation, payload, { ...options, rootId: this.rootId }); + } + submit(payload, options = {}) { return this.command('submit', payload, options); } + /** Submit one turn and observe it: an async iterable of typed events plus a typed result. */ + run(input, options = {}) { + return new Turn(this, typeof input === 'string' ? { text: input } : input, options); + } + /** The open questions and permission prompts from a fresh snapshot, with the same reply methods turn events carry. */ + async prompts(options = {}) { + const snapshot = await this.snapshot(options); + const base = (payload) => ({ seq: snapshot.cursor, agentId: typeof payload.agent_id === 'string' ? payload.agent_id : this.rootId, turnId: typeof payload.turn_id === 'string' ? payload.turn_id : '' }); + const questions = (snapshot.questions ?? []).filter(object).map(payload => questionEvent(this, base(payload), payload)); + const permissions = (snapshot.permissions ?? []).filter(object).filter(payload => payload.status === 'pending' || payload.status === undefined).map(payload => permissionEvent(this, base(payload), payload)); + return [...questions, ...permissions]; + } + steer(payload, options = {}) { return this.command('steer', payload, options); } + inbox = { + steer: (agentId, inboxSeq, turnId) => this.command('inbox.steer', { id: agentId, inbox_seq: inboxSeq, turn_id: turnId }), + remove: (agentId, inboxSeq) => this.command('inbox.remove', { id: agentId, inbox_seq: inboxSeq }), + }; + rename(title) { return this.command('session.rename', { title }); } + archive(archived) { return this.command('session.archive', { archived }); } + fork(params) { return this.command('session.fork', params); } + delete() { return this.client.sessions.delete(this.rootId); } + snapshot(options = {}) { return this.client.call('root.snapshot', { root_id: this.rootId }, options); } + configure(params) { return this.command('run.configure', params); } + setModel(model, provider = '', persistDefault = false) { return this.command('session.model', { model, provider, persist_default: persistDefault }); } + setEffort(effort, persistDefault = false) { return this.command('session.effort', { effort, persist_default: persistDefault }); } + /** externalPermissions = prompt connected clients; false = approve automatically. */ + setPermissionMode(externalPermissions) { return this.command('permission.mode', { external_permissions: externalPermissions }); } + cancelTurn(turnId) { return this.command('cancel', { turn_id: turnId }); } + /** Reload this session's MCP configuration from its execution host. */ + mcp = { + refresh: (options = {}) => this.command('mcp.refresh', {}, options), + }; + history = { + page: (params = {}, options = {}) => this.client.call('history.page', { + root_id: this.rootId, agent_id: this.rootId, through_seq: -1, limit: 128, max_bytes: 512 << 10, recent: true, ...params, + }, options), + clear: (expectedRevision) => this.command('history.clear', expectedRevision === undefined ? {} : { expected_revision: expectedRevision }), + rewind: (cut, expectedRevision) => this.command('history.rewind', { cut, expected_revision: expectedRevision }), + compact: () => this.command('history.compact', {}), + }; + agents = { + list: (options = {}) => this.query('agents.list', {}, options), + inspect: (id, options = {}) => this.query('agent.transcript', { id }, options), + submit: (id, input, delivery = 'queued') => this.command('agent.submit', { id, ...(typeof input === 'string' ? { text: input } : input), delivery }), + cancelTurn: (id, turnId) => this.command('agent.turn.cancel', { id, turn_id: turnId }), + control: (id) => this.command('agent.control', { id }), + delete: (id) => this.command('agent.delete', { id }), + }; + /** Human inspection does not acknowledge, deliver, or complete agent mail. */ + mailbox = { + list: (params = {}, options = {}) => this.client.call('mailbox.list', { + root_id: this.rootId, agent_id: this.rootId, limit: 64, max_bytes: 256 << 10, ...params, + }, options), + read: (id, agentId = this.rootId, options = {}) => this.client.call('mailbox.read', { + root_id: this.rootId, agent_id: agentId, id, + }, options), + }; + answerQuestion(id, answer, dismissed = false) { return this.command('question.answer', { id, answer, dismissed }); } + /** Answer a batched user.ask: one entry per question, null for skipped. */ + answerQuestions(id, answers) { + return this.command('question.answer', { + id, answer: [], dismissed: false, + answers: answers.map((entry) => ({ answer: entry?.answer ?? [], dismissed: entry?.dismissed ?? !entry?.answer?.length })), + }); + } + terminalInput(id, bytes, options = {}) { + return this.invoke('terminal.input', { id, bytes: encodeBase64(bytes) }, options); + } +} +export class Sessions { + client; + constructor(client) { + this.client = client; + } + create(params, options = {}) { + return this.client.submit('session.create', { kind: 'agent', model: '', provider: '', ...params }, options); + } + list(params = {}, options = {}) { return this.client.call('sessions.list', { limit: 128, max_bytes: 512 << 10, ...params }, options); } + /** Full metadata for one root, without opening its transcript or a subscription. */ + get(rootId, options = {}) { return this.client.call('sessions.get', { root_id: rootId }, options); } + /** Advisory metadata for up to 32 roots, without opening their views. Check missing before using counts. */ + summaries(rootIds, options = {}) { return this.client.call('sessions.summaries', { root_ids: [...rootIds] }, options); } + open(rootId, options = {}) { return this.client.query('session.open', { id: rootId }, options); } + delete(rootId, options = {}) { return this.client.submit('session.delete', { root_id: rootId }, options); } +} +//# sourceMappingURL=session.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/session.js.map b/vendor/@whip/sdk/dist/session.js.map new file mode 100644 index 0000000..34339d0 --- /dev/null +++ b/vendor/@whip/sdk/dist/session.js.map @@ -0,0 +1 @@ +{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CommandOperation, RuntimeOperations, QueryOperation, EphemeralOperation,\n CreateSessionParams, SessionCatalogParams, SubmitPayload, HistoryPageParams, MailboxPageParams,\n} from '@whip/protocol';\nimport type { WhipClient, CallOptions } from './client.js';\nimport type { CommandOptions } from './command.js';\nimport { Turn, permissionEvent, questionEvent, type PermissionEvent, type QuestionEvent, type RunOptions } from './turn.js';\nimport { encodeBase64, object } from './util.js';\n\n/** Root-bound handle. Merely constructing it causes no I/O. */\nexport class Session {\n constructor(readonly client: WhipClient, readonly rootId: string) {\n if (!rootId) throw new TypeError('rootId is required');\n }\n command(operation: O, payload: RuntimeOperations[O]['params'], options: Omit = {}) {\n return this.client.submit(operation, payload, { ...options, rootId: this.rootId });\n }\n query(operation: O, payload: RuntimeOperations[O]['params'], options: CallOptions = {}) {\n return this.client.query(operation, payload, { ...options, rootId: this.rootId });\n }\n invoke(operation: O, payload: RuntimeOperations[O]['params'], options: CallOptions = {}) {\n return this.client.invoke(operation, payload, { ...options, rootId: this.rootId });\n }\n submit(payload: SubmitPayload, options: Omit = {}) { return this.command('submit', payload, options); }\n /** Submit one turn and observe it: an async iterable of typed events plus a typed result. */\n run(input: string | SubmitPayload, options: RunOptions = {}): Turn {\n return new Turn(this, typeof input === 'string' ? { text: input } : input, options);\n }\n /** The open questions and permission prompts from a fresh snapshot, with the same reply methods turn events carry. */\n async prompts(options: CallOptions = {}): Promise<(QuestionEvent | PermissionEvent)[]> {\n const snapshot = await this.snapshot(options);\n const base = (payload: Record) => ({ seq: snapshot.cursor, agentId: typeof payload.agent_id === 'string' ? payload.agent_id : this.rootId, turnId: typeof payload.turn_id === 'string' ? payload.turn_id : '' });\n const questions = (snapshot.questions ?? []).filter(object).map(payload => questionEvent(this, base(payload), payload));\n const permissions = (snapshot.permissions ?? []).filter(object).filter(payload => payload.status === 'pending' || payload.status === undefined).map(payload => permissionEvent(this, base(payload), payload));\n return [...questions, ...permissions];\n }\n steer(payload: SubmitPayload, options: Omit = {}) { return this.command('steer', payload, options); }\n readonly inbox = {\n steer: (agentId: string, inboxSeq: string, turnId: string) => this.command('inbox.steer', { id: agentId, inbox_seq: inboxSeq, turn_id: turnId }),\n remove: (agentId: string, inboxSeq: string) => this.command('inbox.remove', { id: agentId, inbox_seq: inboxSeq }),\n };\n rename(title: string) { return this.command('session.rename', { title }); }\n archive(archived: boolean) { return this.command('session.archive', { archived }); }\n fork(params: RuntimeOperations['session.fork']['params']) { return this.command('session.fork', params); }\n delete() { return this.client.sessions.delete(this.rootId); }\n snapshot(options: CallOptions = {}) { return this.client.call('root.snapshot', { root_id: this.rootId }, options); }\n configure(params: RuntimeOperations['run.configure']['params']) { return this.command('run.configure', params); }\n setModel(model: string, provider = '', persistDefault = false) { return this.command('session.model', { model, provider, persist_default: persistDefault }); }\n setEffort(effort: string, persistDefault = false) { return this.command('session.effort', { effort, persist_default: persistDefault }); }\n /** externalPermissions = prompt connected clients; false = approve automatically. */\n setPermissionMode(externalPermissions: boolean) { return this.command('permission.mode', { external_permissions: externalPermissions }); }\n cancelTurn(turnId: string) { return this.command('cancel', { turn_id: turnId }); }\n /** Reload this session's MCP configuration from its execution host. */\n readonly mcp = {\n refresh: (options: Omit = {}) => this.command('mcp.refresh', {}, options),\n };\n readonly history = {\n page: (params: Partial> = {}, options: CallOptions = {}) => this.client.call('history.page', {\n root_id: this.rootId, agent_id: this.rootId, through_seq: -1, limit: 128, max_bytes: 512 << 10, recent: true, ...params,\n }, options),\n clear: (expectedRevision?: string) => this.command('history.clear', expectedRevision === undefined ? {} : { expected_revision: expectedRevision }),\n rewind: (cut: number, expectedRevision: string) => this.command('history.rewind', { cut, expected_revision: expectedRevision }),\n compact: () => this.command('history.compact', {}),\n };\n readonly agents = {\n list: (options: CallOptions = {}) => this.query('agents.list', {}, options),\n inspect: (id: string, options: CallOptions = {}) => this.query('agent.transcript', { id }, options),\n submit: (id: string, input: string | SubmitPayload, delivery = 'queued') => this.command('agent.submit', { id, ...(typeof input === 'string' ? { text: input } : input), delivery }),\n cancelTurn: (id: string, turnId: string) => this.command('agent.turn.cancel', { id, turn_id: turnId }),\n control: (id: string) => this.command('agent.control', { id }),\n delete: (id: string) => this.command('agent.delete', { id }),\n };\n /** Human inspection does not acknowledge, deliver, or complete agent mail. */\n readonly mailbox = {\n list: (params: Partial> = {}, options: CallOptions = {}) => this.client.call('mailbox.list', {\n root_id: this.rootId, agent_id: this.rootId, limit: 64, max_bytes: 256 << 10, ...params,\n }, options),\n read: (id: string, agentId = this.rootId, options: CallOptions = {}) => this.client.call('mailbox.read', {\n root_id: this.rootId, agent_id: agentId, id,\n }, options),\n };\n answerQuestion(id: string, answer: string[], dismissed = false) { return this.command('question.answer', { id, answer, dismissed }); }\n /** Answer a batched user.ask: one entry per question, null for skipped. */\n answerQuestions(id: string, answers: ({ answer: string[]; dismissed?: boolean } | null)[]) {\n return this.command('question.answer', {\n id, answer: [], dismissed: false,\n answers: answers.map((entry) => ({ answer: entry?.answer ?? [], dismissed: entry?.dismissed ?? !entry?.answer?.length })),\n });\n }\n terminalInput(id: string, bytes: Uint8Array, options: CallOptions = {}) {\n return this.invoke('terminal.input', { id, bytes: encodeBase64(bytes) }, options);\n }\n}\n\nexport class Sessions {\n constructor(private readonly client: WhipClient) {}\n create(params: Pick & Partial>, options: CommandOptions = {}) {\n return this.client.submit('session.create', { kind: 'agent', model: '', provider: '', ...params }, options);\n }\n list(params: Partial = {}, options: CallOptions = {}) { return this.client.call('sessions.list', { limit: 128, max_bytes: 512 << 10, ...params }, options); }\n /** Full metadata for one root, without opening its transcript or a subscription. */\n get(rootId: string, options: CallOptions = {}) { return this.client.call('sessions.get', { root_id: rootId }, options); }\n /** Advisory metadata for up to 32 roots, without opening their views. Check missing before using counts. */\n summaries(rootIds: readonly string[], options: CallOptions = {}) { return this.client.call('sessions.summaries', { root_ids: [...rootIds] }, options); }\n open(rootId: string, options: CallOptions = {}) { return this.client.query('session.open', { id: rootId }, options); }\n delete(rootId: string, options: Omit = {}) { return this.client.submit('session.delete', { root_id: rootId }, options); }\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/state.d.ts b/vendor/@whip/sdk/dist/state.d.ts new file mode 100644 index 0000000..3ec6d87 --- /dev/null +++ b/vendor/@whip/sdk/dist/state.d.ts @@ -0,0 +1,175 @@ +import type { BoundedTranscriptPage, RootCollectionPage, RootSnapshot, SessionCatalogPage } from '@whip/protocol'; +import type { WhipClient } from './client.js'; +import type { Session } from './session.js'; +import { type ExecutionEvidence } from './executions.js'; +import { type TraceEvidence } from './trace.js'; +export { executionRows, type ExecutionCell, type ExecutionHostCall, type ExecutionRestart, type ExecutionRow } from './executions.js'; +export { traceSpans, traceRoots, serverNowMs, toTraceSpan, MAX_TRACE_SPANS, type TraceSpan, type TraceEvidence, type TraceSpanKind, type TraceSpanStatus } from './trace.js'; +export type DeepReadonly = T extends (...args: never[]) => unknown ? T : T extends object ? { + readonly [K in keyof T]: DeepReadonly; +} : T; +type Message = NonNullable[number]; +type Status = 'idle' | 'loading' | 'live' | 'stale' | 'error' | 'closed'; +export interface HistoryGap { + fromSeq: number; + toSeq: number; + status: 'pending' | 'loading' | 'paused' | 'error'; + error?: string; +} +export interface HistoryView { + revision: string; + throughSeq: number; + nextSeq: number; + hasMore: boolean; + loading: boolean; + messages: Message[]; + truncated: boolean; + error?: Error; + /** Missing raw records between retained sections, not offloaded bodies. */ + gaps?: HistoryGap[]; + /** Explicit navigation evicted a newer suffix; Latest can reload it. */ + latestMissing?: boolean; +} +export interface SessionViewSnapshot { + status: Status; + root?: RootSnapshot; + history: Record; + collections: Record; + /** Previously observed waiting inputs omitted by a partial snapshot. Never actionable until verified. */ + unverifiedInbox?: NonNullable; + retainedBytes: number; + /** Bounded supplemental REPL evidence; transcript bodies remain in history. */ + executions?: ExecutionEvidence; + /** Bounded span evidence for the trace view: durable pages plus live span events, merged by id. */ + trace?: TraceEvidence; + truncated: boolean; + unavailable: boolean; + error?: Error; +} +/** Merge bounded inbox pages with snapshot truth; absent partial rows remain visibly unverified. */ +export declare function inboxItems(state: DeepReadonly, agentId: string): { + rows: { + item: NonNullable["inbox"]>[number]; + stale: boolean; + }[]; + hasMore: boolean; +}; +export interface SessionViewOptions { + maxBytes?: number; + maxMessages?: number; + /** Warm at most one older root page after the first live snapshot; off by default. */ + initialHistoryWarmup?: boolean; + /** Notification batching only; every event is processed in order. */ + notificationIntervalMs?: number; +} +/** One root subscription and a bounded, reconstructible projection of daemon state. */ +export declare class SessionView { + readonly session: Session; + private current; + private published; + private readonly listeners; + private readonly opened; + private readonly lifetime; + private readonly historyRequests; + private repair?; + private traceRequest?; + private readonly recentRecoveryCursor; + private stream?; + private disconnectListener?; + private commandListener?; + private noticeTimer?; + private refreshTimer?; + private refreshPromise?; + private refreshAgain; + private epoch; + private started; + private initialHistoryWarmup; + private runtimeID?; + private lastConnectionID?; + private streamConnectionID?; + private presentationGaps; + private incompatibleRuntime; + private unknownSeen; + private pendingBytes; + private readonly maxBytes; + private readonly maxMessages; + private readonly notificationInterval; + constructor(session: Session, options?: SessionViewOptions); + getSnapshot: () => DeepReadonly; + subscribe: (listener: () => void) => (() => void); + start(): Promise; + dispose(): Promise; + /** Refresh reconciles metadata without erasing observed activity in the same turn. */ + refresh(): Promise; + openAgent(agentId: string): Promise; + closeAgent(agentId: string): void; + loadOlder(agentId?: string): Promise; + /** Load one page of a known gap. toSeq remains stable as its beginning fills. */ + loadHistoryGap(agentId: string, toSeq: number): Promise; + loadLatest(agentId?: string): Promise; + private cancelHistoryReads; + loadCollection(name: string, options?: { + more?: boolean; + }): Promise; + private connectionChanged; + private scheduleRefresh; + private synchronize; + private consume; + private apply; + /** + * Page the root's durable spans into the view. The first call reads from the + * beginning; later calls continue from the page cursor, so a reconnect that + * missed journal events catches up without discarding what live events built. + */ + loadTrace(): Promise; + private cancelTraceRead; + private fetchTrace; + private repairHistory; + private warmInitialHistory; + private setGap; + private readHistory; + private fetchHistory; + private set; + private publish; +} +export declare function createSessionView(session: Session, options?: SessionViewOptions): SessionView; +export interface SessionListSnapshot { + status: Status; + page?: SessionCatalogPage; + error?: Error; + truncated: boolean; +} +/** Catalog polling is active only while observed; it never opens root subscriptions. */ +export declare class SessionListView { + readonly client: WhipClient; + private current; + private readonly listeners; + private readonly lifetime; + private timer?; + private stopConnection?; + private stopCommands?; + private pending?; + private started; + private epoch; + private refreshAgain; + private runtimeID?; + private readonly interval; + private readonly maxBytes; + constructor(client: WhipClient, options?: { + pollIntervalMs?: number; + maxBytes?: number; + }); + getSnapshot: () => DeepReadonly; + subscribe: (listener: () => void) => (() => void); + start(): Promise; + refresh(): Promise; + loadMore(): Promise; + dispose(): Promise; + private fetch; + private pollLater; + private set; +} +export declare function createSessionListView(client: WhipClient, options?: { + pollIntervalMs?: number; + maxBytes?: number; +}): SessionListView; diff --git a/vendor/@whip/sdk/dist/state.js b/vendor/@whip/sdk/dist/state.js new file mode 100644 index 0000000..b8669b0 --- /dev/null +++ b/vendor/@whip/sdk/dist/state.js @@ -0,0 +1,1114 @@ +import { asError, WhipError } from './errors.js'; +import { boundExecutionEvidence, emptyExecutionEvidence, observeExecution, reconcileExecutions, seedExecutions, settleExecutions } from './executions.js'; +import { emptyTraceEvidence, mergeSpanPage, observeSpan } from './trace.js'; +export { executionRows } from './executions.js'; +export { traceSpans, traceRoots, serverNowMs, toTraceSpan, MAX_TRACE_SPANS } from './trace.js'; +/** Merge bounded inbox pages with snapshot truth; absent partial rows remain visibly unverified. */ +export function inboxItems(state, agentId) { + const root = state.root; + const page = state.collections.inbox; + const pageStale = !!root && !!page && page.revision !== root.collection_revision && BigInt(page.event_cursor) < BigInt(root.cursor); + const rows = new Map(); + for (const item of state.unverifiedInbox ?? []) + if (item.agent_id === agentId) + rows.set(item.seq, { item, stale: true }); + if (root?.omitted?.inbox) + for (const entry of page?.items ?? []) { + if (entry.inbox?.agent_id === agentId) + rows.set(entry.inbox.seq, { item: entry.inbox, stale: pageStale }); + } + for (const item of root?.inbox ?? []) + if (item.agent_id === agentId && (!rows.has(item.seq) || !page || BigInt(page.event_cursor) <= BigInt(root.cursor))) + rows.set(item.seq, { item, stale: false }); + return { + rows: [...rows.values()].sort((a, b) => BigInt(a.item.seq) < BigInt(b.item.seq) ? -1 : 1), + hasMore: !!state.unverifiedInbox?.length || !!root?.omitted?.inbox && (!page || page.has_more || pageStale), + }; +} +const encoder = new TextEncoder(); +const bytes = (value) => encoder.encode(JSON.stringify(value)).byteLength; +const errorKind = (error) => error && typeof error === 'object' && 'kind' in error ? error.kind : undefined; +function freeze(value) { + if (value && typeof value === 'object' && !Object.isFrozen(value)) { + for (const child of Object.values(value)) + freeze(child); + Object.freeze(value); + } + return value; +} +function positive(value, name) { + if (!Number.isSafeInteger(value) || value < 1) + throw new RangeError(`${name} must be a positive integer`); + return value; +} +function terminalChanges(listener) { + const seen = new Set(); + return outcome => { + if (!['succeeded', 'failed', 'cancelled', 'interrupted'].includes(outcome.status) || seen.has(outcome.command_id)) + return; + seen.add(outcome.command_id); + if (seen.size > 128) + seen.delete(seen.values().next().value); + listener(); + }; +} +/** One root subscription and a bounded, reconstructible projection of daemon state. */ +export class SessionView { + session; + current = { + status: 'idle', history: {}, collections: {}, retainedBytes: 0, truncated: false, unavailable: false, + }; + published = freeze(this.current); + listeners = new Set(); + opened = new Set(); + lifetime = new AbortController(); + historyRequests = new Map(); + repair; + traceRequest; + recentRecoveryCursor = new Map(); + stream; + disconnectListener; + commandListener; + noticeTimer; + refreshTimer; + refreshPromise; + refreshAgain = false; + epoch = 0; + started = false; + initialHistoryWarmup; + runtimeID; + lastConnectionID; + streamConnectionID; + presentationGaps = new Set(); + incompatibleRuntime = false; + unknownSeen = false; + pendingBytes = 0; + maxBytes; + maxMessages; + notificationInterval; + constructor(session, options = {}) { + this.session = session; + this.maxBytes = positive(options.maxBytes ?? 8 * 1024 * 1024, 'maxBytes'); + this.maxMessages = positive(options.maxMessages ?? 512, 'maxMessages'); + this.notificationInterval = positive(options.notificationIntervalMs ?? 16, 'notificationIntervalMs'); + this.initialHistoryWarmup = options.initialHistoryWarmup ?? false; + this.opened.add(session.rootId); + } + getSnapshot = () => this.published; + subscribe = (listener) => { + this.listeners.add(listener); + return () => { this.listeners.delete(listener); }; + }; + async start() { + if (this.lifetime.signal.aborted) + throw new Error('Session view is closed'); + if (this.started) + return this.refreshPromise; + this.started = true; + this.disconnectListener = this.session.client.subscribe(() => this.connectionChanged()); + this.commandListener = this.session.client.onCommand(terminalChanges(() => this.scheduleRefresh())); + this.connectionChanged(false); + await this.refresh(); + } + async dispose() { + if (this.lifetime.signal.aborted) + return; + this.lifetime.abort(); + this.cancelTraceRead(); + this.cancelHistoryReads(); + this.epoch++; + clearTimeout(this.noticeTimer); + clearTimeout(this.refreshTimer); + this.disconnectListener?.(); + this.commandListener?.(); + const stream = this.stream; + this.stream = undefined; + this.set({ ...this.current, status: 'closed', executions: undefined, trace: undefined }, true); + this.listeners.clear(); + await stream?.dispose(); + } + /** Refresh reconciles metadata without erasing observed activity in the same turn. */ + async refresh() { + if (!this.started || this.lifetime.signal.aborted || this.incompatibleRuntime) + return; + if (this.session.client.getSnapshot().state !== 'connected') + return; + clearTimeout(this.refreshTimer); + this.refreshTimer = undefined; + if (this.refreshPromise) { + this.refreshAgain = true; + return this.refreshPromise; + } + this.refreshPromise = this.synchronize(); + try { + await this.refreshPromise; + } + finally { + this.refreshPromise = undefined; + if (this.refreshAgain) { + this.refreshAgain = false; + this.scheduleRefresh(); + } + } + } + async openAgent(agentId) { + if (!agentId) + throw new Error('Agent ID is required'); + if (this.lifetime.signal.aborted) + throw new Error('Session view is closed'); + this.opened.add(agentId); + try { + await this.readHistory(agentId, false); + void this.repairHistory(); + } + catch (error) { + if (agentId !== this.session.rootId) + this.opened.delete(agentId); + throw error; + } + } + closeAgent(agentId) { + if (agentId === this.session.rootId) + return; + this.opened.delete(agentId); + this.historyRequests.get(agentId)?.controller.abort(); + this.historyRequests.delete(agentId); + this.recentRecoveryCursor.delete(agentId); + const history = { ...this.current.history }; + delete history[agentId]; + this.set({ ...this.current, history }, true); + } + async loadOlder(agentId = this.session.rootId) { + if (!this.opened.has(agentId)) + return this.openAgent(agentId); + const history = this.current.history[agentId]; + if (history && !history.hasMore) + return; + await this.readHistory(agentId, !!history?.messages.length); + } + /** Load one page of a known gap. toSeq remains stable as its beginning fills. */ + async loadHistoryGap(agentId, toSeq) { + if (!this.opened.has(agentId)) + return; + await this.readHistory(agentId, false, { toSeq, automatic: false }); + } + async loadLatest(agentId = this.session.rootId) { + if (!this.opened.has(agentId)) + return this.openAgent(agentId); + await this.readHistory(agentId, false); + void this.repairHistory(); + } + cancelHistoryReads() { + for (const request of this.historyRequests.values()) + request.controller.abort(); + this.historyRequests.clear(); + } + async loadCollection(name, options = {}) { + if (this.lifetime.signal.aborted) + throw new Error('Session view is closed'); + const epoch = this.epoch; + const previous = options.more ? this.current.collections[name] : undefined; + if (previous && !previous.has_more) + return; + if (previous && !previous.next_cursor) + throw new WhipError('invalid_response', 'Collection is missing its continuation cursor'); + try { + const page = await this.session.client.call('root.collection', { + root_id: this.session.rootId, collection: name, + ...(previous?.next_cursor ? { cursor: previous.next_cursor } : {}), limit: 128, max_bytes: 256 * 1024, + }, { signal: this.lifetime.signal }); + if (epoch !== this.epoch || this.lifetime.signal.aborted) + return; + if (previous && previous.revision !== page.revision) + throw new WhipError('resynchronization_required', 'Collection revision changed'); + const merged = { ...page, items: [...(previous?.items ?? []), ...(page.items ?? [])] }; + const verified = new Set(merged.items.flatMap(entry => entry.inbox ? [`${entry.inbox.agent_id}:${entry.inbox.seq}`] : [])); + const unverifiedInbox = name === 'inbox' ? page.has_more + ? this.current.unverifiedInbox?.filter(item => !verified.has(`${item.agent_id}:${item.seq}`)) : [] : this.current.unverifiedInbox; + this.set({ ...this.current, unverifiedInbox, collections: { ...this.current.collections, [name]: merged } }, true); + } + catch (error) { + if (errorKind(error) === 'resynchronization_required') { + const collections = { ...this.current.collections }; + // An invalid page cursor is not evidence that previously accepted input vanished. + if (name !== 'inbox') + delete collections[name]; + this.set({ ...this.current, collections }, true); + } + throw error; + } + } + connectionChanged(refresh = true) { + const connection = this.session.client.getSnapshot(); + if (connection.state === 'connected') { + if (this.runtimeID && connection.info?.runtime_id !== this.runtimeID) { + this.incompatibleRuntime = true; + this.cancelTraceRead(); + this.epoch++; + this.cancelHistoryReads(); + void this.stream?.dispose(); + this.stream = undefined; + this.set({ ...this.current, executions: undefined, status: 'error', error: new WhipError('runtime_changed', 'Execution runtime changed; open a new session view') }, true); + } + else if (connection.info?.connection_id !== this.lastConnectionID) { + if (this.lastConnectionID) + this.cancelTraceRead(); + this.lastConnectionID = connection.info?.connection_id; + this.recentRecoveryCursor.clear(); + this.current = { ...this.current, history: Object.fromEntries(Object.entries(this.current.history).map(([id, history]) => [id, + { ...history, gaps: history.gaps?.map(gap => ({ ...gap, status: 'pending', error: undefined })) }])) }; + if (refresh) + void this.refresh(); + } + } + else { + this.cancelTraceRead(); + this.epoch++; + this.cancelHistoryReads(); + this.stream = undefined; + this.set({ ...this.current, executions: this.current.executions && settleExecutions(this.current.executions), status: connection.state === 'incompatible' ? 'error' : this.current.root ? 'stale' : 'loading', error: connection.error }, true); + } + } + scheduleRefresh() { + if (!this.started || this.lifetime.signal.aborted || this.refreshTimer) + return; + this.refreshTimer = setTimeout(() => { + this.refreshTimer = undefined; + void this.refresh(); + }, 100); + } + async synchronize() { + const epoch = ++this.epoch; + this.cancelHistoryReads(); + const oldStream = this.stream; + const continuous = !!oldStream && this.current.status === 'live' + && this.streamConnectionID === this.session.client.getSnapshot().info?.connection_id; + this.stream = undefined; + this.set({ ...this.current, status: continuous ? 'live' : this.current.root ? 'stale' : 'loading' }, true); + // Capture after publishing so reconciliation cannot restore evicted rows. + const retained = this.current.root; + let recovered = continuous ? retained : undefined; + try { + await oldStream?.dispose(); + const snapshot = await this.session.client.call('root.snapshot', { root_id: this.session.rootId }, { signal: this.lifetime.signal }); + if (epoch !== this.epoch || this.lifetime.signal.aborted) + return; + if (snapshot.root_id !== this.session.rootId) + throw new Error('Snapshot belongs to a different root'); + // A reconnect must not undo already observed lifecycle events either. + if (retained && BigInt(snapshot.cursor) < BigInt(retained.cursor)) { + throw new WhipError('resynchronization_required', 'Snapshot cursor moved backwards'); + } + // A reconnect snapshot is only a suffix. Reuse cached activity only when + // one bounded replay proves no intervening discard or other event was lost. + let replay; + if (!continuous && retained?.history_revision === snapshot.history_revision + && Object.entries(snapshot.active_turns).some(([id, turn]) => turn && retained.active_turns[id] === turn)) { + const distance = BigInt(snapshot.cursor) - BigInt(retained.cursor); + if (distance === 0n) + recovered = retained; + else if (distance > 0n && distance <= 1000n) { + try { + const page = await this.session.client.events.replay(snapshot.root_id, retained.cursor, { limit: 1000, signal: this.lifetime.signal }); + const events = page.events.filter(event => BigInt(event.seq) <= BigInt(snapshot.cursor)); + if (!page.expired && BigInt(events.length) === distance && events.every((event, index) => event.root_id === snapshot.root_id && !event.unknown + && BigInt(event.seq) === BigInt(retained.cursor) + BigInt(index + 1) + && !event.payload?.truncated)) { + recovered = retained; + replay = events; + } + } + catch { /* Expired/unavailable replay falls back to the bounded snapshot. */ } + if (epoch !== this.epoch || this.lifetime.signal.aborted) + return; + } + } + const previous = recovered; + const gaps = new Set(); + const omitted = { ...snapshot.omitted }; + const partial = !!(snapshot.omitted?.presentation || snapshot.omitted?.presentation_prefix); + const reconcile = (agentId, events) => { + const sameTurn = previous?.history_revision === snapshot.history_revision + && !!snapshot.active_turns[agentId] && previous.active_turns[agentId] === snapshot.active_turns[agentId]; + if (sameTurn) { + for (const key of ['presentation', 'presentation_prefix']) { + if (previous.omitted?.[key]) + omitted[key] = true; + } + } + if (sameTurn && replay) + events = replay.filter(event => { + const owner = event.payload?.agent_id || snapshot.root_id; + return owner === agentId && (event.kind.startsWith('stream.') || event.kind === 'inbox.running'); + }); + let rows = sameTurn ? (agentId === snapshot.root_id ? previous.presentation : previous.agent_presentations?.[agentId]) ?? [] : []; + let cursor = sameTurn ? BigInt(previous.cursor) : 0n; + let gap = sameTurn && this.presentationGaps.has(agentId); + for (const event of events ?? []) { + const seq = BigInt(event.seq); + // Filter raw events before grouping: a grouped row keeps its FIRST seq. + if (seq <= cursor) + continue; + if (event.kind === 'inbox.running') { + gap = true; + cursor = seq; + continue; + } + gap ||= partial && seq !== cursor + 1n; + gap ||= (snapshot.inbox ?? []).some(item => item.agent_id === agentId && !!item.delivery_seq + && BigInt(item.delivery_seq) > cursor && BigInt(item.delivery_seq) < seq); + const next = appendPresentation(rows, event, !gap); + if (!presentationTelemetry(event.kind) && next.at(-1) !== rows.at(-1)) + gap = false; + rows = next; + cursor = seq; + } + // Missing suffixes also separate the next live delta from retained text. + if (rows.length && (gap || (partial && cursor < BigInt(snapshot.cursor)))) + gaps.add(agentId); + return rows; + }; + const root = { + ...snapshot, + omitted, + presentation: reconcile(snapshot.root_id, snapshot.presentation), + agent_presentations: Object.fromEntries([...new Set([ + ...Object.keys(previous?.agent_presentations ?? {}), ...Object.keys(snapshot.agent_presentations ?? {}), + ])].map(id => [id, reconcile(id, snapshot.agent_presentations?.[id])])), + }; + const stream = await this.session.client.events.subscribe(root.root_id, root.cursor, { signal: this.lifetime.signal }); + if (epoch !== this.epoch || this.lifetime.signal.aborted) { + await stream.dispose(); + return; + } + this.runtimeID ??= this.session.client.getSnapshot().info?.runtime_id; + this.stream = stream; + this.streamConnectionID = this.session.client.getSnapshot().info?.connection_id; + this.presentationGaps = gaps; + const revisionChanged = this.current.root?.history_revision !== root.history_revision; + if (revisionChanged) + this.recentRecoveryCursor.clear(); + const history = revisionChanged ? {} : Object.fromEntries(Object.entries(this.current.history).map(([id, history]) => [id, + { ...history, loading: false, gaps: history.gaps?.map(gap => gap.status === 'loading' ? { ...gap, status: 'pending' } : gap) }])); + const recent = (root.messages ?? []).map((message, index) => ({ + seq: root.message_seqs?.[index] ?? (root.first_message_seq ?? 1) + index, message, + })); + const first = recent[0]?.seq ?? 1; + if (recent.length) + this.recentRecoveryCursor.delete(root.root_id); + const previousHistory = history[root.root_id]; + const all = mergeMessages(previousHistory?.messages ?? [], recent); + const merged = all.slice(-this.maxMessages); + const nextSeq = merged[0]?.seq ?? first; + // Snapshot omission describes its suffix, not the merged history. Preserve + // a known beginning only while that same boundary remains in the cache. + const hasMore = merged.length + ? nextSeq > 1 && (previousHistory?.nextSeq !== nextSeq || previousHistory.hasMore) + : !!root.omitted?.messages; + history[root.root_id] = historyCoverage({ + revision: root.history_revision, throughSeq: recent.at(-1)?.seq ?? previousHistory?.throughSeq ?? 0, + nextSeq, hasMore, + loading: false, messages: merged, truncated: all.length > merged.length || !!root.omitted?.messages, + gaps: previousHistory?.gaps, + latestMissing: !recent.length && !!root.omitted?.messages && this.recentRecoveryCursor.get(root.root_id) !== root.cursor, + }); + this.set({ + status: 'live', root, history, collections: root.omitted?.inbox && this.current.collections.inbox ? { inbox: this.current.collections.inbox } : {}, retainedBytes: 0, + unverifiedInbox: root.omitted?.inbox ? [...new Map([ + ...(this.current.unverifiedInbox ?? []), ...(previous?.inbox ?? []), + ].filter(item => item.status === 'queued' && item.origin === 'client' + && !root.inbox?.some(current => current.agent_id === item.agent_id && current.seq === item.seq)) + .map(item => [`${item.agent_id}:${item.seq}`, item])).values()].slice(-128) : [], + executions: seedExecutions(this.current.executions, snapshot, history), + // Spans do not depend on the history revision; a refresh keeps them and + // the next loadTrace pages in whatever the journal window missed. + trace: this.current.trace, + truncated: Object.values(root.omitted ?? {}).some(Boolean), unavailable: this.unknownSeen, + }, true); + void this.consume(stream, epoch); + for (const agentId of this.opened) { + // A deleted/unavailable child does not invalidate the root snapshot. + if (agentId !== this.session.rootId && epoch === this.epoch) + await this.readHistory(agentId, false).catch(() => { }); + } + void this.repairHistory(); + } + catch (error) { + if (epoch !== this.epoch || this.lifetime.signal.aborted) + return; + this.set({ ...this.current, executions: this.current.executions && settleExecutions(this.current.executions), status: this.current.root ? 'stale' : 'error', error: asError(error) }, true); + if (errorKind(error) === 'resynchronization_required') + this.scheduleRefresh(); + } + } + async consume(stream, epoch) { + try { + for await (const event of stream) { + if (epoch !== this.epoch || this.stream !== stream || this.lifetime.signal.aborted) + return; + this.apply(event); + } + } + catch (error) { + if (epoch !== this.epoch || this.lifetime.signal.aborted) + return; + this.set({ ...this.current, executions: this.current.executions && settleExecutions(this.current.executions), status: 'stale', error: asError(error) }, true); + // Recovery never keeps applying a stream after a gap, expiry, or overflow. + this.scheduleRefresh(); + } + } + apply(event) { + const previous = this.current.root; + if (!previous || event.root_id !== previous.root_id || BigInt(event.seq) <= BigInt(previous.cursor)) + return; + if (BigInt(event.seq) !== BigInt(previous.cursor) + 1n) + throw new WhipError('resynchronization_required', 'Event sequence gap; resynchronization required'); + const executions = observeExecution(this.current.executions ?? emptyExecutionEvidence(previous.root_id, previous.history_revision), event, previous.active_turns, this.current.history, Date.now()); + let root = { ...previous, cursor: event.seq }; + let unavailable = this.current.unavailable; + let trace = this.current.trace; + const payload = event.payload; + if (event.unknown || (payload?.truncated && payload.content)) { + unavailable = true; + if (!this.unknownSeen) { + this.unknownSeen = true; + this.scheduleRefresh(); + } + } + if (event.kind === 'span.started' || event.kind === 'span.ended') { + // Span events carry the whole record, so no snapshot refresh is needed. + trace = observeSpan(trace ?? emptyTraceEvidence(previous.root_id), { kind: event.kind, payload: event.payload }); + } + else if (event.kind === 'stream.accounting') { + const accounting = event.payload.accounting; + if (accounting && accounting.root_id === root.root_id && accounting.agent_id === root.root_id + && accounting.scope === 'subtree' && (!root.accounting || BigInt(accounting.revision) >= BigInt(root.accounting.revision))) { + root.accounting = accounting; + } + } + else if (event.kind.startsWith('stream.')) { + const item = { seq: event.seq, kind: event.kind, payload: event.payload }; + const agentId = typeof payload?.agent_id === 'string' && payload.agent_id ? payload.agent_id : root.root_id; + const rows = (agentId === root.root_id ? root.presentation : root.agent_presentations?.[agentId]) ?? []; + const next = appendPresentation(rows, item, !this.presentationGaps.has(agentId)); + if (!presentationTelemetry(item.kind) && next.at(-1) !== rows.at(-1)) + this.presentationGaps.delete(agentId); + if (agentId !== root.root_id) + root.agent_presentations = { ...root.agent_presentations, [agentId]: next }; + else + root.presentation = next; + } + else if (event.kind.startsWith('session.') && event.kind.endsWith('.updated')) { + root.meta = { + ...root.meta, + ...(typeof payload.title === 'string' ? { title: payload.title } : {}), + ...(typeof payload.model === 'string' ? { model: payload.model } : {}), + ...(typeof payload.provider === 'string' ? { provider: payload.provider } : {}), + ...(payload.effort_changed && typeof payload.effort === 'string' ? { effort: payload.effort } : {}), + ...(typeof payload.working_directory === 'string' ? { cwd: payload.working_directory } : {}), + ...(typeof payload.archived === 'boolean' ? { archived: payload.archived } : {}), + }; + // permission_mode is a top-level RootSnapshot field, not session meta. + if (typeof payload.permission_mode === 'string') + root = { ...root, permission_mode: payload.permission_mode }; + } + else { + const lifecycle = payload; + if (event.kind === 'schedule.fired' && root.upcoming_schedules) { + // Both slots use the host's canonical UTC timestamp, preserving nanoseconds. + // Match occurrences, not IDs: a delayed fire must leave a newer slot. + root = { ...root, + upcoming_schedules: root.upcoming_schedules.filter(item => item.id !== lifecycle.schedule_id || item.next_fire !== lifecycle.slot), + // A recurring or omitted occurrence may already have a new pending slot. + upcoming_schedule_count: undefined, omitted: { ...root.omitted, upcoming_schedules: true } }; + } + if (/^(agent\.)?turn\./.test(event.kind)) { + const agentId = lifecycle.agent_id || root.root_id; + root.active_turns = { ...root.active_turns }; + if (event.kind.endsWith('.started') && lifecycle.turn_id) { + this.presentationGaps.delete(agentId); + root.active_turns[agentId] = lifecycle.turn_id; + if (agentId === root.root_id) + root.presentation = []; + else + root.agent_presentations = { ...root.agent_presentations, [agentId]: [] }; + } + else if (root.active_turns[agentId] === lifecycle.turn_id) + delete root.active_turns[agentId]; + } + if (lifecycle.agent_id && lifecycle.inbox_seq && ['inbox.removed', 'command.cancelled', 'inbox.running', 'turn.started', 'agent.turn.started'].includes(event.kind)) { + const removed = event.kind === 'inbox.removed' || event.kind === 'command.cancelled'; + const update = (item) => item.agent_id === lifecycle.agent_id && item.seq === lifecycle.inbox_seq + ? { ...item, status: removed ? 'cancelled' : 'running', delivery_seq: event.kind === 'inbox.running' ? event.seq : undefined, steer_turn_id: '' } : item; + const known = [...(this.current.unverifiedInbox ?? []), ...(this.current.collections.inbox?.items?.flatMap(entry => entry.inbox ? [entry.inbox] : []) ?? [])] + .find(item => item.agent_id === lifecycle.agent_id && item.seq === lifecycle.inbox_seq); + const inbox = root.inbox ?? []; + root = { ...root, inbox: [...inbox, ...(!removed && known && !inbox.some(item => item.agent_id === known.agent_id && item.seq === known.seq) ? [known] : [])] + .map(update).filter(item => item.status !== 'cancelled') }; + this.current = { ...this.current, unverifiedInbox: this.current.unverifiedInbox?.filter(item => item.agent_id !== lifecycle.agent_id || item.seq !== lifecycle.inbox_seq) }; + const page = this.current.collections.inbox; + if (page) + this.current = { ...this.current, collections: { ...this.current.collections, inbox: { ...page, items: page.items?.map(entry => entry.inbox ? { inbox: update(entry.inbox) } : entry).filter(entry => entry.inbox?.status !== 'cancelled') ?? null } } }; + } + if (event.kind === 'inbox.running') + this.presentationGaps.add(lifecycle.agent_id || root.root_id); + if (event.kind === 'question.pending') { + root.questions = [...(root.questions ?? []).filter(item => item.question_id !== lifecycle.question_id), lifecycle]; + } + else if (event.kind === 'question.answered' || event.kind === 'question.closed') { + root.questions = (root.questions ?? []).filter(item => item.question_id !== lifecycle.question_id); + } + // Lifecycle events intentionally carry partial records. A coalesced snapshot + // refreshes collections and committed history instead of inventing missing fields. + if (!event.unknown) + this.scheduleRefresh(); + } + // Hidden tabs can throttle notification timers indefinitely. Bound incoming + // growth independently, without reserializing the cached history per delta. + this.pendingBytes += bytes(event); + this.set({ ...this.current, root, executions, unavailable, trace }, this.published.retainedBytes + this.pendingBytes > this.maxBytes); + } + /** + * Page the root's durable spans into the view. The first call reads from the + * beginning; later calls continue from the page cursor, so a reconnect that + * missed journal events catches up without discarding what live events built. + */ + async loadTrace() { + if (this.lifetime.signal.aborted) + throw new Error('Session view is closed'); + if (this.incompatibleRuntime) + throw new WhipError('runtime_changed', 'Execution runtime changed; open a new session view'); + if (this.traceRequest) + return this.traceRequest.promise; + const controller = new AbortController(); + // Trace pages are root-scoped, not snapshot/history-revision-scoped. A + // concurrent root refresh must not discard their result or strand loading. + const promise = Promise.resolve().then(() => this.fetchTrace(controller.signal)); + const request = { controller, promise }; + this.traceRequest = request; + try { + await promise; + } + finally { + if (this.traceRequest === request) + this.traceRequest = undefined; + } + } + cancelTraceRead() { + this.traceRequest?.controller.abort(); + this.traceRequest = undefined; + if (this.current.trace?.loading) + this.current = { ...this.current, trace: { ...this.current.trace, loading: false } }; + } + async fetchTrace(signal) { + if (signal.aborted) + return; + const rootId = this.session.rootId; + let evidence = this.current.trace ?? emptyTraceEvidence(rootId); + this.set({ ...this.current, trace: { ...evidence, loading: true, error: undefined } }, true); + try { + for (let pages = 0; pages < 8; pages++) { + const page = await this.session.client.call('trace.page', { root_id: rootId, after_seq: evidence.pageCursor, limit: 2048 }, { signal }); + if (signal.aborted) + return; + if (page.root_id !== rootId) + throw new Error('Trace page belongs to a different root'); + if (page.has_more && BigInt(page.next_seq) <= BigInt(evidence.pageCursor)) + throw new Error('Trace page cursor did not advance'); + evidence = mergeSpanPage(this.current.trace ?? evidence, page); + this.set({ ...this.current, trace: { ...evidence, loading: true } }, true); + if (!page.has_more) + break; + } + } + catch (error) { + if (signal.aborted) + return; + this.set({ ...this.current, trace: { ...(this.current.trace ?? evidence), error: asError(error) } }, true); + throw error; + } + finally { + if (!signal.aborted) + this.set({ ...this.current, trace: { ...(this.current.trace ?? evidence), loading: false } }, true); + } + } + async repairHistory() { + if (this.repair?.epoch === this.epoch) + return this.repair.promise; + if (this.lifetime.signal.aborted || this.current.status !== 'live') + return; + const epoch = this.epoch; + const promise = (async () => { + for (let pages = 0; pages < 4 && epoch === this.epoch; pages++) { + const target = [...this.opened].map(agentId => ({ agentId, history: this.current.history[agentId] })) + .find(({ agentId, history }) => { + if (!history) + return false; + const omittedSnapshot = agentId === this.session.rootId && !this.current.root?.messages?.length && this.current.root?.omitted?.messages; + const missingRecent = (!history.messages.length && history.hasMore) || omittedSnapshot; + return history.gaps?.some(gap => gap.status === 'pending') || (missingRecent && !history.error + && this.recentRecoveryCursor.get(agentId) !== this.current.root?.cursor); + }); + if (!target) + break; + const { agentId, history } = target; + const gap = history.gaps?.find(gap => gap.status === 'pending'); + if (!gap) + this.recentRecoveryCursor.set(agentId, this.current.root?.cursor ?? ''); + try { + await this.readHistory(agentId, false, gap && { toSeq: gap.toSeq, automatic: true }); + } + catch { /* The scoped history/gap error supplies explicit retry. */ } + } + if (epoch !== this.epoch) + return; + // A publication or ordinary refresh must not restart an exhausted pass. + for (const [agentId, history] of Object.entries(this.current.history)) + for (const gap of history.gaps ?? []) + if (gap.status === 'pending') + this.setGap(agentId, gap.toSeq, { status: 'paused' }); + })(); + const repair = { epoch, promise }; + this.repair = repair; + try { + await promise; + } + finally { + if (this.repair === repair) + this.repair = undefined; + } + if (epoch === this.epoch) + void this.warmInitialHistory(); + } + async warmInitialHistory() { + if (!this.initialHistoryWarmup || this.current.status !== 'live') + return; + const epoch = this.epoch; + const agentId = this.session.rootId; + // Let a user read or recent recovery finish before deciding whether to extend. + const existing = this.historyRequests.get(agentId); + if (existing) + await existing.promise.catch(() => { }); + if (epoch !== this.epoch || this.lifetime.signal.aborted || this.current.status !== 'live' || !this.initialHistoryWarmup) + return; + this.initialHistoryWarmup = false; + const history = this.current.history[agentId]; + if (!history?.messages.length || !history.hasMore || history.error || history.latestMissing || history.gaps?.length) + return; + await this.readHistory(agentId, true).catch(() => { }); + } + setGap(agentId, toSeq, patch) { + const history = this.current.history[agentId]; + if (!history?.gaps?.some(gap => gap.toSeq === toSeq)) + return; + this.set({ ...this.current, history: { ...this.current.history, [agentId]: { + ...history, gaps: history.gaps.map(gap => gap.toSeq === toSeq ? { ...gap, ...patch } : gap), + } } }, true); + } + async readHistory(agentId, older, repair) { + const key = repair ? `gap:${repair.toSeq}` : older ? 'older' : 'recent'; + const existing = this.historyRequests.get(agentId); + if (existing) { + if (existing.epoch === this.epoch && existing.key === key) + return existing.promise; + await existing.promise.catch(() => { }); + if (this.historyRequests.get(agentId) === existing) + this.historyRequests.delete(agentId); + if (this.lifetime.signal.aborted || !this.opened.has(agentId)) + return; + return this.readHistory(agentId, older, repair); + } + if (this.lifetime.signal.aborted || this.session.client.getSnapshot().state !== 'connected') + return; + // An explicit older read also spends the shared view's one warm-up page. + if (older && agentId === this.session.rootId) + this.initialHistoryWarmup = false; + const controller = new AbortController(); + const request = { epoch: this.epoch, key, controller, promise: this.fetchHistory(agentId, older, controller.signal, repair) }; + this.historyRequests.set(agentId, request); + try { + await request.promise; + } + finally { + if (this.historyRequests.get(agentId) === request) + this.historyRequests.delete(agentId); + } + } + async fetchHistory(agentId, older, signal, repair) { + const epoch = this.epoch; + const prior = this.current.history[agentId]; + const gap = repair && prior?.gaps?.find(gap => gap.toSeq === repair.toSeq); + if (repair && !gap) + return; + const placeholder = prior ?? { + revision: this.current.root?.history_revision ?? '', throughSeq: 0, nextSeq: 0, + hasMore: true, messages: [], truncated: false, loading: true, + }; + if (gap) + this.setGap(agentId, gap.toSeq, { status: 'loading', error: undefined }); + else + this.set({ ...this.current, history: { ...this.current.history, [agentId]: { ...placeholder, loading: true, error: undefined } } }, true); + const currentRequest = () => epoch === this.epoch && this.opened.has(agentId) && !signal.aborted && !this.lifetime.signal.aborted; + try { + const page = await this.session.client.call('history.page', { + root_id: this.session.rootId, agent_id: agentId, + ...(gap ? { after_seq: gap.fromSeq - 1, through_seq: gap.toSeq, revision: placeholder.revision } + : { recent: true, through_seq: older && prior ? prior.throughSeq : -1, + ...(older && prior ? { before_seq: prior.nextSeq, revision: prior.revision } : {}) }), + limit: 128, max_bytes: 256 * 1024, + }, { signal }); + if (!currentRequest()) + return; + if (older && prior && page.has_more && page.next_seq >= prior.nextSeq && prior.nextSeq > 0) + throw new WhipError('invalid_response', 'History cursor did not advance'); + if (this.current.root && page.history_revision !== this.current.root.history_revision) + throw new WhipError('resynchronization_required', 'History revision changed'); + if (gap && (page.history_revision !== placeholder.revision || page.through_seq !== gap.toSeq + || !page.messages?.length || page.messages.some((item, index) => item.seq !== gap.fromSeq + index || item.seq > gap.toSeq) + || page.next_seq !== page.messages.at(-1).seq || (!page.has_more && page.next_seq < gap.toSeq))) + throw new WhipError('invalid_response', 'Missing history page did not advance within its requested range'); + const retained = this.current.history[agentId]; + // Budget eviction can remove a child's window while its read is in flight. + // Keep the known end, but never resurrect its evicted message bodies. + const current = retained ?? prior; + const sameRevision = current?.revision === page.history_revision; + const all = mergeMessages(sameRevision ? retained?.messages ?? [] : [], page.messages ?? []); + // Explicit reads retain the page the person requested. Automatic repair + // favors the newest window, just like an ordinary metadata refresh. + const start = gap && !repair?.automatic + ? Math.max(0, all.findIndex(item => item.seq === page.messages[0].seq) - Math.floor((this.maxMessages - Math.min(this.maxMessages, page.messages.length)) / 2)) : 0; + const messages = gap && !repair?.automatic ? all.slice(start, start + this.maxMessages) + : older ? all.slice(0, this.maxMessages) : all.slice(-this.maxMessages); + const nextSeq = messages[0]?.seq ?? page.next_seq; + const hasMore = sameRevision && current.nextSeq === nextSeq && (gap || nextSeq < page.next_seq) + ? current.hasMore : gap && sameRevision ? nextSeq > current.nextSeq || current.hasMore : nextSeq > page.next_seq || page.has_more; + const history = historyCoverage({ + revision: page.history_revision, throughSeq: Math.max(sameRevision ? current.throughSeq : 0, page.through_seq), + nextSeq, hasMore, loading: false, messages, + gaps: sameRevision ? current.gaps?.map(item => gap && item.toSeq === gap.toSeq + ? { ...item, status: repair?.automatic ? 'pending' : 'paused', error: undefined } : item) : undefined, + truncated: all.length > messages.length || messages.some(item => !!item.body), + }); + const histories = { ...this.current.history, [agentId]: history }; + this.set({ ...this.current, history: histories, executions: this.current.executions && reconcileExecutions(this.current.executions, histories) }, true); + const remaining = this.current.history[agentId]?.gaps?.find(item => item.toSeq === gap?.toSeq); + if (remaining && remaining.fromSeq === gap?.fromSeq) + this.setGap(agentId, remaining.toSeq, { status: 'paused' }); + } + catch (error) { + if (!currentRequest()) + return; + if (errorKind(error) === 'resynchronization_required') { + const history = { ...this.current.history }; + delete history[agentId]; + this.set({ ...this.current, history }, true); + this.scheduleRefresh(); + } + else if (gap) + this.setGap(agentId, gap.toSeq, { status: 'error', error: asError(error).message.slice(0, 512) }); + else + this.set({ ...this.current, history: { ...this.current.history, + [agentId]: { ...(this.current.history[agentId] ?? placeholder), loading: false, error: asError(error) } }, error: asError(error) }, true); + throw error; + } + } + set(next, immediate = false) { + this.current = next; + if (immediate) { + clearTimeout(this.noticeTimer); + this.noticeTimer = undefined; + this.publish(); + } + else if (!this.noticeTimer) + this.noticeTimer = setTimeout(() => { this.noticeTimer = undefined; this.publish(); }, this.notificationInterval); + } + publish() { + const before = this.current; + this.current = bound(before, this.maxBytes, this.maxMessages); + for (const id of Object.keys(before.history)) { + if (id !== this.session.rootId && !this.current.history[id]) + this.opened.delete(id); + } + this.published = freeze(this.current); + this.pendingBytes = 0; + for (const listener of this.listeners) + listener(); + } +} +/** Raw sequence numbers are contiguous per agent/revision. Bodies may be handles. */ +function historyCoverage(history) { + const previous = new Map(history.gaps?.map(gap => [gap.toSeq, gap])); + const gaps = []; + for (let index = 1; index < history.messages.length; index++) { + const fromSeq = history.messages[index - 1].seq + 1, toSeq = history.messages[index].seq - 1; + if (fromSeq <= toSeq) + gaps.push({ ...previous.get(toSeq), fromSeq, toSeq, status: previous.get(toSeq)?.status ?? 'pending' }); + } + return { ...history, gaps, latestMissing: !!history.latestMissing || (!!history.messages.length && history.messages.at(-1).seq < history.throughSeq) }; +} +function mergeMessages(left, right) { + return [...new Map([...left, ...right].map(message => [message.seq, message])).values()].sort((a, b) => a.seq - b.seq); +} +function presentationTelemetry(kind) { + return kind === 'stream.usage' || kind === 'stream.cell.host' || kind === 'stream.cell.host.started'; +} +function appendPresentation(previous, item, continuous = true) { + if (item.kind === 'stream.accounting') + return previous; + const payload = (item.payload ?? {}); + // Legacy content-only events have no owner or call identity. Never attribute + // them to the root or turn cumulative deltas into anonymous tool rows. + if (payload.truncated && payload.content && !payload.agent_id) + return previous; + const cumulative = ['stream.tool.call', 'stream.tool.output'].includes(item.kind); + let index = cumulative && payload.id ? previous.findIndex(row => { + const value = (row.payload ?? {}); + return row.kind === item.kind && value.id === payload.id && value.agent_id === payload.agent_id && value.turn_id === payload.turn_id && value.part_id === payload.part_id; + }) : previous.length - 1; + // These events update existing evidence; they do not split a prose/thought + // part. Only join proven continuous text, never across notices or missing data. + if (continuous && payload.part_id && (item.kind === 'stream.text' || item.kind === 'stream.reasoning')) { + while (index >= 0 && presentationTelemetry(previous[index].kind)) + index--; + } + const last = previous[index]; + const lastPayload = (last?.payload ?? {}); + if (last?.kind === item.kind && payload.id === lastPayload.id && payload.agent_id === lastPayload.agent_id && payload.turn_id === lastPayload.turn_id && payload.part_id === lastPayload.part_id) { + let next; + // Tool arguments/output are full values so far, even when calls interleave. + if (cumulative && payload.id) + next = { ...item, seq: last.seq }; + else if (continuous && ['stream.text', 'stream.reasoning', 'stream.terminal.output'].includes(item.kind) + && typeof payload.text === 'string' && typeof lastPayload.text === 'string') { + next = { ...last, payload: { ...payload, text: lastPayload.text + payload.text } }; + } + // Keep the first event's sequence as the row key; root.cursor tracks progress. + if (next) + return [...previous.slice(0, index), next, ...previous.slice(index + 1)]; + } + return [...previous, item]; +} +function bound(state, maxBytes, maxMessages) { + let next = { ...state, history: { ...state.history }, collections: { ...state.collections } }; + for (const [id, history] of Object.entries(next.history)) { + if (history.messages.length > maxMessages) { + const messages = history.messages.slice(-maxMessages); + next.history[id] = historyCoverage({ ...history, messages, nextSeq: messages[0].seq, hasMore: true, truncated: true }); + next.truncated = true; + } + } + const size = () => bytes({ root: next.root, history: next.history, collections: next.collections, unverifiedInbox: next.unverifiedInbox, executions: next.executions }); + if (next.executions?.truncated) + next.truncated = true; + let total = size(); + if (total > maxBytes) { + next.truncated = true; + next.collections = {}; + next.unverifiedInbox = []; + if (next.executions) { + const otherBytes = bytes({ root: next.root, history: next.history, collections: next.collections }); + next.executions = boundExecutionEvidence(next.executions, Math.max(0, maxBytes - otherBytes - 32)); + } + total = size(); + const children = Object.keys(next.history).filter(id => id !== next.root?.root_id) + .sort((a, b) => Number(!!next.root?.active_turns[a]) - Number(!!next.root?.active_turns[b])); + for (const id of children) { + if (total <= maxBytes) + break; + delete next.history[id]; + total = size(); + } + const rootHistory = next.root && next.history[next.root.root_id]; + if (total > maxBytes && next.root && rootHistory) { + next.history[next.root.root_id] = historyCoverage({ ...rootHistory, messages: [], truncated: true, hasMore: true, nextSeq: 0 }); + total = size(); + } + } + if (total > maxBytes && next.root) { + next.root = { ...next.root, messages: [], message_seqs: [], presentation: [], agent_presentations: {}, omitted: { ...next.root.omitted, messages: true, presentation: true } }; + next.unavailable = true; + total = size(); + } + if (total > maxBytes) { + // A configured budget may be smaller than a single metadata record. Expose + // that limitation explicitly rather than violating the bound indefinitely. + next.root = undefined; + next.executions = undefined; + next.history = {}; + next.collections = {}; + next.unavailable = true; + next.error = new Error('Session metadata exceeds the view byte budget'); + total = size(); + } + return { ...next, retainedBytes: total }; +} +export function createSessionView(session, options) { + return new SessionView(session, options); +} +/** Catalog polling is active only while observed; it never opens root subscriptions. */ +export class SessionListView { + client; + current = Object.freeze({ status: 'idle', truncated: false }); + listeners = new Set(); + lifetime = new AbortController(); + timer; + stopConnection; + stopCommands; + pending; + started = false; + epoch = 0; + refreshAgain = false; + runtimeID; + interval; + maxBytes; + constructor(client, options = {}) { + this.client = client; + this.interval = positive(options.pollIntervalMs ?? 2000, 'pollIntervalMs'); + this.maxBytes = positive(options.maxBytes ?? 2 * 1024 * 1024, 'maxBytes'); + } + getSnapshot = () => this.current; + subscribe = (listener) => { + this.listeners.add(listener); + if (this.listeners.size === 1 && this.started) { + void this.refresh(); + this.pollLater(); + } + return () => { + this.listeners.delete(listener); + if (!this.listeners.size) { + clearTimeout(this.timer); + this.timer = undefined; + } + }; + }; + async start() { + if (this.lifetime.signal.aborted) + throw new Error('Session list view is closed'); + if (this.started) + return this.pending; + this.started = true; + this.stopConnection = this.client.subscribe(() => { + this.epoch++; + if (this.client.getSnapshot().state === 'connected') { + if (this.runtimeID && this.runtimeID !== this.client.getSnapshot().info?.runtime_id) { + this.set({ status: 'loading', truncated: false }); + } + void this.refresh(); + this.pollLater(); + } + else { + clearTimeout(this.timer); + this.timer = undefined; + this.set({ ...this.current, status: 'stale' }); + } + }); + this.stopCommands = this.client.onCommand(terminalChanges(() => { + if (this.listeners.size) + void this.refresh(); + })); + await this.refresh(); + this.pollLater(); + } + async refresh() { + if (!this.started || this.lifetime.signal.aborted || this.client.getSnapshot().state !== 'connected') + return; + if (this.pending) { + this.refreshAgain = true; + return this.pending; + } + this.pending = this.fetch(false); + try { + await this.pending; + } + finally { + this.pending = undefined; + if (this.refreshAgain) { + this.refreshAgain = false; + void this.refresh(); + } + } + } + async loadMore() { + if (this.pending) + return this.pending; + if (!this.current.page?.has_more) + return; + if (!this.current.page.next_cursor) + throw new WhipError('invalid_response', 'Session list is missing its continuation cursor'); + this.pending = this.fetch(true); + try { + await this.pending; + } + finally { + this.pending = undefined; + } + } + async dispose() { + if (this.lifetime.signal.aborted) + return; + this.lifetime.abort(); + this.epoch++; + clearTimeout(this.timer); + this.stopConnection?.(); + this.stopCommands?.(); + this.set({ ...this.current, status: 'closed' }); + this.listeners.clear(); + } + async fetch(more) { + const epoch = this.epoch; + const previous = this.current.page; + this.set({ ...this.current, status: previous ? 'stale' : 'loading' }); + try { + if (previous && !more) { + const revision = await this.client.call('sessions.revision', {}, { signal: this.lifetime.signal }); + if (epoch !== this.epoch || this.lifetime.signal.aborted) + return; + if (revision.revision === previous.revision) { + this.set({ ...this.current, status: 'live', error: undefined }); + return; + } + } + const page = await this.client.call('sessions.list', { + ...(more && previous?.next_cursor ? { cursor: previous.next_cursor } : {}), status: 'active', limit: 128, max_bytes: 256 * 1024, + }, { signal: this.lifetime.signal }); + if (epoch !== this.epoch || this.lifetime.signal.aborted) + return; + if (more && previous && previous.revision !== page.revision) + throw new WhipError('resynchronization_required', 'Session list revision changed'); + this.runtimeID = this.client.getSnapshot().info?.runtime_id; + const merged = { ...page, items: more ? [...(previous?.items ?? []), ...(page.items ?? [])] : page.items }; + if (bytes(merged) > this.maxBytes) { + this.set({ ...this.current, status: 'live', truncated: true, error: new Error('Session list cache is full; refresh to start a new page window') }); + } + else + this.set({ status: 'live', page: merged, truncated: false }); + } + catch (error) { + if (epoch !== this.epoch || this.lifetime.signal.aborted) + return; + this.set({ ...this.current, status: previous ? 'stale' : 'error', error: asError(error) }); + if (more && errorKind(error) === 'resynchronization_required') { + this.set({ status: 'stale', truncated: false }); + await this.fetch(false); + } + } + } + pollLater() { + if (this.lifetime.signal.aborted || !this.started || !this.listeners.size || this.timer || this.client.getSnapshot().state !== 'connected') + return; + this.timer = setTimeout(async () => { + this.timer = undefined; + await this.refresh(); + this.pollLater(); + }, this.interval); + } + set(value) { + this.current = freeze(value); + for (const listener of this.listeners) + listener(); + } +} +export function createSessionListView(client, options) { + return new SessionListView(client, options); +} +//# sourceMappingURL=state.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/state.js.map b/vendor/@whip/sdk/dist/state.js.map new file mode 100644 index 0000000..70f40c0 --- /dev/null +++ b/vendor/@whip/sdk/dist/state.js.map @@ -0,0 +1 @@ +{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BoundedTranscriptPage, LifecycleEvent, RootCollectionPage, RootSnapshot,\n SessionCatalogPage, StreamEvent,\n} from '@whip/protocol';\nimport type { SdkEvent, WhipClient } from './client.js';\nimport type { Session } from './session.js';\nimport { asError, WhipError } from './errors.js';\nimport { boundExecutionEvidence, emptyExecutionEvidence, observeExecution, reconcileExecutions, seedExecutions, settleExecutions, type ExecutionEvidence } from './executions.js';\nimport { emptyTraceEvidence, mergeSpanPage, observeSpan, type TraceEvidence } from './trace.js';\nexport { executionRows, type ExecutionCell, type ExecutionHostCall, type ExecutionRestart, type ExecutionRow } from './executions.js';\nexport { traceSpans, traceRoots, serverNowMs, toTraceSpan, MAX_TRACE_SPANS, type TraceSpan, type TraceEvidence, type TraceSpanKind, type TraceSpanStatus } from './trace.js';\n\nexport type DeepReadonly = T extends (...args: never[]) => unknown ? T\n : T extends object ? { readonly [K in keyof T]: DeepReadonly } : T;\ntype Message = NonNullable[number];\ntype Presentation = NonNullable[number];\ntype Status = 'idle' | 'loading' | 'live' | 'stale' | 'error' | 'closed';\n\nexport interface HistoryGap {\n fromSeq: number;\n toSeq: number;\n status: 'pending' | 'loading' | 'paused' | 'error';\n error?: string;\n}\n\nexport interface HistoryView {\n revision: string;\n throughSeq: number;\n nextSeq: number;\n hasMore: boolean;\n loading: boolean;\n messages: Message[];\n truncated: boolean;\n error?: Error;\n /** Missing raw records between retained sections, not offloaded bodies. */\n gaps?: HistoryGap[];\n /** Explicit navigation evicted a newer suffix; Latest can reload it. */\n latestMissing?: boolean;\n}\n\nexport interface SessionViewSnapshot {\n status: Status;\n root?: RootSnapshot;\n history: Record;\n collections: Record;\n /** Previously observed waiting inputs omitted by a partial snapshot. Never actionable until verified. */\n unverifiedInbox?: NonNullable;\n retainedBytes: number;\n /** Bounded supplemental REPL evidence; transcript bodies remain in history. */\n executions?: ExecutionEvidence;\n /** Bounded span evidence for the trace view: durable pages plus live span events, merged by id. */\n trace?: TraceEvidence;\n truncated: boolean;\n unavailable: boolean;\n error?: Error;\n}\n\n/** Merge bounded inbox pages with snapshot truth; absent partial rows remain visibly unverified. */\nexport function inboxItems(state: DeepReadonly, agentId: string) {\n const root = state.root;\n const page = state.collections.inbox;\n const pageStale = !!root && !!page && page.revision !== root.collection_revision && BigInt(page.event_cursor) < BigInt(root.cursor);\n const rows = new Map['inbox']>[number]; stale: boolean }>();\n for (const item of state.unverifiedInbox ?? []) if (item.agent_id === agentId) rows.set(item.seq, { item, stale: true });\n if (root?.omitted?.inbox) for (const entry of page?.items ?? []) {\n if (entry.inbox?.agent_id === agentId) rows.set(entry.inbox.seq, { item: entry.inbox, stale: pageStale });\n }\n for (const item of root?.inbox ?? []) if (item.agent_id === agentId && (!rows.has(item.seq) || !page || BigInt(page.event_cursor) <= BigInt(root!.cursor))) rows.set(item.seq, { item, stale: false });\n return {\n rows: [...rows.values()].sort((a, b) => BigInt(a.item.seq) < BigInt(b.item.seq) ? -1 : 1),\n hasMore: !!state.unverifiedInbox?.length || !!root?.omitted?.inbox && (!page || page.has_more || pageStale),\n };\n}\n\nexport interface SessionViewOptions {\n maxBytes?: number;\n maxMessages?: number;\n /** Warm at most one older root page after the first live snapshot; off by default. */\n initialHistoryWarmup?: boolean;\n /** Notification batching only; every event is processed in order. */\n notificationIntervalMs?: number;\n}\n\nconst encoder = new TextEncoder();\nconst bytes = (value: unknown): number => encoder.encode(JSON.stringify(value)).byteLength;\nconst errorKind = (error: unknown): unknown => error && typeof error === 'object' && 'kind' in error ? error.kind : undefined;\n\nfunction freeze(value: T): DeepReadonly {\n if (value && typeof value === 'object' && !Object.isFrozen(value)) {\n for (const child of Object.values(value)) freeze(child);\n Object.freeze(value);\n }\n return value as DeepReadonly;\n}\n\nfunction positive(value: number, name: string): number {\n if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`${name} must be a positive integer`);\n return value;\n}\n\nfunction terminalChanges(listener: () => void): (outcome: { command_id: string; status: string }) => void {\n const seen = new Set();\n return outcome => {\n if (!['succeeded', 'failed', 'cancelled', 'interrupted'].includes(outcome.status) || seen.has(outcome.command_id)) return;\n seen.add(outcome.command_id);\n if (seen.size > 128) seen.delete(seen.values().next().value!);\n listener();\n };\n}\n\n/** One root subscription and a bounded, reconstructible projection of daemon state. */\nexport class SessionView {\n private current: SessionViewSnapshot = {\n status: 'idle', history: {}, collections: {}, retainedBytes: 0, truncated: false, unavailable: false,\n };\n private published = freeze(this.current);\n private readonly listeners = new Set<() => void>();\n private readonly opened = new Set();\n private readonly lifetime = new AbortController();\n private readonly historyRequests = new Map }>();\n private repair?: { epoch: number; promise: Promise };\n private traceRequest?: { controller: AbortController; promise: Promise };\n private readonly recentRecoveryCursor = new Map();\n private stream?: Awaited>;\n private disconnectListener?: () => void;\n private commandListener?: () => void;\n private noticeTimer?: ReturnType;\n private refreshTimer?: ReturnType;\n private refreshPromise?: Promise;\n private refreshAgain = false;\n private epoch = 0;\n private started = false;\n private initialHistoryWarmup: boolean;\n private runtimeID?: string;\n private lastConnectionID?: string;\n private streamConnectionID?: string;\n private presentationGaps = new Set();\n private incompatibleRuntime = false;\n private unknownSeen = false;\n private pendingBytes = 0;\n private readonly maxBytes: number;\n private readonly maxMessages: number;\n private readonly notificationInterval: number;\n\n constructor(readonly session: Session, options: SessionViewOptions = {}) {\n this.maxBytes = positive(options.maxBytes ?? 8 * 1024 * 1024, 'maxBytes');\n this.maxMessages = positive(options.maxMessages ?? 512, 'maxMessages');\n this.notificationInterval = positive(options.notificationIntervalMs ?? 16, 'notificationIntervalMs');\n this.initialHistoryWarmup = options.initialHistoryWarmup ?? false;\n this.opened.add(session.rootId);\n }\n\n getSnapshot = (): DeepReadonly => this.published;\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => { this.listeners.delete(listener); };\n };\n\n async start(): Promise {\n if (this.lifetime.signal.aborted) throw new Error('Session view is closed');\n if (this.started) return this.refreshPromise;\n this.started = true;\n this.disconnectListener = this.session.client.subscribe(() => this.connectionChanged());\n this.commandListener = this.session.client.onCommand(terminalChanges(() => this.scheduleRefresh()));\n this.connectionChanged(false);\n await this.refresh();\n }\n\n async dispose(): Promise {\n if (this.lifetime.signal.aborted) return;\n this.lifetime.abort();\n this.cancelTraceRead();\n this.cancelHistoryReads();\n this.epoch++;\n clearTimeout(this.noticeTimer);\n clearTimeout(this.refreshTimer);\n this.disconnectListener?.();\n this.commandListener?.();\n const stream = this.stream;\n this.stream = undefined;\n this.set({ ...this.current, status: 'closed', executions: undefined, trace: undefined }, true);\n this.listeners.clear();\n await stream?.dispose();\n }\n\n /** Refresh reconciles metadata without erasing observed activity in the same turn. */\n async refresh(): Promise {\n if (!this.started || this.lifetime.signal.aborted || this.incompatibleRuntime) return;\n if (this.session.client.getSnapshot().state !== 'connected') return;\n clearTimeout(this.refreshTimer);\n this.refreshTimer = undefined;\n if (this.refreshPromise) {\n this.refreshAgain = true;\n return this.refreshPromise;\n }\n this.refreshPromise = this.synchronize();\n try { await this.refreshPromise; }\n finally {\n this.refreshPromise = undefined;\n if (this.refreshAgain) {\n this.refreshAgain = false;\n this.scheduleRefresh();\n }\n }\n }\n\n async openAgent(agentId: string): Promise {\n if (!agentId) throw new Error('Agent ID is required');\n if (this.lifetime.signal.aborted) throw new Error('Session view is closed');\n this.opened.add(agentId);\n try { await this.readHistory(agentId, false); void this.repairHistory(); }\n catch (error) {\n if (agentId !== this.session.rootId) this.opened.delete(agentId);\n throw error;\n }\n }\n\n closeAgent(agentId: string): void {\n if (agentId === this.session.rootId) return;\n this.opened.delete(agentId);\n this.historyRequests.get(agentId)?.controller.abort();\n this.historyRequests.delete(agentId);\n this.recentRecoveryCursor.delete(agentId);\n const history = { ...this.current.history };\n delete history[agentId];\n this.set({ ...this.current, history }, true);\n }\n\n async loadOlder(agentId = this.session.rootId): Promise {\n if (!this.opened.has(agentId)) return this.openAgent(agentId);\n const history = this.current.history[agentId];\n if (history && !history.hasMore) return;\n await this.readHistory(agentId, !!history?.messages.length);\n }\n\n /** Load one page of a known gap. toSeq remains stable as its beginning fills. */\n async loadHistoryGap(agentId: string, toSeq: number): Promise {\n if (!this.opened.has(agentId)) return;\n await this.readHistory(agentId, false, { toSeq, automatic: false });\n }\n\n async loadLatest(agentId = this.session.rootId): Promise {\n if (!this.opened.has(agentId)) return this.openAgent(agentId);\n await this.readHistory(agentId, false);\n void this.repairHistory();\n }\n\n private cancelHistoryReads(): void {\n for (const request of this.historyRequests.values()) request.controller.abort();\n this.historyRequests.clear();\n }\n\n async loadCollection(name: string, options: { more?: boolean } = {}): Promise {\n if (this.lifetime.signal.aborted) throw new Error('Session view is closed');\n const epoch = this.epoch;\n const previous = options.more ? this.current.collections[name] : undefined;\n if (previous && !previous.has_more) return;\n if (previous && !previous.next_cursor) throw new WhipError('invalid_response', 'Collection is missing its continuation cursor');\n try {\n const page = await this.session.client.call('root.collection', {\n root_id: this.session.rootId, collection: name,\n ...(previous?.next_cursor ? { cursor: previous.next_cursor } : {}), limit: 128, max_bytes: 256 * 1024,\n }, { signal: this.lifetime.signal });\n if (epoch !== this.epoch || this.lifetime.signal.aborted) return;\n if (previous && previous.revision !== page.revision) throw new WhipError('resynchronization_required', 'Collection revision changed');\n const merged = { ...page, items: [...(previous?.items ?? []), ...(page.items ?? [])] };\n const verified = new Set(merged.items.flatMap(entry => entry.inbox ? [`${entry.inbox.agent_id}:${entry.inbox.seq}`] : []));\n const unverifiedInbox = name === 'inbox' ? page.has_more\n ? this.current.unverifiedInbox?.filter(item => !verified.has(`${item.agent_id}:${item.seq}`)) : [] : this.current.unverifiedInbox;\n this.set({ ...this.current, unverifiedInbox, collections: { ...this.current.collections, [name]: merged } }, true);\n } catch (error) {\n if (errorKind(error) === 'resynchronization_required') {\n const collections = { ...this.current.collections };\n // An invalid page cursor is not evidence that previously accepted input vanished.\n if (name !== 'inbox') delete collections[name];\n this.set({ ...this.current, collections }, true);\n }\n throw error;\n }\n }\n\n private connectionChanged(refresh = true): void {\n const connection = this.session.client.getSnapshot();\n if (connection.state === 'connected') {\n if (this.runtimeID && connection.info?.runtime_id !== this.runtimeID) {\n this.incompatibleRuntime = true;\n this.cancelTraceRead();\n this.epoch++;\n this.cancelHistoryReads();\n void this.stream?.dispose();\n this.stream = undefined;\n this.set({ ...this.current, executions: undefined, status: 'error', error: new WhipError('runtime_changed', 'Execution runtime changed; open a new session view') }, true);\n } else if (connection.info?.connection_id !== this.lastConnectionID) {\n if (this.lastConnectionID) this.cancelTraceRead();\n this.lastConnectionID = connection.info?.connection_id;\n this.recentRecoveryCursor.clear();\n this.current = { ...this.current, history: Object.fromEntries(Object.entries(this.current.history).map(([id, history]) => [id,\n { ...history, gaps: history.gaps?.map(gap => ({ ...gap, status: 'pending' as const, error: undefined })) }])) };\n if (refresh) void this.refresh();\n }\n } else {\n this.cancelTraceRead();\n this.epoch++;\n this.cancelHistoryReads();\n this.stream = undefined;\n this.set({ ...this.current, executions: this.current.executions && settleExecutions(this.current.executions), status: connection.state === 'incompatible' ? 'error' : this.current.root ? 'stale' : 'loading', error: connection.error }, true);\n }\n }\n\n private scheduleRefresh(): void {\n if (!this.started || this.lifetime.signal.aborted || this.refreshTimer) return;\n this.refreshTimer = setTimeout(() => {\n this.refreshTimer = undefined;\n void this.refresh();\n }, 100);\n }\n\n private async synchronize(): Promise {\n const epoch = ++this.epoch;\n this.cancelHistoryReads();\n const oldStream = this.stream;\n const continuous = !!oldStream && this.current.status === 'live'\n && this.streamConnectionID === this.session.client.getSnapshot().info?.connection_id;\n this.stream = undefined;\n this.set({ ...this.current, status: continuous ? 'live' : this.current.root ? 'stale' : 'loading' }, true);\n // Capture after publishing so reconciliation cannot restore evicted rows.\n const retained = this.current.root;\n let recovered = continuous ? retained : undefined;\n try {\n await oldStream?.dispose();\n const snapshot = await this.session.client.call('root.snapshot', { root_id: this.session.rootId }, { signal: this.lifetime.signal });\n if (epoch !== this.epoch || this.lifetime.signal.aborted) return;\n if (snapshot.root_id !== this.session.rootId) throw new Error('Snapshot belongs to a different root');\n // A reconnect must not undo already observed lifecycle events either.\n if (retained && BigInt(snapshot.cursor) < BigInt(retained.cursor)) {\n throw new WhipError('resynchronization_required', 'Snapshot cursor moved backwards');\n }\n // A reconnect snapshot is only a suffix. Reuse cached activity only when\n // one bounded replay proves no intervening discard or other event was lost.\n let replay: SdkEvent[] | undefined;\n if (!continuous && retained?.history_revision === snapshot.history_revision\n && Object.entries(snapshot.active_turns).some(([id, turn]) => turn && retained.active_turns[id] === turn)) {\n const distance = BigInt(snapshot.cursor) - BigInt(retained.cursor);\n if (distance === 0n) recovered = retained;\n else if (distance > 0n && distance <= 1000n) {\n try {\n const page = await this.session.client.events.replay(snapshot.root_id, retained.cursor, { limit: 1000, signal: this.lifetime.signal });\n const events = page.events.filter(event => BigInt(event.seq) <= BigInt(snapshot.cursor));\n if (!page.expired && BigInt(events.length) === distance && events.every((event, index) =>\n event.root_id === snapshot.root_id && !event.unknown\n && BigInt(event.seq) === BigInt(retained.cursor) + BigInt(index + 1)\n && !(event.payload as { truncated?: boolean } | undefined)?.truncated)) {\n recovered = retained;\n replay = events;\n }\n } catch { /* Expired/unavailable replay falls back to the bounded snapshot. */ }\n if (epoch !== this.epoch || this.lifetime.signal.aborted) return;\n }\n }\n const previous = recovered;\n const gaps = new Set();\n const omitted = { ...snapshot.omitted };\n const partial = !!(snapshot.omitted?.presentation || snapshot.omitted?.presentation_prefix);\n const reconcile = (agentId: string, events?: Presentation[] | null): Presentation[] => {\n const sameTurn = previous?.history_revision === snapshot.history_revision\n && !!snapshot.active_turns[agentId] && previous.active_turns[agentId] === snapshot.active_turns[agentId];\n if (sameTurn) {\n for (const key of ['presentation', 'presentation_prefix']) {\n if (previous.omitted?.[key]) omitted[key] = true;\n }\n }\n if (sameTurn && replay) events = replay.filter(event => {\n const owner = (event.payload as { agent_id?: string } | undefined)?.agent_id || snapshot.root_id;\n return owner === agentId && (event.kind.startsWith('stream.') || event.kind === 'inbox.running');\n });\n let rows = sameTurn ? (agentId === snapshot.root_id ? previous.presentation : previous.agent_presentations?.[agentId]) ?? [] : [];\n let cursor = sameTurn ? BigInt(previous.cursor) : 0n;\n let gap = sameTurn && this.presentationGaps.has(agentId);\n for (const event of events ?? []) {\n const seq = BigInt(event.seq);\n // Filter raw events before grouping: a grouped row keeps its FIRST seq.\n if (seq <= cursor) continue;\n if (event.kind === 'inbox.running') { gap = true; cursor = seq; continue; }\n gap ||= partial && seq !== cursor + 1n;\n gap ||= (snapshot.inbox ?? []).some(item => item.agent_id === agentId && !!item.delivery_seq\n && BigInt(item.delivery_seq) > cursor && BigInt(item.delivery_seq) < seq);\n const next = appendPresentation(rows, event, !gap);\n if (!presentationTelemetry(event.kind) && next.at(-1) !== rows.at(-1)) gap = false;\n rows = next;\n cursor = seq;\n }\n // Missing suffixes also separate the next live delta from retained text.\n if (rows.length && (gap || (partial && cursor < BigInt(snapshot.cursor)))) gaps.add(agentId);\n return rows;\n };\n const root = {\n ...snapshot,\n omitted,\n presentation: reconcile(snapshot.root_id, snapshot.presentation),\n agent_presentations: Object.fromEntries([...new Set([\n ...Object.keys(previous?.agent_presentations ?? {}), ...Object.keys(snapshot.agent_presentations ?? {}),\n ])].map(id => [id, reconcile(id, snapshot.agent_presentations?.[id])])),\n };\n const stream = await this.session.client.events.subscribe(root.root_id, root.cursor, { signal: this.lifetime.signal });\n if (epoch !== this.epoch || this.lifetime.signal.aborted) { await stream.dispose(); return; }\n this.runtimeID ??= this.session.client.getSnapshot().info?.runtime_id;\n this.stream = stream;\n this.streamConnectionID = this.session.client.getSnapshot().info?.connection_id;\n this.presentationGaps = gaps;\n const revisionChanged = this.current.root?.history_revision !== root.history_revision;\n if (revisionChanged) this.recentRecoveryCursor.clear();\n const history: Record = revisionChanged ? {} : Object.fromEntries(Object.entries(this.current.history).map(([id, history]) => [id,\n { ...history, loading: false, gaps: history.gaps?.map(gap => gap.status === 'loading' ? { ...gap, status: 'pending' as const } : gap) }]));\n const recent = (root.messages ?? []).map((message, index) => ({\n seq: root.message_seqs?.[index] ?? (root.first_message_seq ?? 1) + index, message,\n }));\n const first = recent[0]?.seq ?? 1;\n if (recent.length) this.recentRecoveryCursor.delete(root.root_id);\n const previousHistory = history[root.root_id];\n const all = mergeMessages(previousHistory?.messages ?? [], recent);\n const merged = all.slice(-this.maxMessages);\n const nextSeq = merged[0]?.seq ?? first;\n // Snapshot omission describes its suffix, not the merged history. Preserve\n // a known beginning only while that same boundary remains in the cache.\n const hasMore = merged.length\n ? nextSeq > 1 && (previousHistory?.nextSeq !== nextSeq || previousHistory.hasMore)\n : !!root.omitted?.messages;\n history[root.root_id] = historyCoverage({\n revision: root.history_revision, throughSeq: recent.at(-1)?.seq ?? previousHistory?.throughSeq ?? 0,\n nextSeq, hasMore,\n loading: false, messages: merged, truncated: all.length > merged.length || !!root.omitted?.messages,\n gaps: previousHistory?.gaps,\n latestMissing: !recent.length && !!root.omitted?.messages && this.recentRecoveryCursor.get(root.root_id) !== root.cursor,\n });\n this.set({\n status: 'live', root, history, collections: root.omitted?.inbox && this.current.collections.inbox ? { inbox: this.current.collections.inbox } : {}, retainedBytes: 0,\n unverifiedInbox: root.omitted?.inbox ? [...new Map([\n ...(this.current.unverifiedInbox ?? []), ...(previous?.inbox ?? []),\n ].filter(item => item.status === 'queued' && item.origin === 'client'\n && !root.inbox?.some(current => current.agent_id === item.agent_id && current.seq === item.seq))\n .map(item => [`${item.agent_id}:${item.seq}`, item])).values()].slice(-128) : [],\n executions: seedExecutions(this.current.executions, snapshot, history),\n // Spans do not depend on the history revision; a refresh keeps them and\n // the next loadTrace pages in whatever the journal window missed.\n trace: this.current.trace,\n truncated: Object.values(root.omitted ?? {}).some(Boolean), unavailable: this.unknownSeen,\n }, true);\n void this.consume(stream, epoch);\n for (const agentId of this.opened) {\n // A deleted/unavailable child does not invalidate the root snapshot.\n if (agentId !== this.session.rootId && epoch === this.epoch) await this.readHistory(agentId, false).catch(() => {});\n }\n void this.repairHistory();\n } catch (error) {\n if (epoch !== this.epoch || this.lifetime.signal.aborted) return;\n this.set({ ...this.current, executions: this.current.executions && settleExecutions(this.current.executions), status: this.current.root ? 'stale' : 'error', error: asError(error) }, true);\n if (errorKind(error) === 'resynchronization_required') this.scheduleRefresh();\n }\n }\n\n private async consume(stream: NonNullable, epoch: number): Promise {\n try {\n for await (const event of stream) {\n if (epoch !== this.epoch || this.stream !== stream || this.lifetime.signal.aborted) return;\n this.apply(event);\n }\n } catch (error) {\n if (epoch !== this.epoch || this.lifetime.signal.aborted) return;\n this.set({ ...this.current, executions: this.current.executions && settleExecutions(this.current.executions), status: 'stale', error: asError(error) }, true);\n // Recovery never keeps applying a stream after a gap, expiry, or overflow.\n this.scheduleRefresh();\n }\n }\n\n private apply(event: SdkEvent): void {\n const previous = this.current.root;\n if (!previous || event.root_id !== previous.root_id || BigInt(event.seq) <= BigInt(previous.cursor)) return;\n if (BigInt(event.seq) !== BigInt(previous.cursor) + 1n) throw new WhipError('resynchronization_required', 'Event sequence gap; resynchronization required');\n const executions = observeExecution(this.current.executions ?? emptyExecutionEvidence(previous.root_id, previous.history_revision),\n event, previous.active_turns, this.current.history, Date.now());\n let root = { ...previous, cursor: event.seq };\n let unavailable = this.current.unavailable;\n let trace = this.current.trace;\n const payload = event.payload as Record;\n if (event.unknown || (payload?.truncated && payload.content)) {\n unavailable = true;\n if (!this.unknownSeen) { this.unknownSeen = true; this.scheduleRefresh(); }\n }\n if (event.kind === 'span.started' || event.kind === 'span.ended') {\n // Span events carry the whole record, so no snapshot refresh is needed.\n trace = observeSpan(trace ?? emptyTraceEvidence(previous.root_id), { kind: event.kind, payload: event.payload });\n } else if (event.kind === 'stream.accounting') {\n const accounting = (event.payload as StreamEvent).accounting;\n if (accounting && accounting.root_id === root.root_id && accounting.agent_id === root.root_id\n && accounting.scope === 'subtree' && (!root.accounting || BigInt(accounting.revision) >= BigInt(root.accounting.revision))) {\n root.accounting = accounting;\n }\n } else if (event.kind.startsWith('stream.')) {\n const item: Presentation = { seq: event.seq, kind: event.kind, payload: event.payload };\n const agentId = typeof payload?.agent_id === 'string' && payload.agent_id ? payload.agent_id : root.root_id;\n const rows = (agentId === root.root_id ? root.presentation : root.agent_presentations?.[agentId]) ?? [];\n const next = appendPresentation(rows, item, !this.presentationGaps.has(agentId));\n if (!presentationTelemetry(item.kind) && next.at(-1) !== rows.at(-1)) this.presentationGaps.delete(agentId);\n if (agentId !== root.root_id) root.agent_presentations = { ...root.agent_presentations, [agentId]: next };\n else root.presentation = next;\n } else if (event.kind.startsWith('session.') && event.kind.endsWith('.updated')) {\n root.meta = {\n ...root.meta,\n ...(typeof payload.title === 'string' ? { title: payload.title } : {}),\n ...(typeof payload.model === 'string' ? { model: payload.model } : {}),\n ...(typeof payload.provider === 'string' ? { provider: payload.provider } : {}),\n ...(payload.effort_changed && typeof payload.effort === 'string' ? { effort: payload.effort } : {}),\n ...(typeof payload.working_directory === 'string' ? { cwd: payload.working_directory } : {}),\n ...(typeof payload.archived === 'boolean' ? { archived: payload.archived } : {}),\n };\n // permission_mode is a top-level RootSnapshot field, not session meta.\n if (typeof payload.permission_mode === 'string') root = { ...root, permission_mode: payload.permission_mode };\n } else {\n const lifecycle = payload as LifecycleEvent;\n if (event.kind === 'schedule.fired' && root.upcoming_schedules) {\n // Both slots use the host's canonical UTC timestamp, preserving nanoseconds.\n // Match occurrences, not IDs: a delayed fire must leave a newer slot.\n root = { ...root,\n upcoming_schedules: root.upcoming_schedules.filter(item => item.id !== lifecycle.schedule_id || item.next_fire !== lifecycle.slot),\n // A recurring or omitted occurrence may already have a new pending slot.\n upcoming_schedule_count: undefined, omitted: { ...root.omitted, upcoming_schedules: true } };\n }\n if (/^(agent\\.)?turn\\./.test(event.kind)) {\n const agentId = lifecycle.agent_id || root.root_id;\n root.active_turns = { ...root.active_turns };\n if (event.kind.endsWith('.started') && lifecycle.turn_id) {\n this.presentationGaps.delete(agentId);\n root.active_turns[agentId] = lifecycle.turn_id;\n if (agentId === root.root_id) root.presentation = [];\n else root.agent_presentations = { ...root.agent_presentations, [agentId]: [] };\n } else if (root.active_turns[agentId] === lifecycle.turn_id) delete root.active_turns[agentId];\n }\n if (lifecycle.agent_id && lifecycle.inbox_seq && ['inbox.removed', 'command.cancelled', 'inbox.running', 'turn.started', 'agent.turn.started'].includes(event.kind)) {\n const removed = event.kind === 'inbox.removed' || event.kind === 'command.cancelled';\n const update = (item: NonNullable[number]) => item.agent_id === lifecycle.agent_id && item.seq === lifecycle.inbox_seq\n ? { ...item, status: removed ? 'cancelled' : 'running', delivery_seq: event.kind === 'inbox.running' ? event.seq : undefined, steer_turn_id: '' } : item;\n const known = [...(this.current.unverifiedInbox ?? []), ...(this.current.collections.inbox?.items?.flatMap(entry => entry.inbox ? [entry.inbox] : []) ?? [])]\n .find(item => item.agent_id === lifecycle.agent_id && item.seq === lifecycle.inbox_seq);\n const inbox = root.inbox ?? [];\n root = { ...root, inbox: [...inbox, ...(!removed && known && !inbox.some(item => item.agent_id === known.agent_id && item.seq === known.seq) ? [known] : [])]\n .map(update).filter(item => item.status !== 'cancelled') };\n this.current = { ...this.current, unverifiedInbox: this.current.unverifiedInbox?.filter(item => item.agent_id !== lifecycle.agent_id || item.seq !== lifecycle.inbox_seq) };\n const page = this.current.collections.inbox;\n if (page) this.current = { ...this.current, collections: { ...this.current.collections, inbox: { ...page, items: page.items?.map(entry => entry.inbox ? { inbox: update(entry.inbox) } : entry).filter(entry => entry.inbox?.status !== 'cancelled') ?? null } } };\n }\n if (event.kind === 'inbox.running') this.presentationGaps.add(lifecycle.agent_id || root.root_id);\n if (event.kind === 'question.pending') {\n root.questions = [...(root.questions ?? []).filter(item => item.question_id !== lifecycle.question_id), lifecycle];\n } else if (event.kind === 'question.answered' || event.kind === 'question.closed') {\n root.questions = (root.questions ?? []).filter(item => item.question_id !== lifecycle.question_id);\n }\n // Lifecycle events intentionally carry partial records. A coalesced snapshot\n // refreshes collections and committed history instead of inventing missing fields.\n if (!event.unknown) this.scheduleRefresh();\n }\n // Hidden tabs can throttle notification timers indefinitely. Bound incoming\n // growth independently, without reserializing the cached history per delta.\n this.pendingBytes += bytes(event);\n this.set({ ...this.current, root, executions, unavailable, trace }, this.published.retainedBytes + this.pendingBytes > this.maxBytes);\n }\n\n /**\n * Page the root's durable spans into the view. The first call reads from the\n * beginning; later calls continue from the page cursor, so a reconnect that\n * missed journal events catches up without discarding what live events built.\n */\n async loadTrace(): Promise {\n if (this.lifetime.signal.aborted) throw new Error('Session view is closed');\n if (this.incompatibleRuntime) throw new WhipError('runtime_changed', 'Execution runtime changed; open a new session view');\n if (this.traceRequest) return this.traceRequest.promise;\n const controller = new AbortController();\n // Trace pages are root-scoped, not snapshot/history-revision-scoped. A\n // concurrent root refresh must not discard their result or strand loading.\n const promise = Promise.resolve().then(() => this.fetchTrace(controller.signal));\n const request = { controller, promise };\n this.traceRequest = request;\n try { await promise; }\n finally { if (this.traceRequest === request) this.traceRequest = undefined; }\n }\n\n private cancelTraceRead(): void {\n this.traceRequest?.controller.abort();\n this.traceRequest = undefined;\n if (this.current.trace?.loading) this.current = { ...this.current, trace: { ...this.current.trace, loading: false } };\n }\n\n private async fetchTrace(signal: AbortSignal): Promise {\n if (signal.aborted) return;\n const rootId = this.session.rootId;\n let evidence = this.current.trace ?? emptyTraceEvidence(rootId);\n this.set({ ...this.current, trace: { ...evidence, loading: true, error: undefined } }, true);\n try {\n for (let pages = 0; pages < 8; pages++) {\n const page = await this.session.client.call('trace.page', { root_id: rootId, after_seq: evidence.pageCursor, limit: 2048 }, { signal });\n if (signal.aborted) return;\n if (page.root_id !== rootId) throw new Error('Trace page belongs to a different root');\n if (page.has_more && BigInt(page.next_seq) <= BigInt(evidence.pageCursor)) throw new Error('Trace page cursor did not advance');\n evidence = mergeSpanPage(this.current.trace ?? evidence, page);\n this.set({ ...this.current, trace: { ...evidence, loading: true } }, true);\n if (!page.has_more) break;\n }\n } catch (error) {\n if (signal.aborted) return;\n this.set({ ...this.current, trace: { ...(this.current.trace ?? evidence), error: asError(error) } }, true);\n throw error;\n } finally {\n if (!signal.aborted) this.set({ ...this.current, trace: { ...(this.current.trace ?? evidence), loading: false } }, true);\n }\n }\n\n private async repairHistory(): Promise {\n if (this.repair?.epoch === this.epoch) return this.repair.promise;\n if (this.lifetime.signal.aborted || this.current.status !== 'live') return;\n const epoch = this.epoch;\n const promise = (async () => {\n for (let pages = 0; pages < 4 && epoch === this.epoch; pages++) {\n const target = [...this.opened].map(agentId => ({ agentId, history: this.current.history[agentId] }))\n .find(({ agentId, history }) => {\n if (!history) return false;\n const omittedSnapshot = agentId === this.session.rootId && !this.current.root?.messages?.length && this.current.root?.omitted?.messages;\n const missingRecent = (!history.messages.length && history.hasMore) || omittedSnapshot;\n return history.gaps?.some(gap => gap.status === 'pending') || (missingRecent && !history.error\n && this.recentRecoveryCursor.get(agentId) !== this.current.root?.cursor);\n });\n if (!target) break;\n const { agentId, history } = target;\n const gap = history!.gaps?.find(gap => gap.status === 'pending');\n if (!gap) this.recentRecoveryCursor.set(agentId, this.current.root?.cursor ?? '');\n try { await this.readHistory(agentId, false, gap && { toSeq: gap.toSeq, automatic: true }); }\n catch { /* The scoped history/gap error supplies explicit retry. */ }\n }\n if (epoch !== this.epoch) return;\n // A publication or ordinary refresh must not restart an exhausted pass.\n for (const [agentId, history] of Object.entries(this.current.history))\n for (const gap of history.gaps ?? []) if (gap.status === 'pending') this.setGap(agentId, gap.toSeq, { status: 'paused' });\n })();\n const repair = { epoch, promise };\n this.repair = repair;\n try { await promise; } finally { if (this.repair === repair) this.repair = undefined; }\n if (epoch === this.epoch) void this.warmInitialHistory();\n }\n\n private async warmInitialHistory(): Promise {\n if (!this.initialHistoryWarmup || this.current.status !== 'live') return;\n const epoch = this.epoch;\n const agentId = this.session.rootId;\n // Let a user read or recent recovery finish before deciding whether to extend.\n const existing = this.historyRequests.get(agentId);\n if (existing) await existing.promise.catch(() => {});\n if (epoch !== this.epoch || this.lifetime.signal.aborted || this.current.status !== 'live' || !this.initialHistoryWarmup) return;\n this.initialHistoryWarmup = false;\n const history = this.current.history[agentId];\n if (!history?.messages.length || !history.hasMore || history.error || history.latestMissing || history.gaps?.length) return;\n await this.readHistory(agentId, true).catch(() => { /* History retains the explicit retry. */ });\n }\n\n private setGap(agentId: string, toSeq: number, patch: Partial): void {\n const history = this.current.history[agentId];\n if (!history?.gaps?.some(gap => gap.toSeq === toSeq)) return;\n this.set({ ...this.current, history: { ...this.current.history, [agentId]: {\n ...history, gaps: history.gaps.map(gap => gap.toSeq === toSeq ? { ...gap, ...patch } : gap),\n } } }, true);\n }\n\n private async readHistory(agentId: string, older: boolean, repair?: { toSeq: number; automatic: boolean }): Promise {\n const key = repair ? `gap:${repair.toSeq}` : older ? 'older' : 'recent';\n const existing = this.historyRequests.get(agentId);\n if (existing) {\n if (existing.epoch === this.epoch && existing.key === key) return existing.promise;\n await existing.promise.catch(() => {});\n if (this.historyRequests.get(agentId) === existing) this.historyRequests.delete(agentId);\n if (this.lifetime.signal.aborted || !this.opened.has(agentId)) return;\n return this.readHistory(agentId, older, repair);\n }\n if (this.lifetime.signal.aborted || this.session.client.getSnapshot().state !== 'connected') return;\n // An explicit older read also spends the shared view's one warm-up page.\n if (older && agentId === this.session.rootId) this.initialHistoryWarmup = false;\n const controller = new AbortController();\n const request = { epoch: this.epoch, key, controller, promise: this.fetchHistory(agentId, older, controller.signal, repair) };\n this.historyRequests.set(agentId, request);\n try { await request.promise; }\n finally { if (this.historyRequests.get(agentId) === request) this.historyRequests.delete(agentId); }\n }\n\n private async fetchHistory(agentId: string, older: boolean, signal: AbortSignal, repair?: { toSeq: number; automatic: boolean }): Promise {\n const epoch = this.epoch;\n const prior = this.current.history[agentId];\n const gap = repair && prior?.gaps?.find(gap => gap.toSeq === repair.toSeq);\n if (repair && !gap) return;\n const placeholder: HistoryView = prior ?? {\n revision: this.current.root?.history_revision ?? '', throughSeq: 0, nextSeq: 0,\n hasMore: true, messages: [], truncated: false, loading: true,\n };\n if (gap) this.setGap(agentId, gap.toSeq, { status: 'loading', error: undefined });\n else this.set({ ...this.current, history: { ...this.current.history, [agentId]: { ...placeholder, loading: true, error: undefined } } }, true);\n const currentRequest = () => epoch === this.epoch && this.opened.has(agentId) && !signal.aborted && !this.lifetime.signal.aborted;\n try {\n const page = await this.session.client.call('history.page', {\n root_id: this.session.rootId, agent_id: agentId,\n ...(gap ? { after_seq: gap.fromSeq - 1, through_seq: gap.toSeq, revision: placeholder.revision }\n : { recent: true, through_seq: older && prior ? prior.throughSeq : -1,\n ...(older && prior ? { before_seq: prior.nextSeq, revision: prior.revision } : {}) }),\n limit: 128, max_bytes: 256 * 1024,\n }, { signal });\n if (!currentRequest()) return;\n if (older && prior && page.has_more && page.next_seq >= prior.nextSeq && prior.nextSeq > 0)\n throw new WhipError('invalid_response', 'History cursor did not advance');\n if (this.current.root && page.history_revision !== this.current.root.history_revision)\n throw new WhipError('resynchronization_required', 'History revision changed');\n if (gap && (page.history_revision !== placeholder.revision || page.through_seq !== gap.toSeq\n || !page.messages?.length || page.messages.some((item, index) => item.seq !== gap.fromSeq + index || item.seq > gap.toSeq)\n || page.next_seq !== page.messages.at(-1)!.seq || (!page.has_more && page.next_seq < gap.toSeq)))\n throw new WhipError('invalid_response', 'Missing history page did not advance within its requested range');\n const retained = this.current.history[agentId];\n // Budget eviction can remove a child's window while its read is in flight.\n // Keep the known end, but never resurrect its evicted message bodies.\n const current = retained ?? prior;\n const sameRevision = current?.revision === page.history_revision;\n const all = mergeMessages(sameRevision ? retained?.messages ?? [] : [], page.messages ?? []);\n // Explicit reads retain the page the person requested. Automatic repair\n // favors the newest window, just like an ordinary metadata refresh.\n const start = gap && !repair?.automatic\n ? Math.max(0, all.findIndex(item => item.seq === page.messages![0]!.seq) - Math.floor((this.maxMessages - Math.min(this.maxMessages, page.messages!.length)) / 2)) : 0;\n const messages = gap && !repair?.automatic ? all.slice(start, start + this.maxMessages)\n : older ? all.slice(0, this.maxMessages) : all.slice(-this.maxMessages);\n const nextSeq = messages[0]?.seq ?? page.next_seq;\n const hasMore = sameRevision && current.nextSeq === nextSeq && (gap || nextSeq < page.next_seq)\n ? current.hasMore : gap && sameRevision ? nextSeq > current.nextSeq || current.hasMore : nextSeq > page.next_seq || page.has_more;\n const history = historyCoverage({\n revision: page.history_revision, throughSeq: Math.max(sameRevision ? current.throughSeq : 0, page.through_seq),\n nextSeq, hasMore, loading: false, messages,\n gaps: sameRevision ? current.gaps?.map(item => gap && item.toSeq === gap.toSeq\n ? { ...item, status: repair?.automatic ? 'pending' as const : 'paused' as const, error: undefined } : item) : undefined,\n truncated: all.length > messages.length || messages.some(item => !!item.body),\n });\n const histories = { ...this.current.history, [agentId]: history };\n this.set({ ...this.current, history: histories, executions: this.current.executions && reconcileExecutions(this.current.executions, histories) }, true);\n const remaining = this.current.history[agentId]?.gaps?.find(item => item.toSeq === gap?.toSeq);\n if (remaining && remaining.fromSeq === gap?.fromSeq) this.setGap(agentId, remaining.toSeq, { status: 'paused' });\n } catch (error) {\n if (!currentRequest()) return;\n if (errorKind(error) === 'resynchronization_required') {\n const history = { ...this.current.history };\n delete history[agentId];\n this.set({ ...this.current, history }, true);\n this.scheduleRefresh();\n } else if (gap) this.setGap(agentId, gap.toSeq, { status: 'error', error: asError(error).message.slice(0, 512) });\n else this.set({ ...this.current, history: { ...this.current.history,\n [agentId]: { ...(this.current.history[agentId] ?? placeholder), loading: false, error: asError(error) } }, error: asError(error) }, true);\n throw error;\n }\n }\n\n private set(next: SessionViewSnapshot, immediate = false): void {\n this.current = next;\n if (immediate) { clearTimeout(this.noticeTimer); this.noticeTimer = undefined; this.publish(); }\n else if (!this.noticeTimer) this.noticeTimer = setTimeout(() => { this.noticeTimer = undefined; this.publish(); }, this.notificationInterval);\n }\n\n private publish(): void {\n const before = this.current;\n this.current = bound(before, this.maxBytes, this.maxMessages);\n for (const id of Object.keys(before.history)) {\n if (id !== this.session.rootId && !this.current.history[id]) this.opened.delete(id);\n }\n this.published = freeze(this.current);\n this.pendingBytes = 0;\n for (const listener of this.listeners) listener();\n }\n}\n\n/** Raw sequence numbers are contiguous per agent/revision. Bodies may be handles. */\nfunction historyCoverage(history: HistoryView): HistoryView {\n const previous = new Map(history.gaps?.map(gap => [gap.toSeq, gap]));\n const gaps: HistoryGap[] = [];\n for (let index = 1; index < history.messages.length; index++) {\n const fromSeq = history.messages[index - 1]!.seq + 1, toSeq = history.messages[index]!.seq - 1;\n if (fromSeq <= toSeq) gaps.push({ ...previous.get(toSeq), fromSeq, toSeq, status: previous.get(toSeq)?.status ?? 'pending' });\n }\n return { ...history, gaps, latestMissing: !!history.latestMissing || (!!history.messages.length && history.messages.at(-1)!.seq < history.throughSeq) };\n}\n\nfunction mergeMessages(left: Message[], right: Message[]): Message[] {\n return [...new Map([...left, ...right].map(message => [message.seq, message])).values()].sort((a, b) => a.seq - b.seq);\n}\n\nfunction presentationTelemetry(kind: string): boolean {\n return kind === 'stream.usage' || kind === 'stream.cell.host' || kind === 'stream.cell.host.started';\n}\n\nfunction appendPresentation(previous: Presentation[], item: Presentation, continuous = true): Presentation[] {\n if (item.kind === 'stream.accounting') return previous;\n const payload = (item.payload ?? {}) as StreamEvent & { truncated?: boolean; content?: unknown };\n // Legacy content-only events have no owner or call identity. Never attribute\n // them to the root or turn cumulative deltas into anonymous tool rows.\n if (payload.truncated && payload.content && !payload.agent_id) return previous;\n const cumulative = ['stream.tool.call', 'stream.tool.output'].includes(item.kind);\n let index = cumulative && payload.id ? previous.findIndex(row => {\n const value = (row.payload ?? {}) as StreamEvent;\n return row.kind === item.kind && value.id === payload.id && value.agent_id === payload.agent_id && value.turn_id === payload.turn_id && value.part_id === payload.part_id;\n }) : previous.length - 1;\n // These events update existing evidence; they do not split a prose/thought\n // part. Only join proven continuous text, never across notices or missing data.\n if (continuous && payload.part_id && (item.kind === 'stream.text' || item.kind === 'stream.reasoning')) {\n while (index >= 0 && presentationTelemetry(previous[index]!.kind)) index--;\n }\n const last = previous[index];\n const lastPayload = (last?.payload ?? {}) as StreamEvent;\n if (last?.kind === item.kind && payload.id === lastPayload.id && payload.agent_id === lastPayload.agent_id && payload.turn_id === lastPayload.turn_id && payload.part_id === lastPayload.part_id) {\n let next: Presentation | undefined;\n // Tool arguments/output are full values so far, even when calls interleave.\n if (cumulative && payload.id) next = { ...item, seq: last.seq };\n else if (continuous && ['stream.text', 'stream.reasoning', 'stream.terminal.output'].includes(item.kind)\n && typeof payload.text === 'string' && typeof lastPayload.text === 'string') {\n next = { ...last, payload: { ...payload, text: lastPayload.text + payload.text } };\n }\n // Keep the first event's sequence as the row key; root.cursor tracks progress.\n if (next) return [...previous.slice(0, index), next, ...previous.slice(index + 1)];\n }\n return [...previous, item];\n}\n\nfunction bound(state: SessionViewSnapshot, maxBytes: number, maxMessages: number): SessionViewSnapshot {\n let next = { ...state, history: { ...state.history }, collections: { ...state.collections } };\n for (const [id, history] of Object.entries(next.history)) {\n if (history.messages.length > maxMessages) {\n const messages = history.messages.slice(-maxMessages);\n next.history[id] = historyCoverage({ ...history, messages, nextSeq: messages[0]!.seq, hasMore: true, truncated: true });\n next.truncated = true;\n }\n }\n const size = (): number => bytes({ root: next.root, history: next.history, collections: next.collections, unverifiedInbox: next.unverifiedInbox, executions: next.executions });\n if (next.executions?.truncated) next.truncated = true;\n let total = size();\n if (total > maxBytes) {\n next.truncated = true;\n next.collections = {};\n next.unverifiedInbox = [];\n if (next.executions) {\n const otherBytes = bytes({ root: next.root, history: next.history, collections: next.collections });\n next.executions = boundExecutionEvidence(next.executions, Math.max(0, maxBytes - otherBytes - 32));\n }\n total = size();\n const children = Object.keys(next.history).filter(id => id !== next.root?.root_id)\n .sort((a, b) => Number(!!next.root?.active_turns[a]) - Number(!!next.root?.active_turns[b]));\n for (const id of children) {\n if (total <= maxBytes) break;\n delete next.history[id];\n total = size();\n }\n const rootHistory = next.root && next.history[next.root.root_id];\n if (total > maxBytes && next.root && rootHistory) {\n next.history[next.root.root_id] = historyCoverage({ ...rootHistory, messages: [], truncated: true, hasMore: true, nextSeq: 0 });\n total = size();\n }\n }\n if (total > maxBytes && next.root) {\n next.root = { ...next.root, messages: [], message_seqs: [], presentation: [], agent_presentations: {}, omitted: { ...next.root.omitted, messages: true, presentation: true } };\n next.unavailable = true;\n total = size();\n }\n if (total > maxBytes) {\n // A configured budget may be smaller than a single metadata record. Expose\n // that limitation explicitly rather than violating the bound indefinitely.\n next.root = undefined;\n next.executions = undefined;\n next.history = {};\n next.collections = {};\n next.unavailable = true;\n next.error = new Error('Session metadata exceeds the view byte budget');\n total = size();\n }\n return { ...next, retainedBytes: total };\n}\n\nexport function createSessionView(session: Session, options?: SessionViewOptions): SessionView {\n return new SessionView(session, options);\n}\n\nexport interface SessionListSnapshot {\n status: Status;\n page?: SessionCatalogPage;\n error?: Error;\n truncated: boolean;\n}\n\n/** Catalog polling is active only while observed; it never opens root subscriptions. */\nexport class SessionListView {\n private current: SessionListSnapshot = Object.freeze({ status: 'idle', truncated: false });\n private readonly listeners = new Set<() => void>();\n private readonly lifetime = new AbortController();\n private timer?: ReturnType;\n private stopConnection?: () => void;\n private stopCommands?: () => void;\n private pending?: Promise;\n private started = false;\n private epoch = 0;\n private refreshAgain = false;\n private runtimeID?: string;\n private readonly interval: number;\n private readonly maxBytes: number;\n\n constructor(readonly client: WhipClient, options: { pollIntervalMs?: number; maxBytes?: number } = {}) {\n this.interval = positive(options.pollIntervalMs ?? 2000, 'pollIntervalMs');\n this.maxBytes = positive(options.maxBytes ?? 2 * 1024 * 1024, 'maxBytes');\n }\n\n getSnapshot = (): DeepReadonly => this.current;\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n if (this.listeners.size === 1 && this.started) { void this.refresh(); this.pollLater(); }\n return () => {\n this.listeners.delete(listener);\n if (!this.listeners.size) { clearTimeout(this.timer); this.timer = undefined; }\n };\n };\n\n async start(): Promise {\n if (this.lifetime.signal.aborted) throw new Error('Session list view is closed');\n if (this.started) return this.pending;\n this.started = true;\n this.stopConnection = this.client.subscribe(() => {\n this.epoch++;\n if (this.client.getSnapshot().state === 'connected') {\n if (this.runtimeID && this.runtimeID !== this.client.getSnapshot().info?.runtime_id) {\n this.set({ status: 'loading', truncated: false });\n }\n void this.refresh();\n this.pollLater();\n } else {\n clearTimeout(this.timer);\n this.timer = undefined;\n this.set({ ...this.current, status: 'stale' });\n }\n });\n this.stopCommands = this.client.onCommand(terminalChanges(() => {\n if (this.listeners.size) void this.refresh();\n }));\n await this.refresh();\n this.pollLater();\n }\n\n async refresh(): Promise {\n if (!this.started || this.lifetime.signal.aborted || this.client.getSnapshot().state !== 'connected') return;\n if (this.pending) { this.refreshAgain = true; return this.pending; }\n this.pending = this.fetch(false);\n try { await this.pending; } finally {\n this.pending = undefined;\n if (this.refreshAgain) { this.refreshAgain = false; void this.refresh(); }\n }\n }\n\n async loadMore(): Promise {\n if (this.pending) return this.pending;\n if (!this.current.page?.has_more) return;\n if (!this.current.page.next_cursor) throw new WhipError('invalid_response', 'Session list is missing its continuation cursor');\n this.pending = this.fetch(true);\n try { await this.pending; } finally { this.pending = undefined; }\n }\n\n async dispose(): Promise {\n if (this.lifetime.signal.aborted) return;\n this.lifetime.abort();\n this.epoch++;\n clearTimeout(this.timer);\n this.stopConnection?.();\n this.stopCommands?.();\n this.set({ ...this.current, status: 'closed' });\n this.listeners.clear();\n }\n\n private async fetch(more: boolean): Promise {\n const epoch = this.epoch;\n const previous = this.current.page;\n this.set({ ...this.current, status: previous ? 'stale' : 'loading' });\n try {\n if (previous && !more) {\n const revision = await this.client.call('sessions.revision', {}, { signal: this.lifetime.signal });\n if (epoch !== this.epoch || this.lifetime.signal.aborted) return;\n if (revision.revision === previous.revision) { this.set({ ...this.current, status: 'live', error: undefined }); return; }\n }\n const page = await this.client.call('sessions.list', {\n ...(more && previous?.next_cursor ? { cursor: previous.next_cursor } : {}), status: 'active', limit: 128, max_bytes: 256 * 1024,\n }, { signal: this.lifetime.signal });\n if (epoch !== this.epoch || this.lifetime.signal.aborted) return;\n if (more && previous && previous.revision !== page.revision) throw new WhipError('resynchronization_required', 'Session list revision changed');\n this.runtimeID = this.client.getSnapshot().info?.runtime_id;\n const merged = { ...page, items: more ? [...(previous?.items ?? []), ...(page.items ?? [])] : page.items };\n if (bytes(merged) > this.maxBytes) {\n this.set({ ...this.current, status: 'live', truncated: true, error: new Error('Session list cache is full; refresh to start a new page window') });\n } else this.set({ status: 'live', page: merged, truncated: false });\n } catch (error) {\n if (epoch !== this.epoch || this.lifetime.signal.aborted) return;\n this.set({ ...this.current, status: previous ? 'stale' : 'error', error: asError(error) });\n if (more && errorKind(error) === 'resynchronization_required') {\n this.set({ status: 'stale', truncated: false });\n await this.fetch(false);\n }\n }\n }\n\n private pollLater(): void {\n if (this.lifetime.signal.aborted || !this.started || !this.listeners.size || this.timer || this.client.getSnapshot().state !== 'connected') return;\n this.timer = setTimeout(async () => {\n this.timer = undefined;\n await this.refresh();\n this.pollLater();\n }, this.interval);\n }\n\n private set(value: SessionListSnapshot): void {\n this.current = freeze(value) as SessionListSnapshot;\n for (const listener of this.listeners) listener();\n }\n}\n\nexport function createSessionListView(client: WhipClient, options?: { pollIntervalMs?: number; maxBytes?: number }): SessionListView {\n return new SessionListView(client, options);\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/subscription.d.ts b/vendor/@whip/sdk/dist/subscription.d.ts new file mode 100644 index 0000000..9be3dfa --- /dev/null +++ b/vendor/@whip/sdk/dist/subscription.d.ts @@ -0,0 +1,30 @@ +import type { CallOptions, SdkEvent, WhipClient } from './client.js'; +export interface SubscriptionOptions extends Pick { + maxMessages?: number; + maxBytes?: number; +} +/** One ordered, bounded consumer. Fan out UI components through a SessionView. */ +export declare class Subscription implements AsyncIterableIterator { + private readonly client; + readonly rootId: string; + cursor: string; + private readonly options; + readonly id: string; + private queue; + private bytes; + private waiter?; + private error?; + private closed; + private lastReceived; + private abortListener?; + constructor(client: WhipClient, rootId: string, cursor: string, options: SubscriptionOptions); + start(signal?: AbortSignal): Promise; + push(event: SdkEvent): void; + next(): Promise>; + [Symbol.asyncIterator](): AsyncIterableIterator; + return(): Promise>; + fail(error: Error): void; + private finish; + private unsubscribe; + dispose(): Promise; +} diff --git a/vendor/@whip/sdk/dist/subscription.js b/vendor/@whip/sdk/dist/subscription.js new file mode 100644 index 0000000..3792983 --- /dev/null +++ b/vendor/@whip/sdk/dist/subscription.js @@ -0,0 +1,143 @@ +import { assertValid } from '@whip/protocol'; +import { asError, WhipError, abortError } from './errors.js'; +import { byteLength, withSignal } from './util.js'; +/** One ordered, bounded consumer. Fan out UI components through a SessionView. */ +export class Subscription { + client; + rootId; + cursor; + options; + id; + queue = []; + bytes = 0; + waiter; + error; + closed = false; + lastReceived; + abortListener; + constructor(client, rootId, cursor, options) { + this.client = client; + this.rootId = rootId; + this.cursor = cursor; + this.options = options; + this.id = client.createId(); + assertValid('SubscribeParams', { root_id: rootId, subscription_id: this.id, cursor }); + this.lastReceived = BigInt(cursor); + if (cursor.startsWith('-')) + throw new TypeError('Subscription cursor cannot be negative'); + for (const value of [options.maxMessages, options.maxBytes]) + if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) + throw new TypeError('Subscription limits must be positive integers'); + } + async start(signal) { + signal?.throwIfAborted(); + // Don't cancel admission itself: unsubscribe must happen after registration. + const admission = this.client.call('events.subscribe', { root_id: this.rootId, subscription_id: this.id, cursor: this.cursor }); + let result; + try { + result = await withSignal(admission, signal); + } + catch (error) { + void admission.then(() => this.unsubscribe(), failure => { + if (failure instanceof WhipError && ['timeout', 'invalid_response'].includes(failure.kind) && this.client.getSnapshot().state === 'connected') + this.client.reconnect(); + }).catch(() => { }); + throw error; + } + if (result.subscription_id !== this.id) + throw new WhipError('invalid_response', 'Subscription acknowledgement changed identity'); + if (signal) { + this.abortListener = () => { this.fail(abortError(signal)); }; + signal.addEventListener('abort', this.abortListener, { once: true }); + if (signal.aborted) + this.abortListener(); + } + if (this.error) + throw this.error; + } + push(event) { + if (this.closed || event.subscription_id !== this.id) + return; + if (event.root_id !== this.rootId) { + this.fail(new WhipError('invalid_response', 'Subscription root changed')); + return; + } + const seq = BigInt(event.seq); + if (seq <= this.lastReceived) + return; + if (seq !== this.lastReceived + 1n) { + this.fail(new WhipError('resynchronization_required', 'Event sequence gap; replay or obtain a fresh snapshot')); + return; + } + this.lastReceived = seq; + if (this.waiter) { + const waiter = this.waiter; + this.waiter = undefined; + this.cursor = event.seq; + waiter.resolve({ value: event, done: false }); + return; + } + const bytes = byteLength(JSON.stringify(event)); + if (this.queue.length >= (this.options.maxMessages ?? 1024) || this.bytes + bytes > (this.options.maxBytes ?? 8 << 20)) { + this.fail(new WhipError('resynchronization_required', 'Event consumer exceeded its buffer; obtain a fresh snapshot')); + return; + } + this.queue.push({ event, bytes }); + this.bytes += bytes; + } + next() { + if (this.error) + return Promise.reject(this.error); + const next = this.queue.shift(); + if (next) { + this.bytes -= next.bytes; + this.cursor = next.event.seq; + return Promise.resolve({ done: false, value: next.event }); + } + if (this.closed) + return Promise.resolve({ done: true, value: undefined }); + if (this.waiter) + return Promise.reject(new WhipError('invalid_arguments', 'Subscription supports one iterator consumer')); + return new Promise((resolve, reject) => { this.waiter = { resolve, reject }; }); + } + [Symbol.asyncIterator]() { return this; } + async return() { await this.dispose(); return { done: true, value: undefined }; } + fail(error) { + if (this.closed) + return; + this.error = error; + this.finish(); + // The connection may still be healthy after an individual stream expires. + void this.unsubscribe().catch(() => { }); + } + finish() { + this.closed = true; + this.queue = []; + this.bytes = 0; + this.client.releaseSubscription(this.id); + if (this.abortListener) + this.options.signal?.removeEventListener('abort', this.abortListener); + if (this.error) + this.waiter?.reject(this.error); + else + this.waiter?.resolve({ done: true, value: undefined }); + this.waiter = undefined; + } + async unsubscribe() { + if (this.client.getSnapshot().state === 'connected') + await this.client.call('events.unsubscribe', { subscription_id: this.id }); + } + async dispose() { + if (this.closed) + return; + this.finish(); + try { + await this.unsubscribe(); + } + catch (error) { + if (this.client.getSnapshot().state === 'connected') + throw asError(error); + } + } +} +//# sourceMappingURL=subscription.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/subscription.js.map b/vendor/@whip/sdk/dist/subscription.js.map new file mode 100644 index 0000000..dc6e1d2 --- /dev/null +++ b/vendor/@whip/sdk/dist/subscription.js.map @@ -0,0 +1 @@ +{"version":3,"file":"subscription.js","sourceRoot":"","sources":["../src/subscription.ts"],"names":[],"mappings":"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","sourcesContent":["import { assertValid } from '@whip/protocol';\nimport type { CallOptions, SdkEvent, WhipClient } from './client.js';\nimport { asError, WhipError, abortError } from './errors.js';\nimport { byteLength, withSignal } from './util.js';\n\nexport interface SubscriptionOptions extends Pick { maxMessages?: number; maxBytes?: number }\n\n/** One ordered, bounded consumer. Fan out UI components through a SessionView. */\nexport class Subscription implements AsyncIterableIterator {\n readonly id: string;\n private queue: { event: SdkEvent; bytes: number }[] = [];\n private bytes = 0;\n private waiter?: { resolve(value: IteratorResult): void; reject(error: Error): void };\n private error?: Error;\n private closed = false;\n private lastReceived: bigint;\n private abortListener?: () => void;\n constructor(private readonly client: WhipClient, readonly rootId: string, public cursor: string, private readonly options: SubscriptionOptions) {\n this.id = client.createId();\n assertValid('SubscribeParams', { root_id: rootId, subscription_id: this.id, cursor });\n this.lastReceived = BigInt(cursor);\n if (cursor.startsWith('-')) throw new TypeError('Subscription cursor cannot be negative');\n for (const value of [options.maxMessages, options.maxBytes]) if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) throw new TypeError('Subscription limits must be positive integers');\n }\n async start(signal?: AbortSignal): Promise {\n signal?.throwIfAborted();\n // Don't cancel admission itself: unsubscribe must happen after registration.\n const admission = this.client.call('events.subscribe', { root_id: this.rootId, subscription_id: this.id, cursor: this.cursor });\n let result;\n try { result = await withSignal(admission, signal); }\n catch (error) {\n void admission.then(() => this.unsubscribe(), failure => {\n if (failure instanceof WhipError && ['timeout', 'invalid_response'].includes(failure.kind) && this.client.getSnapshot().state === 'connected') this.client.reconnect();\n }).catch(() => {});\n throw error;\n }\n if (result.subscription_id !== this.id) throw new WhipError('invalid_response', 'Subscription acknowledgement changed identity');\n if (signal) {\n this.abortListener = () => { this.fail(abortError(signal)); };\n signal.addEventListener('abort', this.abortListener, { once: true });\n if (signal.aborted) this.abortListener();\n }\n if (this.error) throw this.error;\n }\n push(event: SdkEvent): void {\n if (this.closed || event.subscription_id !== this.id) return;\n if (event.root_id !== this.rootId) { this.fail(new WhipError('invalid_response', 'Subscription root changed')); return; }\n const seq = BigInt(event.seq);\n if (seq <= this.lastReceived) return;\n if (seq !== this.lastReceived + 1n) { this.fail(new WhipError('resynchronization_required', 'Event sequence gap; replay or obtain a fresh snapshot')); return; }\n this.lastReceived = seq;\n if (this.waiter) {\n const waiter = this.waiter; this.waiter = undefined; this.cursor = event.seq;\n waiter.resolve({ value: event, done: false }); return;\n }\n const bytes = byteLength(JSON.stringify(event));\n if (this.queue.length >= (this.options.maxMessages ?? 1024) || this.bytes + bytes > (this.options.maxBytes ?? 8 << 20)) {\n this.fail(new WhipError('resynchronization_required', 'Event consumer exceeded its buffer; obtain a fresh snapshot')); return;\n }\n this.queue.push({ event, bytes }); this.bytes += bytes;\n }\n next(): Promise> {\n if (this.error) return Promise.reject(this.error);\n const next = this.queue.shift();\n if (next) { this.bytes -= next.bytes; this.cursor = next.event.seq; return Promise.resolve({ done: false, value: next.event }); }\n if (this.closed) return Promise.resolve({ done: true, value: undefined });\n if (this.waiter) return Promise.reject(new WhipError('invalid_arguments', 'Subscription supports one iterator consumer'));\n return new Promise((resolve, reject) => { this.waiter = { resolve, reject }; });\n }\n [Symbol.asyncIterator](): AsyncIterableIterator { return this; }\n async return(): Promise> { await this.dispose(); return { done: true, value: undefined }; }\n fail(error: Error): void {\n if (this.closed) return;\n this.error = error;\n this.finish();\n // The connection may still be healthy after an individual stream expires.\n void this.unsubscribe().catch(() => {});\n }\n private finish(): void {\n this.closed = true;\n this.queue = []; this.bytes = 0;\n this.client.releaseSubscription(this.id);\n if (this.abortListener) this.options.signal?.removeEventListener('abort', this.abortListener);\n if (this.error) this.waiter?.reject(this.error);\n else this.waiter?.resolve({ done: true, value: undefined });\n this.waiter = undefined;\n }\n private async unsubscribe(): Promise {\n if (this.client.getSnapshot().state === 'connected') await this.client.call('events.unsubscribe', { subscription_id: this.id });\n }\n async dispose(): Promise {\n if (this.closed) return;\n this.finish();\n try { await this.unsubscribe(); }\n catch (error) { if (this.client.getSnapshot().state === 'connected') throw asError(error); }\n }\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/terminals.d.ts b/vendor/@whip/sdk/dist/terminals.d.ts new file mode 100644 index 0000000..375c82d --- /dev/null +++ b/vendor/@whip/sdk/dist/terminals.d.ts @@ -0,0 +1,50 @@ +import type { TerminalOpenResult } from '@whip/protocol'; +import type { CallOptions, WhipClient } from './client.js'; +/** Keystroke batches match the daemon's per-write bound. */ +export declare const MAX_TERMINAL_WRITE_BYTES: number; +export interface TerminalAttachment { + /** Absolute cursor of the first replayed byte; live output follows from there. */ + cursor: number; + cwd: string; + cols: number; + rows: number; + exited: boolean; + exitCode?: number; + signal?: string; +} +export interface TerminalOutput { + id: string; + cursor: number; + bytes: Uint8Array; +} +export interface TerminalExit { + id: string; + exitCode: number; + signal?: string; +} +/** + * Workspace terminals: one login shell per tab, running where the daemon runs. + * Output reaches only the connection that attached last; the daemon replays + * retained output from the cursor an attach names, so a reload or reconnect + * resumes without loss. Closing ends the shell; disconnecting does not. + */ +export declare class Terminals { + private readonly client; + constructor(client: WhipClient); + /** Start a login shell. Omitted cwd resolves to the named session's directory, then the daemon home. */ + open(params: { + cwd?: string; + rootId?: string; + cols: number; + rows: number; + }, options?: CallOptions): Promise; + /** cursor -1 asks for live output only; a cursor older than the retained ring is clamped to its start. */ + attach(id: string, cursor: number, options?: CallOptions): Promise; + write(id: string, data: Uint8Array | string, options?: CallOptions): Promise; + resize(id: string, cols: number, rows: number, options?: CallOptions): Promise; + /** Ends the shell. Use it when the tab closes, never on unmount or disconnect. */ + close(id: string, options?: CallOptions): Promise; + onOutput(listener: (output: TerminalOutput) => void): () => void; + onExited(listener: (exit: TerminalExit) => void): () => void; + onDetached(listener: (id: string) => void): () => void; +} diff --git a/vendor/@whip/sdk/dist/terminals.js b/vendor/@whip/sdk/dist/terminals.js new file mode 100644 index 0000000..00ade5d --- /dev/null +++ b/vendor/@whip/sdk/dist/terminals.js @@ -0,0 +1,66 @@ +import { WhipError } from './errors.js'; +import { decodeBase64, encodeBase64 } from './util.js'; +/** Keystroke batches match the daemon's per-write bound. */ +export const MAX_TERMINAL_WRITE_BYTES = 16 << 10; +const encoder = new TextEncoder(); +const cursorOf = (value) => { + const cursor = Number(value); + if (!Number.isSafeInteger(cursor)) + throw new WhipError('invalid_response', 'Terminal cursor is not a safe integer'); + return cursor; +}; +/** + * Workspace terminals: one login shell per tab, running where the daemon runs. + * Output reaches only the connection that attached last; the daemon replays + * retained output from the cursor an attach names, so a reload or reconnect + * resumes without loss. Closing ends the shell; disconnecting does not. + */ +export class Terminals { + client; + constructor(client) { + this.client = client; + } + /** Start a login shell. Omitted cwd resolves to the named session's directory, then the daemon home. */ + open(params, options = {}) { + return this.client.call('terminal.open', { + ...(params.cwd ? { cwd: params.cwd } : {}), ...(params.rootId ? { root_id: params.rootId } : {}), + cols: params.cols, rows: params.rows, + }, options); + } + /** cursor -1 asks for live output only; a cursor older than the retained ring is clamped to its start. */ + async attach(id, cursor, options = {}) { + const result = await this.client.call('terminal.attach', { id, cursor: String(cursor) }, options); + return { + cursor: cursorOf(result.cursor), cwd: result.cwd, cols: result.cols, rows: result.rows, exited: result.exited, + ...(result.exit_code === undefined ? {} : { exitCode: result.exit_code }), + ...(result.signal ? { signal: result.signal } : {}), + }; + } + async write(id, data, options = {}) { + const bytes = typeof data === 'string' ? encoder.encode(data) : data; + if (!bytes.byteLength || bytes.byteLength > MAX_TERMINAL_WRITE_BYTES) + throw new WhipError('invalid_request', `Terminal writes must be 1..${MAX_TERMINAL_WRITE_BYTES} bytes`); + await this.client.call('terminal.write', { id, bytes: encodeBase64(bytes) }, options); + } + async resize(id, cols, rows, options = {}) { + await this.client.call('terminal.resize', { id, cols, rows }, options); + } + /** Ends the shell. Use it when the tab closes, never on unmount or disconnect. */ + async close(id, options = {}) { + await this.client.call('terminal.close', { id }, options); + } + onOutput(listener) { + return this.client.onNotification('terminal.output', params => { + listener({ id: params.id, cursor: cursorOf(params.cursor), bytes: decodeBase64(params.bytes ?? '') }); + }); + } + onExited(listener) { + return this.client.onNotification('terminal.exited', params => { + listener({ id: params.id, exitCode: params.exit_code, ...(params.signal ? { signal: params.signal } : {}) }); + }); + } + onDetached(listener) { + return this.client.onNotification('terminal.detached', params => listener(params.id)); + } +} +//# sourceMappingURL=terminals.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/terminals.js.map b/vendor/@whip/sdk/dist/terminals.js.map new file mode 100644 index 0000000..45a1852 --- /dev/null +++ b/vendor/@whip/sdk/dist/terminals.js.map @@ -0,0 +1 @@ +{"version":3,"file":"terminals.js","sourceRoot":"","sources":["../src/terminals.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TerminalOpenResult } from '@whip/protocol';\nimport type { CallOptions, WhipClient } from './client.js';\nimport { WhipError } from './errors.js';\nimport { decodeBase64, encodeBase64 } from './util.js';\n\n/** Keystroke batches match the daemon's per-write bound. */\nexport const MAX_TERMINAL_WRITE_BYTES = 16 << 10;\n\nexport interface TerminalAttachment {\n /** Absolute cursor of the first replayed byte; live output follows from there. */\n cursor: number;\n cwd: string;\n cols: number;\n rows: number;\n exited: boolean;\n exitCode?: number;\n signal?: string;\n}\nexport interface TerminalOutput { id: string; cursor: number; bytes: Uint8Array }\nexport interface TerminalExit { id: string; exitCode: number; signal?: string }\n\nconst encoder = new TextEncoder();\nconst cursorOf = (value: string): number => {\n const cursor = Number(value);\n if (!Number.isSafeInteger(cursor)) throw new WhipError('invalid_response', 'Terminal cursor is not a safe integer');\n return cursor;\n};\n\n/**\n * Workspace terminals: one login shell per tab, running where the daemon runs.\n * Output reaches only the connection that attached last; the daemon replays\n * retained output from the cursor an attach names, so a reload or reconnect\n * resumes without loss. Closing ends the shell; disconnecting does not.\n */\nexport class Terminals {\n constructor(private readonly client: WhipClient) {}\n /** Start a login shell. Omitted cwd resolves to the named session's directory, then the daemon home. */\n open(params: { cwd?: string; rootId?: string; cols: number; rows: number }, options: CallOptions = {}): Promise {\n return this.client.call('terminal.open', {\n ...(params.cwd ? { cwd: params.cwd } : {}), ...(params.rootId ? { root_id: params.rootId } : {}),\n cols: params.cols, rows: params.rows,\n }, options);\n }\n /** cursor -1 asks for live output only; a cursor older than the retained ring is clamped to its start. */\n async attach(id: string, cursor: number, options: CallOptions = {}): Promise {\n const result = await this.client.call('terminal.attach', { id, cursor: String(cursor) }, options);\n return {\n cursor: cursorOf(result.cursor), cwd: result.cwd, cols: result.cols, rows: result.rows, exited: result.exited,\n ...(result.exit_code === undefined ? {} : { exitCode: result.exit_code }),\n ...(result.signal ? { signal: result.signal } : {}),\n };\n }\n async write(id: string, data: Uint8Array | string, options: CallOptions = {}): Promise {\n const bytes = typeof data === 'string' ? encoder.encode(data) : data;\n if (!bytes.byteLength || bytes.byteLength > MAX_TERMINAL_WRITE_BYTES) throw new WhipError('invalid_request', `Terminal writes must be 1..${MAX_TERMINAL_WRITE_BYTES} bytes`);\n await this.client.call('terminal.write', { id, bytes: encodeBase64(bytes) }, options);\n }\n async resize(id: string, cols: number, rows: number, options: CallOptions = {}): Promise {\n await this.client.call('terminal.resize', { id, cols, rows }, options);\n }\n /** Ends the shell. Use it when the tab closes, never on unmount or disconnect. */\n async close(id: string, options: CallOptions = {}): Promise {\n await this.client.call('terminal.close', { id }, options);\n }\n onOutput(listener: (output: TerminalOutput) => void): () => void {\n return this.client.onNotification('terminal.output', params => {\n listener({ id: params.id, cursor: cursorOf(params.cursor), bytes: decodeBase64(params.bytes ?? '') });\n });\n }\n onExited(listener: (exit: TerminalExit) => void): () => void {\n return this.client.onNotification('terminal.exited', params => {\n listener({ id: params.id, exitCode: params.exit_code, ...(params.signal ? { signal: params.signal } : {}) });\n });\n }\n onDetached(listener: (id: string) => void): () => void {\n return this.client.onNotification('terminal.detached', params => listener(params.id));\n }\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/testing-node.d.ts b/vendor/@whip/sdk/dist/testing-node.d.ts new file mode 100644 index 0000000..b869920 --- /dev/null +++ b/vendor/@whip/sdk/dist/testing-node.d.ts @@ -0,0 +1,49 @@ +import { type SpawnOptions } from 'node:child_process'; +export * from './testing.js'; +/** The repository root; the live daemon is built from its Go sources. */ +export declare const repository: string; +export declare function fixtureExternalOrigin(value: unknown): string | undefined; +export declare function run(command: string, args: string[], options?: SpawnOptions): Promise; +export declare function eventually(check: () => T | Promise, { timeout, interval, description }?: { + timeout?: number | undefined; + interval?: number | undefined; + description?: string | undefined; +}): Promise>; +/** What the daemon fixture writes once it is serving. */ +export interface LiveDaemonInfo { + generation: number; + endpoint: string; + frontend: string; + [key: string]: unknown; +} +export interface LiveDaemonOptions { + allowedOrigins?: string[]; + retainOnFailure?: boolean; + /** Fixture lifetime; four minutes by default, at most thirty. */ + lifetimeMs?: number; + externalOrigin?: string; + /** Extra environment for the daemon process, such as WHIP_SDK_AGENTS_FIXTURE=1 for the recursive runtime behind a scripted model. */ + env?: Record; +} +export interface LiveDaemon { + readonly info: LiveDaemonInfo; + readonly exited: Promise; + readonly directory: string; + readonly pid: number | undefined; + readonly output: string; + crashAndRestart(options?: { + beforeRestart?: () => Promise | void; + }): Promise; + release(key: string): Promise; + effects(): Promise; + close(): Promise; +} +/** + * A real daemon for acceptance tests: the integration test binary serving the + * WHIP protocol from an isolated home, attach-only from the SDK's perspective. + * Keeping the binary lets restart tests kill the daemon process itself. Node + * only; it builds and spawns Go. + */ +export declare function startFixture({ allowedOrigins, retainOnFailure, lifetimeMs, externalOrigin, env }?: LiveDaemonOptions): Promise; +/** A live daemon under the name the plan uses. */ +export declare const liveDaemon: typeof startFixture; diff --git a/vendor/@whip/sdk/dist/testing-node.js b/vendor/@whip/sdk/dist/testing-node.js new file mode 100644 index 0000000..77bcf19 --- /dev/null +++ b/vendor/@whip/sdk/dist/testing-node.js @@ -0,0 +1,174 @@ +import { spawn } from 'node:child_process'; +import { once } from 'node:events'; +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +export * from './testing.js'; +/** The repository root; the live daemon is built from its Go sources. */ +export const repository = fileURLToPath(new URL('../../../', import.meta.url)); +export function fixtureExternalOrigin(value) { + if (value === undefined) + return undefined; + try { + if (typeof value !== 'string' || value.length > 2048) + throw new TypeError(); + const parsed = new URL(value); + if (parsed.protocol !== 'https:' || value !== parsed.origin || parsed.hostname.includes('*')) + throw new TypeError(); + return value; + } + catch { + throw new TypeError('externalOrigin must be an exact HTTPS origin without credentials, path, query, fragment or wildcards'); + } +} +export async function run(command, args, options = {}) { + const child = spawn(command, args, { cwd: repository, stdio: 'inherit', ...options }); + const [code, signal] = (await once(child, 'exit')); + if (code !== 0) + throw new Error(`${command} exited with ${code ?? signal}`); +} +export async function eventually(check, { timeout = 15_000, interval = 25, description = 'condition' } = {}) { + const deadline = Date.now() + timeout; + let last; + while (Date.now() < deadline) { + try { + const value = await check(); + if (value) + return value; + } + catch (error) { + last = error; + } + await new Promise(resolve => setTimeout(resolve, interval)); + } + throw new Error(`Timed out waiting for ${description}`, { cause: last }); +} +/** + * A real daemon for acceptance tests: the integration test binary serving the + * WHIP protocol from an isolated home, attach-only from the SDK's perspective. + * Keeping the binary lets restart tests kill the daemon process itself. Node + * only; it builds and spawns Go. + */ +export async function startFixture({ allowedOrigins = [], retainOnFailure = false, lifetimeMs = 4 * 60_000, externalOrigin, env = {} } = {}) { + if (!Number.isInteger(lifetimeMs) || lifetimeMs <= 0 || lifetimeMs > 30 * 60_000) { + throw new RangeError('Fixture lifetimeMs must be a positive integer no greater than 1800000 (30 minutes)'); + } + const origin = fixtureExternalOrigin(externalOrigin); + const directory = await mkdtemp(join(tmpdir(), 'whip-sdk-')); + const binary = join(directory, 'daemon.test'); + await mkdir(join(directory, 'public')); + let child; + let exit; + let info; + let output = ''; + let finished = false; + const start = async () => { + const generation = (info?.generation ?? 0) + 1; + child = spawn(binary, ['-test.run=^TestV2SDKBridge$', `-test.timeout=${lifetimeMs + 60_000}ms`, '-test.v'], { + cwd: repository, + env: { + ...process.env, + WHIP_HOME: join(directory, 'home'), + WHIP_SDK_FIXTURE_DIR: directory, + WHIP_SDK_FIXTURE_LIFETIME: `${lifetimeMs}ms`, + WHIP_SDK_FIXTURE_ORIGIN: origin ?? '', + WHIP_SDK_FIXTURE_ALLOWED_ORIGINS: JSON.stringify(allowedOrigins), + ...env, + }, + stdio: ['ignore', 'pipe', 'pipe'], + }); + child.stdout.on('data', data => { output += data; }); + child.stderr.on('data', data => { output += data; }); + exit = once(child, 'exit'); + if (retainOnFailure) + await writeFile(join(directory, 'fixture-process.json'), JSON.stringify({ pid: child.pid, binary, generation, home: join(directory, 'home') }, null, 2)); + const process_ = child; + info = await eventually(async () => { + if (process_.exitCode !== null) + throw new Error(`Fixture exited: ${output}`); + const candidate = JSON.parse(await readFile(join(directory, 'bridge.json'), 'utf8')); + if (candidate.generation !== generation) + return undefined; + return candidate; + }, { timeout: 30_000, description: `daemon generation ${generation}` }); + }; + const close = async () => { + if (finished) + return; + finished = true; + let failed = false; + try { + if (child && child.exitCode === null && child.signalCode === null) { + await fetch(info.frontend + '/done', { method: 'POST', signal: AbortSignal.timeout(3_000) }).catch(() => { }); + const timer = setTimeout(() => child?.kill('SIGKILL'), 5_000); + await exit; + clearTimeout(timer); + } + const [code, signal] = await exit; + if (code !== 0 || output.includes('WARNING: DATA RACE')) + throw new Error(`SDK fixture failed (${String(code ?? signal)}): ${output}`); + } + catch (error) { + failed = true; + if (retainOnFailure) + await writeFile(join(directory, 'fixture-failure.log'), `${error.stack ?? String(error)}\n${output}`).catch(() => { }); + throw error; + } + finally { + if (process.env.WHIP_SDK_KEEP_FIXTURE || (failed && retainOnFailure)) + console.log(`SDK fixture retained: ${directory}`); + else + await rm(directory, { recursive: true, force: true }); + } + }; + try { + await run('go', ['test', '-c', ...(process.env.WHIP_SDK_RACE ? ['-race'] : []), '-tags=integration', '-o', binary, './internal/daemon']); + await start(); + } + catch (error) { + if (child) + child.kill('SIGKILL'); + if (retainOnFailure) { + await writeFile(join(directory, 'fixture-failure.log'), `${error.stack ?? String(error)}\n${output}`).catch(() => { }); + console.error(`SDK fixture retained: ${directory}`); + } + else + await rm(directory, { recursive: true, force: true }); + throw new Error(`Cannot start SDK fixture: ${output}`, { cause: error }); + } + return { + get info() { return info; }, + get exited() { return exit; }, + directory, + get pid() { return child?.pid; }, + get output() { return output; }, + async crashAndRestart({ beforeRestart } = {}) { + child.kill('SIGKILL'); + const [, signal] = await exit; + if (signal !== 'SIGKILL') + throw new Error(`Fixture did not crash: ${output}`); + try { + await beforeRestart?.(); + } + finally { + await start(); + } + return info; + }, + async release(key) { + const response = await fetch(info.frontend + '/control/release?key=' + encodeURIComponent(key), { method: 'POST' }); + if (!response.ok) + throw new Error(`Release failed: ${response.status}`); + }, + async effects() { + const response = await fetch(info.frontend + '/control/effects'); + const text = await response.text(); + return text.trim() ? text.trim().split('\n').map(line => JSON.parse(line)) : []; + }, + close, + }; +} +/** A live daemon under the name the plan uses. */ +export const liveDaemon = startFixture; +//# sourceMappingURL=testing-node.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/testing-node.js.map b/vendor/@whip/sdk/dist/testing-node.js.map new file mode 100644 index 0000000..b4f921e --- /dev/null +++ b/vendor/@whip/sdk/dist/testing-node.js.map @@ -0,0 +1 @@ +{"version":3,"file":"testing-node.js","sourceRoot":"","sources":["../src/testing-node.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, type ChildProcess, type SpawnOptions } from 'node:child_process';\nimport { once } from 'node:events';\nimport { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport * from './testing.js';\n\n/** The repository root; the live daemon is built from its Go sources. */\nexport const repository = fileURLToPath(new URL('../../../', import.meta.url));\n\nexport function fixtureExternalOrigin(value: unknown): string | undefined {\n if (value === undefined) return undefined;\n try {\n if (typeof value !== 'string' || value.length > 2048) throw new TypeError();\n const parsed = new URL(value);\n if (parsed.protocol !== 'https:' || value !== parsed.origin || parsed.hostname.includes('*')) throw new TypeError();\n return value;\n } catch {\n throw new TypeError('externalOrigin must be an exact HTTPS origin without credentials, path, query, fragment or wildcards');\n }\n}\n\nexport async function run(command: string, args: string[], options: SpawnOptions = {}): Promise {\n const child = spawn(command, args, { cwd: repository, stdio: 'inherit', ...options });\n const [code, signal] = (await once(child, 'exit')) as [number | null, NodeJS.Signals | null];\n if (code !== 0) throw new Error(`${command} exited with ${code ?? signal}`);\n}\n\nexport async function eventually(check: () => T | Promise, { timeout = 15_000, interval = 25, description = 'condition' } = {}): Promise> {\n const deadline = Date.now() + timeout;\n let last: unknown;\n while (Date.now() < deadline) {\n try {\n const value = await check();\n if (value) return value as NonNullable;\n } catch (error) { last = error; }\n await new Promise(resolve => setTimeout(resolve, interval));\n }\n throw new Error(`Timed out waiting for ${description}`, { cause: last });\n}\n\n/** What the daemon fixture writes once it is serving. */\nexport interface LiveDaemonInfo {\n generation: number;\n endpoint: string;\n frontend: string;\n [key: string]: unknown;\n}\nexport interface LiveDaemonOptions {\n allowedOrigins?: string[];\n retainOnFailure?: boolean;\n /** Fixture lifetime; four minutes by default, at most thirty. */\n lifetimeMs?: number;\n externalOrigin?: string;\n /** Extra environment for the daemon process, such as WHIP_SDK_AGENTS_FIXTURE=1 for the recursive runtime behind a scripted model. */\n env?: Record;\n}\nexport interface LiveDaemon {\n readonly info: LiveDaemonInfo;\n readonly exited: Promise;\n readonly directory: string;\n readonly pid: number | undefined;\n readonly output: string;\n crashAndRestart(options?: { beforeRestart?: () => Promise | void }): Promise;\n release(key: string): Promise;\n effects(): Promise;\n close(): Promise;\n}\n\n/**\n * A real daemon for acceptance tests: the integration test binary serving the\n * WHIP protocol from an isolated home, attach-only from the SDK's perspective.\n * Keeping the binary lets restart tests kill the daemon process itself. Node\n * only; it builds and spawns Go.\n */\nexport async function startFixture({ allowedOrigins = [], retainOnFailure = false, lifetimeMs = 4 * 60_000, externalOrigin, env = {} }: LiveDaemonOptions = {}): Promise {\n if (!Number.isInteger(lifetimeMs) || lifetimeMs <= 0 || lifetimeMs > 30 * 60_000) {\n throw new RangeError('Fixture lifetimeMs must be a positive integer no greater than 1800000 (30 minutes)');\n }\n const origin = fixtureExternalOrigin(externalOrigin);\n const directory = await mkdtemp(join(tmpdir(), 'whip-sdk-'));\n const binary = join(directory, 'daemon.test');\n await mkdir(join(directory, 'public'));\n let child: ChildProcess | undefined;\n let exit!: Promise;\n let info!: LiveDaemonInfo;\n let output = '';\n let finished = false;\n const start = async () => {\n const generation = (info?.generation ?? 0) + 1;\n child = spawn(binary, ['-test.run=^TestV2SDKBridge$', `-test.timeout=${lifetimeMs + 60_000}ms`, '-test.v'], {\n cwd: repository,\n env: {\n ...process.env,\n WHIP_HOME: join(directory, 'home'),\n WHIP_SDK_FIXTURE_DIR: directory,\n WHIP_SDK_FIXTURE_LIFETIME: `${lifetimeMs}ms`,\n WHIP_SDK_FIXTURE_ORIGIN: origin ?? '',\n WHIP_SDK_FIXTURE_ALLOWED_ORIGINS: JSON.stringify(allowedOrigins),\n ...env,\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n child.stdout!.on('data', data => { output += data; });\n child.stderr!.on('data', data => { output += data; });\n exit = once(child, 'exit');\n if (retainOnFailure) await writeFile(join(directory, 'fixture-process.json'), JSON.stringify({ pid: child.pid, binary, generation, home: join(directory, 'home') }, null, 2));\n const process_ = child;\n info = await eventually(async () => {\n if (process_.exitCode !== null) throw new Error(`Fixture exited: ${output}`);\n const candidate = JSON.parse(await readFile(join(directory, 'bridge.json'), 'utf8')) as LiveDaemonInfo;\n if (candidate.generation !== generation) return undefined;\n return candidate;\n }, { timeout: 30_000, description: `daemon generation ${generation}` });\n };\n const close = async () => {\n if (finished) return;\n finished = true;\n let failed = false;\n try {\n if (child && child.exitCode === null && child.signalCode === null) {\n await fetch(info.frontend + '/done', { method: 'POST', signal: AbortSignal.timeout(3_000) }).catch(() => {});\n const timer = setTimeout(() => child?.kill('SIGKILL'), 5_000);\n await exit;\n clearTimeout(timer);\n }\n const [code, signal] = await exit;\n if (code !== 0 || output.includes('WARNING: DATA RACE')) throw new Error(`SDK fixture failed (${String(code ?? signal)}): ${output}`);\n } catch (error) {\n failed = true;\n if (retainOnFailure) await writeFile(join(directory, 'fixture-failure.log'), `${(error as Error).stack ?? String(error)}\\n${output}`).catch(() => {});\n throw error;\n } finally {\n if (process.env.WHIP_SDK_KEEP_FIXTURE || (failed && retainOnFailure)) console.log(`SDK fixture retained: ${directory}`);\n else await rm(directory, { recursive: true, force: true });\n }\n };\n try {\n await run('go', ['test', '-c', ...(process.env.WHIP_SDK_RACE ? ['-race'] : []), '-tags=integration', '-o', binary, './internal/daemon']);\n await start();\n } catch (error) {\n if (child) child.kill('SIGKILL');\n if (retainOnFailure) {\n await writeFile(join(directory, 'fixture-failure.log'), `${(error as Error).stack ?? String(error)}\\n${output}`).catch(() => {});\n console.error(`SDK fixture retained: ${directory}`);\n } else await rm(directory, { recursive: true, force: true });\n throw new Error(`Cannot start SDK fixture: ${output}`, { cause: error });\n }\n return {\n get info() { return info; },\n get exited() { return exit; },\n directory,\n get pid() { return child?.pid; },\n get output() { return output; },\n async crashAndRestart({ beforeRestart }: { beforeRestart?: () => Promise | void } = {}) {\n child!.kill('SIGKILL');\n const [, signal] = await exit;\n if (signal !== 'SIGKILL') throw new Error(`Fixture did not crash: ${output}`);\n try { await beforeRestart?.(); } finally { await start(); }\n return info;\n },\n async release(key: string) {\n const response = await fetch(info.frontend + '/control/release?key=' + encodeURIComponent(key), { method: 'POST' });\n if (!response.ok) throw new Error(`Release failed: ${response.status}`);\n },\n async effects() {\n const response = await fetch(info.frontend + '/control/effects');\n const text = await response.text();\n return text.trim() ? text.trim().split('\\n').map(line => JSON.parse(line) as unknown) : [];\n },\n close,\n };\n}\n\n/** A live daemon under the name the plan uses. */\nexport const liveDaemon = startFixture;\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/testing.d.ts b/vendor/@whip/sdk/dist/testing.d.ts new file mode 100644 index 0000000..d801fba --- /dev/null +++ b/vendor/@whip/sdk/dist/testing.d.ts @@ -0,0 +1,112 @@ +import { type InitializeResult } from '@whip/protocol'; +import type { TransportFactory } from './transport.js'; +/** One request the client sent, as the scripted daemon saw it. */ +export interface FixtureRequest { + id: string; + method: string; + params: Record; + raw: string; +} +/** One client connection to the scripted daemon; replies, errors, notifications, and failure are under test control. */ +export interface FixtureConnection { + readonly requests: FixtureRequest[]; + reply(request: FixtureRequest, result: unknown): void; + error(request: FixtureRequest, kind: string, code?: number): void; + fail(error?: Error): void; + notify(method: string, params: unknown): void; + /** Whether a reply or error was sent for the request. */ + answered(request: FixtureRequest): boolean; +} +export interface ScriptedDaemonOptions { + kind?: 'unix' | 'websocket'; + initialize?: Partial; + /** Legacy per-request hook; runs after table replies and before the session defaults. */ + request?: (request: FixtureRequest, connection: FixtureConnection) => void; +} +export type ReplyHandler = (request: FixtureRequest, connection: FixtureConnection) => unknown; +export type TurnScriptStep = { + text: string; +} | { + reasoning: string; +} | { + notice: string; +} | { + question: { + id: string; + question: string; + options: { + label: string; + description?: string; + recommended?: boolean; + }[]; + multiple?: boolean; + }; +} | { + permission: { + id: string; + operation: string; + command?: string; + rule?: string; + path?: string; + }; +} | { + hook: { + hook: string; + operation: string; + decision: string; + reason?: string; + }; +} | { + host: { + id: string; + operation: string; + summary?: string; + error?: string; + }; +} | { + event: { + kind: string; + payload: unknown; + }; +}; +/** A whole turn for session.run: journal events after turn.started, then how the command settles. */ +export interface TurnScript { + turnId?: string; + steps?: TurnScriptStep[]; + status?: 'succeeded' | 'failed' | 'cancelled' | 'interrupted'; + /** The submit result's text on success. */ + text?: string; + /** The submit result's output on success (phase 6 output contracts). */ + output?: unknown; + failure?: { + code: number; + message: string; + data?: { + kind: string; + }; + }; +} +export interface ScriptedDaemon { + readonly info: InitializeResult; + readonly factory: TransportFactory; + readonly connections: readonly FixtureConnection[]; + readonly current: FixtureConnection; + /** Reply to a method from a table: a returned value is the reply; undefined leaves the request for other handlers. */ + reply(method: string, handler: ReplyHandler): ScriptedDaemon; + /** Push one journal event to every live subscription of a root; returns its sequence. */ + emit(rootId: string, kind: string, payload: unknown): string; + /** Answer snapshot, subscribe, submit, status, decide, and ping for sessions so session.run works without a daemon. */ + serveSessions(): ScriptedDaemon; + /** Play a scripted turn for the next submit on a root; resolves once its events are emitted and the command settled. */ + turn(rootId: string, script?: TurnScript): Promise; + /** Durable command records by id, as command.status reports them. */ + readonly commands: ReadonlyMap>; +} +/** + * A daemon made of replies. The client runs its real protocol processing over + * an in-memory transport; the test decides what each request receives, pushes + * journal events, or plays whole turns. Browser-safe. + */ +export declare function scriptedDaemon(options?: ScriptedDaemonOptions): ScriptedDaemon; +/** The scripted daemon under its earlier name. */ +export declare const transportFixture: typeof scriptedDaemon; diff --git a/vendor/@whip/sdk/dist/testing.js b/vendor/@whip/sdk/dist/testing.js new file mode 100644 index 0000000..608f50a --- /dev/null +++ b/vendor/@whip/sdk/dist/testing.js @@ -0,0 +1,183 @@ +import { manifest } from '@whip/protocol'; +function snapshot(rootId, cursor, pinned = { definition: 'coding', revision: '' }) { + return { + root_id: rootId, cursor, history_revision: '1', active_turns: {}, + meta: { id: rootId, kind: 'agent', title: '', model: 'scripted-model', provider: 'scripted', cwd: '/scripted', execution_engine: 'starlark', definition: pinned.definition, definition_revision: pinned.revision, + goal: '', forked_from: '', fork_seq: 0, tags: [], archived: false, pinned: false, effort: '', usage_in: 0, usage_cached: 0, usage_out: 0, updated_at: '' }, + messages: [], message_seqs: [], presentation: [], agent_presentations: {}, agents: [], inbox: [], blackboard: [], + budgets: [], capabilities: [], schedules: [], permissions: [], questions: [], + }; +} +/** + * A daemon made of replies. The client runs its real protocol processing over + * an in-memory transport; the test decides what each request receives, pushes + * journal events, or plays whole turns. Browser-safe. + */ +export function scriptedDaemon(options = {}) { + const connections = []; + const table = new Map(); + const commands = new Map(); + const subscriptions = new Map(); + const sequences = new Map(); + const scripts = new Map(); + const turns = new Map(); + const roots = new Map(); + let ingress = 0; + let generation = 0; + let created = 0; + const revisionOf = (id) => `scripted-revision-${id}`; + let sessions = false; + const info = { + protocol_major: manifest.major, protocol_minor: manifest.minor, runtime_id: 'fixture-runtime', connection_id: 'fixture-connection', + generation: '9007199254740993', build_id: 'fixture', host_platform: 'darwin', host_architecture: 'arm64', + capabilities: [], negotiated_capabilities: [], + execution_engines: [{ id: 'starlark', language: 'starlark', label: 'Starlark' }, { id: 'quickjs', language: 'javascript', label: 'JavaScript (QuickJS)' }], default_execution_engine: 'starlark', + operations: manifest.operations.map(operation => ({ ...operation })), + limits: { frame_bytes: 1 << 20, connections: 64, in_flight_requests: 32, outbound_messages: 1024, + outbound_bytes: String(8 << 20), root_subscriptions: 16, content_chunk_bytes: 4, upload_bytes: String(64 << 20) }, + ...options.initialize, + }; + const emit = (rootId, kind, payload) => { + const seq = String((sequences.get(rootId) ?? 0) + 1); + sequences.set(rootId, Number(seq)); + for (const [id, subscription] of subscriptions) { + if (subscription.rootId === rootId) + subscription.connection.notify('event', { event: { root_id: rootId, subscription_id: id, seq, kind, payload } }); + } + return seq; + }; + const play = async (rootId, commandId, ingressSeq, entry) => { + await new Promise(resolve => setTimeout(resolve, 0)); // the client sees the submit reply before the turn starts + const { script } = entry; + const count = (turns.get(rootId) ?? 0) + 1; + turns.set(rootId, count); + const turnId = script.turnId ?? `${rootId}-turn-${count}`; + const base = { agent_id: rootId, turn_id: turnId }; + emit(rootId, 'turn.started', { ...base, inbox_seq: ingressSeq, phase: 'running', status: 'running' }); + for (const step of script.steps ?? []) { + if ('text' in step) + emit(rootId, 'stream.text', { ...base, text: step.text }); + else if ('reasoning' in step) + emit(rootId, 'stream.reasoning', { ...base, text: step.reasoning }); + else if ('notice' in step) + emit(rootId, 'stream.notice', { ...base, text: step.notice }); + else if ('question' in step) + emit(rootId, 'question.pending', { ...base, question_id: step.question.id, question: step.question.question, options: step.question.options, multiple: step.question.multiple ?? false }); + else if ('permission' in step) + emit(rootId, 'permission.pending', { ...base, permission_id: step.permission.id, operation: step.permission.operation, command: step.permission.command ?? '', rule: step.permission.rule ?? '', canonical_path: step.permission.path ?? '' }); + else if ('hook' in step) + emit(rootId, 'stream.hook.decision', { ...base, name: step.hook.hook, args: step.hook.operation, text: step.hook.decision, result: step.hook.reason ?? '' }); + else if ('host' in step) { + emit(rootId, 'stream.cell.host.started', { ...base, id: 'call-1', invocation_id: step.host.id, name: step.host.operation, args: step.host.summary ?? '' }); + emit(rootId, 'stream.cell.host', { ...base, id: 'call-1', invocation_id: step.host.id, name: step.host.operation, args: step.host.summary ?? '', text: '1ms', host_status: step.host.error ? 'failed' : 'succeeded', result: step.host.error ?? '' }); + } + else + emit(rootId, step.event.kind, step.event.payload); + } + const status = script.status ?? 'succeeded'; + emit(rootId, `turn.${status}`, { ...base, status, ...(script.failure ? { error: script.failure.message } : {}) }); + const record = commands.get(commandId); + if (record) { + Object.assign(record, { status }, status === 'succeeded' + ? { result: { text: script.text ?? '', ...(script.output !== undefined ? { output: script.output } : {}) } } + : { failure: script.failure ?? { code: -32000, message: `turn ${status}`, data: { kind: status === 'cancelled' ? 'cancelled' : 'execution_failed' } } }); + } + entry.resolve(); + }; + const defaults = { + 'daemon.ping': () => ({ generation: info.generation, build_id: info.build_id }), + 'root.snapshot': request => snapshot(String(request.params.root_id), String(sequences.get(String(request.params.root_id)) ?? 0), roots.get(String(request.params.root_id))), + 'definitions.register': request => ({ id: request.params.definition.id, revision: revisionOf(request.params.definition.id), created: true }), + 'executor.bind': request => ({ generation: String(++generation), tools: request.params.tools ?? [], ...(request.params.hooks ? { hooks: request.params.hooks } : {}) }), + 'tool.result': () => ({ accepted: true }), + 'tool.progress': () => ({ accepted: true }), + 'hook.result': () => ({ accepted: true }), + 'events.subscribe': (request, connection) => { + subscriptions.set(String(request.params.subscription_id), { rootId: String(request.params.root_id), connection }); + return { subscription_id: request.params.subscription_id, cursor: request.params.cursor }; + }, + 'events.unsubscribe': request => { subscriptions.delete(String(request.params.subscription_id)); return {}; }, + 'command.submit': request => { + const id = String(request.params.command_id); + const operation = String(request.params.operation); + const rootId = String(request.params.root_id ?? ''); + const ingressSeq = String(++ingress); + const record = { operation, command_id: id, ingress_seq: ingressSeq, status: operation === 'submit' ? 'running' : 'succeeded', ...(operation === 'submit' ? {} : { result: {} }) }; + if (operation === 'session.create') { + const payload = request.params.payload; + const newRoot = `root-${++created}`; + roots.set(newRoot, { definition: payload.definition ?? 'coding', revision: payload.definition ? revisionOf(payload.definition) : '' }); + record.result = { root_id: newRoot }; + } + commands.set(id, record); + if (operation === 'submit') { + const queued = scripts.get(rootId)?.shift(); + if (queued) + void play(rootId, id, ingressSeq, queued); + } + return record; + }, + 'command.status': request => commands.get(String(request.params.command_id)) ?? { operation: 'unknown', command_id: request.params.command_id, ingress_seq: '0', status: 'failed', failure: { code: -32011, message: 'unknown command', data: { kind: 'command_not_found' } } }, + 'permission.decide': () => ({ operation_id: 'scripted-operation', lease_id: 'scripted-lease' }), + }; + const factory = async (handlers) => { + const answered = new Set(); + const connection = { + requests: [], + reply: (request, result) => { answered.add(request.id); handlers.message(JSON.stringify({ jsonrpc: '2.0', id: request.id, result })); }, + error: (request, kind, code = -32000) => { answered.add(request.id); handlers.message(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code, message: kind, data: { kind } } })); }, + fail: (error = new Error('Fixture connection lost')) => handlers.close(error), + notify: (method, params) => handlers.message(JSON.stringify({ jsonrpc: '2.0', method, params })), + answered: request => answered.has(request.id), + }; + connections.push(connection); + return { + kind: options.kind ?? 'unix', httpEndpoint: 'http://fixture.invalid', bufferedAmount: 0, + send(raw) { + const request = { ...JSON.parse(raw), raw }; + connection.requests.push(request); + if (request.method === 'initialize') { + connection.reply(request, { ...info, connection_id: `${info.connection_id}-${connections.length}` }); + return; + } + const scripted = table.get(request.method); + if (scripted) { + const result = scripted(request, connection); + if (result !== undefined) { + connection.reply(request, result); + return; + } + } + options.request?.(request, connection); + if (connection.answered(request) || !sessions) + return; + const fallback = defaults[request.method]; + if (fallback) { + const result = fallback(request, connection); + if (result !== undefined) + connection.reply(request, result); + } + }, + close() { }, + }; + }; + const daemon = { + info, factory, connections, commands, + get current() { return connections.at(-1); }, + reply(method, handler) { table.set(method, handler); return daemon; }, + emit, + serveSessions() { sessions = true; return daemon; }, + turn(rootId, script = {}) { + sessions = true; + return new Promise(resolve => { + const queue = scripts.get(rootId) ?? []; + queue.push({ script, resolve }); + scripts.set(rootId, queue); + }); + }, + }; + return daemon; +} +/** The scripted daemon under its earlier name. */ +export const transportFixture = scriptedDaemon; +//# sourceMappingURL=testing.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/testing.js.map b/vendor/@whip/sdk/dist/testing.js.map new file mode 100644 index 0000000..d966fea --- /dev/null +++ b/vendor/@whip/sdk/dist/testing.js.map @@ -0,0 +1 @@ +{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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","sourcesContent":["import { manifest, type InitializeResult } from '@whip/protocol';\nimport type { TransportFactory } from './transport.js';\n\n/** One request the client sent, as the scripted daemon saw it. */\nexport interface FixtureRequest { id: string; method: string; params: Record; raw: string }\n/** One client connection to the scripted daemon; replies, errors, notifications, and failure are under test control. */\nexport interface FixtureConnection {\n readonly requests: FixtureRequest[];\n reply(request: FixtureRequest, result: unknown): void;\n error(request: FixtureRequest, kind: string, code?: number): void;\n fail(error?: Error): void;\n notify(method: string, params: unknown): void;\n /** Whether a reply or error was sent for the request. */\n answered(request: FixtureRequest): boolean;\n}\nexport interface ScriptedDaemonOptions {\n kind?: 'unix' | 'websocket';\n initialize?: Partial;\n /** Legacy per-request hook; runs after table replies and before the session defaults. */\n request?: (request: FixtureRequest, connection: FixtureConnection) => void;\n}\nexport type ReplyHandler = (request: FixtureRequest, connection: FixtureConnection) => unknown;\n\nexport type TurnScriptStep =\n | { text: string }\n | { reasoning: string }\n | { notice: string }\n | { question: { id: string; question: string; options: { label: string; description?: string; recommended?: boolean }[]; multiple?: boolean } }\n | { permission: { id: string; operation: string; command?: string; rule?: string; path?: string } }\n | { hook: { hook: string; operation: string; decision: string; reason?: string } }\n | { host: { id: string; operation: string; summary?: string; error?: string } }\n | { event: { kind: string; payload: unknown } };\n/** A whole turn for session.run: journal events after turn.started, then how the command settles. */\nexport interface TurnScript {\n turnId?: string;\n steps?: TurnScriptStep[];\n status?: 'succeeded' | 'failed' | 'cancelled' | 'interrupted';\n /** The submit result's text on success. */\n text?: string;\n /** The submit result's output on success (phase 6 output contracts). */\n output?: unknown;\n failure?: { code: number; message: string; data?: { kind: string } };\n}\n\nexport interface ScriptedDaemon {\n readonly info: InitializeResult;\n readonly factory: TransportFactory;\n readonly connections: readonly FixtureConnection[];\n readonly current: FixtureConnection;\n /** Reply to a method from a table: a returned value is the reply; undefined leaves the request for other handlers. */\n reply(method: string, handler: ReplyHandler): ScriptedDaemon;\n /** Push one journal event to every live subscription of a root; returns its sequence. */\n emit(rootId: string, kind: string, payload: unknown): string;\n /** Answer snapshot, subscribe, submit, status, decide, and ping for sessions so session.run works without a daemon. */\n serveSessions(): ScriptedDaemon;\n /** Play a scripted turn for the next submit on a root; resolves once its events are emitted and the command settled. */\n turn(rootId: string, script?: TurnScript): Promise;\n /** Durable command records by id, as command.status reports them. */\n readonly commands: ReadonlyMap>;\n}\n\nfunction snapshot(rootId: string, cursor: string, pinned: { definition: string; revision: string } = { definition: 'coding', revision: '' }) {\n return {\n root_id: rootId, cursor, history_revision: '1', active_turns: {},\n meta: { id: rootId, kind: 'agent', title: '', model: 'scripted-model', provider: 'scripted', cwd: '/scripted', execution_engine: 'starlark', definition: pinned.definition, definition_revision: pinned.revision,\n goal: '', forked_from: '', fork_seq: 0, tags: [], archived: false, pinned: false, effort: '', usage_in: 0, usage_cached: 0, usage_out: 0, updated_at: '' },\n messages: [], message_seqs: [], presentation: [], agent_presentations: {}, agents: [], inbox: [], blackboard: [],\n budgets: [], capabilities: [], schedules: [], permissions: [], questions: [],\n };\n}\n\n/**\n * A daemon made of replies. The client runs its real protocol processing over\n * an in-memory transport; the test decides what each request receives, pushes\n * journal events, or plays whole turns. Browser-safe.\n */\nexport function scriptedDaemon(options: ScriptedDaemonOptions = {}): ScriptedDaemon {\n const connections: FixtureConnection[] = [];\n const table = new Map();\n const commands = new Map>();\n const subscriptions = new Map();\n const sequences = new Map();\n const scripts = new Map();\n const turns = new Map();\n const roots = new Map();\n let ingress = 0;\n let generation = 0;\n let created = 0;\n const revisionOf = (id: string) => `scripted-revision-${id}`;\n let sessions = false;\n const info: InitializeResult = {\n protocol_major: manifest.major, protocol_minor: manifest.minor, runtime_id: 'fixture-runtime', connection_id: 'fixture-connection',\n generation: '9007199254740993', build_id: 'fixture', host_platform: 'darwin', host_architecture: 'arm64',\n capabilities: [], negotiated_capabilities: [],\n execution_engines: [{ id: 'starlark', language: 'starlark', label: 'Starlark' }, { id: 'quickjs', language: 'javascript', label: 'JavaScript (QuickJS)' }], default_execution_engine: 'starlark',\n operations: manifest.operations.map(operation => ({ ...operation })),\n limits: { frame_bytes: 1 << 20, connections: 64, in_flight_requests: 32, outbound_messages: 1024,\n outbound_bytes: String(8 << 20), root_subscriptions: 16, content_chunk_bytes: 4, upload_bytes: String(64 << 20) },\n ...options.initialize,\n };\n const emit = (rootId: string, kind: string, payload: unknown): string => {\n const seq = String((sequences.get(rootId) ?? 0) + 1);\n sequences.set(rootId, Number(seq));\n for (const [id, subscription] of subscriptions) {\n if (subscription.rootId === rootId) subscription.connection.notify('event', { event: { root_id: rootId, subscription_id: id, seq, kind, payload } });\n }\n return seq;\n };\n const play = async (rootId: string, commandId: string, ingressSeq: string, entry: { script: TurnScript; resolve(): void }) => {\n await new Promise(resolve => setTimeout(resolve, 0)); // the client sees the submit reply before the turn starts\n const { script } = entry;\n const count = (turns.get(rootId) ?? 0) + 1;\n turns.set(rootId, count);\n const turnId = script.turnId ?? `${rootId}-turn-${count}`;\n const base = { agent_id: rootId, turn_id: turnId };\n emit(rootId, 'turn.started', { ...base, inbox_seq: ingressSeq, phase: 'running', status: 'running' });\n for (const step of script.steps ?? []) {\n if ('text' in step) emit(rootId, 'stream.text', { ...base, text: step.text });\n else if ('reasoning' in step) emit(rootId, 'stream.reasoning', { ...base, text: step.reasoning });\n else if ('notice' in step) emit(rootId, 'stream.notice', { ...base, text: step.notice });\n else if ('question' in step) emit(rootId, 'question.pending', { ...base, question_id: step.question.id, question: step.question.question, options: step.question.options, multiple: step.question.multiple ?? false });\n else if ('permission' in step) emit(rootId, 'permission.pending', { ...base, permission_id: step.permission.id, operation: step.permission.operation, command: step.permission.command ?? '', rule: step.permission.rule ?? '', canonical_path: step.permission.path ?? '' });\n else if ('hook' in step) emit(rootId, 'stream.hook.decision', { ...base, name: step.hook.hook, args: step.hook.operation, text: step.hook.decision, result: step.hook.reason ?? '' });\n else if ('host' in step) {\n emit(rootId, 'stream.cell.host.started', { ...base, id: 'call-1', invocation_id: step.host.id, name: step.host.operation, args: step.host.summary ?? '' });\n emit(rootId, 'stream.cell.host', { ...base, id: 'call-1', invocation_id: step.host.id, name: step.host.operation, args: step.host.summary ?? '', text: '1ms', host_status: step.host.error ? 'failed' : 'succeeded', result: step.host.error ?? '' });\n } else emit(rootId, step.event.kind, step.event.payload);\n }\n const status = script.status ?? 'succeeded';\n emit(rootId, `turn.${status}`, { ...base, status, ...(script.failure ? { error: script.failure.message } : {}) });\n const record = commands.get(commandId);\n if (record) {\n Object.assign(record, { status }, status === 'succeeded'\n ? { result: { text: script.text ?? '', ...(script.output !== undefined ? { output: script.output } : {}) } }\n : { failure: script.failure ?? { code: -32000, message: `turn ${status}`, data: { kind: status === 'cancelled' ? 'cancelled' : 'execution_failed' } } });\n }\n entry.resolve();\n };\n const defaults: Record = {\n 'daemon.ping': () => ({ generation: info.generation, build_id: info.build_id }),\n 'root.snapshot': request => snapshot(String(request.params.root_id), String(sequences.get(String(request.params.root_id)) ?? 0), roots.get(String(request.params.root_id))),\n 'definitions.register': request => ({ id: (request.params.definition as { id: string }).id, revision: revisionOf((request.params.definition as { id: string }).id), created: true }),\n 'executor.bind': request => ({ generation: String(++generation), tools: request.params.tools ?? [], ...(request.params.hooks ? { hooks: request.params.hooks } : {}) }),\n 'tool.result': () => ({ accepted: true }),\n 'tool.progress': () => ({ accepted: true }),\n 'hook.result': () => ({ accepted: true }),\n 'events.subscribe': (request, connection) => {\n subscriptions.set(String(request.params.subscription_id), { rootId: String(request.params.root_id), connection });\n return { subscription_id: request.params.subscription_id, cursor: request.params.cursor };\n },\n 'events.unsubscribe': request => { subscriptions.delete(String(request.params.subscription_id)); return {}; },\n 'command.submit': request => {\n const id = String(request.params.command_id);\n const operation = String(request.params.operation);\n const rootId = String(request.params.root_id ?? '');\n const ingressSeq = String(++ingress);\n const record: Record = { operation, command_id: id, ingress_seq: ingressSeq, status: operation === 'submit' ? 'running' : 'succeeded', ...(operation === 'submit' ? {} : { result: {} }) };\n if (operation === 'session.create') {\n const payload = request.params.payload as { definition?: string };\n const newRoot = `root-${++created}`;\n roots.set(newRoot, { definition: payload.definition ?? 'coding', revision: payload.definition ? revisionOf(payload.definition) : '' });\n record.result = { root_id: newRoot };\n }\n commands.set(id, record);\n if (operation === 'submit') {\n const queued = scripts.get(rootId)?.shift();\n if (queued) void play(rootId, id, ingressSeq, queued);\n }\n return record;\n },\n 'command.status': request => commands.get(String(request.params.command_id)) ?? { operation: 'unknown', command_id: request.params.command_id, ingress_seq: '0', status: 'failed', failure: { code: -32011, message: 'unknown command', data: { kind: 'command_not_found' } } },\n 'permission.decide': () => ({ operation_id: 'scripted-operation', lease_id: 'scripted-lease' }),\n };\n const factory: TransportFactory = async handlers => {\n const answered = new Set();\n const connection: FixtureConnection = {\n requests: [],\n reply: (request, result) => { answered.add(request.id); handlers.message(JSON.stringify({ jsonrpc: '2.0', id: request.id, result })); },\n error: (request, kind, code = -32000) => { answered.add(request.id); handlers.message(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code, message: kind, data: { kind } } })); },\n fail: (error = new Error('Fixture connection lost')) => handlers.close(error),\n notify: (method, params) => handlers.message(JSON.stringify({ jsonrpc: '2.0', method, params })),\n answered: request => answered.has(request.id),\n };\n connections.push(connection);\n return {\n kind: options.kind ?? 'unix', httpEndpoint: 'http://fixture.invalid', bufferedAmount: 0,\n send(raw) {\n const request = { ...JSON.parse(raw), raw } as FixtureRequest;\n connection.requests.push(request);\n if (request.method === 'initialize') { connection.reply(request, { ...info, connection_id: `${info.connection_id}-${connections.length}` }); return; }\n const scripted = table.get(request.method);\n if (scripted) {\n const result = scripted(request, connection);\n if (result !== undefined) { connection.reply(request, result); return; }\n }\n options.request?.(request, connection);\n if (connection.answered(request) || !sessions) return;\n const fallback = defaults[request.method];\n if (fallback) { const result = fallback(request, connection); if (result !== undefined) connection.reply(request, result); }\n },\n close() {},\n };\n };\n const daemon: ScriptedDaemon = {\n info, factory, connections, commands,\n get current() { return connections.at(-1)!; },\n reply(method, handler) { table.set(method, handler); return daemon; },\n emit,\n serveSessions() { sessions = true; return daemon; },\n turn(rootId, script = {}) {\n sessions = true;\n return new Promise(resolve => {\n const queue = scripts.get(rootId) ?? [];\n queue.push({ script, resolve });\n scripts.set(rootId, queue);\n });\n },\n };\n return daemon;\n}\n\n/** The scripted daemon under its earlier name. */\nexport const transportFixture = scriptedDaemon;\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/trace.d.ts b/vendor/@whip/sdk/dist/trace.d.ts new file mode 100644 index 0000000..3b3d2fa --- /dev/null +++ b/vendor/@whip/sdk/dist/trace.d.ts @@ -0,0 +1,66 @@ +import type { SpanPage } from '@whip/protocol'; +import type { DeepReadonly, SessionViewSnapshot } from './state.js'; +/** + * Trace evidence is the client's copy of the daemon's span table for one root: + * durable pages (`trace.page`) merged with live `span.started` / `span.ended` + * events by span id. Spans are bounded per root; when the cap is reached the + * oldest traces are evicted whole so the newest trace stays complete and the + * view says it is truncated. Timing is server-measured; open spans are drawn + * to the server's clock through `clockOffsetMs`. + */ +export type TraceSpanKind = 'agent' | 'llm' | 'tool' | 'host' | 'wait'; +export type TraceSpanStatus = 'running' | 'ok' | 'error' | 'cancelled' | 'interrupted'; +export interface TraceSpan { + readonly id: string; + readonly traceId: string; + readonly parentId: string; + readonly rootId: string; + readonly agentId: string; + readonly turnId: string; + readonly kind: TraceSpanKind; + readonly name: string; + readonly status: TraceSpanStatus; + /** Wall clock in milliseconds since the epoch, fractional. */ + readonly startMs: number; + /** 0 while the span is open. */ + readonly endMs: number; + readonly attrs: Readonly>; + readonly links: readonly { + readonly traceId: string; + readonly spanId: string; + }[]; + readonly updatedSeq: string; +} +export interface TraceEvidence { + readonly rootId: string; + /** Page cursor: the last `updated_seq` a durable page returned. */ + readonly pageCursor: string; + readonly spans: Readonly>; + readonly loaded: boolean; + readonly loading: boolean; + readonly hasMore: boolean; + readonly truncated: boolean; + /** Server clock minus client clock at the last page, so open spans grow against the daemon's time. */ + readonly clockOffsetMs: number; + readonly error?: Error; +} +export declare const MAX_TRACE_SPANS = 4096; +export declare const MAX_TRACE_BYTES: number; +/** Decode one wire record; undefined when it is not a span. */ +export declare function toTraceSpan(record: unknown): TraceSpan | undefined; +export declare function emptyTraceEvidence(rootId: string): TraceEvidence; +/** Fold one journal event; anything but a span event returns the evidence unchanged. */ +export declare function observeSpan(evidence: TraceEvidence, event: { + kind: string; + payload: unknown; +}): TraceEvidence; +/** Merge one durable page and record the server clock it was read at. */ +export declare function mergeSpanPage(evidence: TraceEvidence, page: SpanPage, receivedAtMs?: number): TraceEvidence; +/** Evict whole traces, oldest first, until the span count and byte budget hold. */ +export declare function boundTraceEvidence(evidence: TraceEvidence, maxSpans?: number, maxBytes?: number): TraceEvidence; +/** Spans of one trace (or every trace) in start order. */ +export declare function traceSpans(snapshot: DeepReadonly, traceId?: string): readonly TraceSpan[]; +/** Turn spans that start a trace, newest first: the trace picker's list. */ +export declare function traceRoots(snapshot: DeepReadonly): readonly TraceSpan[]; +/** The daemon's current time as this client can best estimate it. */ +export declare function serverNowMs(evidence: DeepReadonly | undefined, clientNowMs?: number): number; diff --git a/vendor/@whip/sdk/dist/trace.js b/vendor/@whip/sdk/dist/trace.js new file mode 100644 index 0000000..2804477 --- /dev/null +++ b/vendor/@whip/sdk/dist/trace.js @@ -0,0 +1,123 @@ +export const MAX_TRACE_SPANS = 4096; +export const MAX_TRACE_BYTES = 2 * 1024 * 1024; +const encoder = new TextEncoder(); +const kinds = new Set(['agent', 'llm', 'tool', 'host', 'wait']); +const statuses = new Set(['running', 'ok', 'error', 'cancelled', 'interrupted']); +const nanosToMs = (value) => { + if (!/^\d+$/.test(value)) + return 0; + const nanos = BigInt(value); + return Number(nanos / 1000000n) + Number(nanos % 1000000n) / 1_000_000; +}; +const object = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {}; +/** Decode one wire record; undefined when it is not a span. */ +export function toTraceSpan(record) { + const raw = object(record); + if (typeof raw.id !== 'string' || !raw.id || typeof raw.trace_id !== 'string' || typeof raw.root_id !== 'string' + || typeof raw.agent_id !== 'string' || typeof raw.start_ns !== 'string' || typeof raw.updated_seq !== 'string') + return undefined; + if (!kinds.has(raw.kind) || !statuses.has(raw.status)) + return undefined; + const links = Array.isArray(raw.links) ? raw.links.flatMap(link => { + const value = object(link); + return typeof value.trace_id === 'string' && typeof value.span_id === 'string' ? [{ traceId: value.trace_id, spanId: value.span_id }] : []; + }) : []; + return { + id: raw.id, traceId: raw.trace_id, parentId: raw.parent_id ?? '', rootId: raw.root_id, agentId: raw.agent_id, turnId: raw.turn_id ?? '', + kind: raw.kind, name: typeof raw.name === 'string' ? raw.name : '', status: raw.status, + startMs: nanosToMs(raw.start_ns), endMs: typeof raw.end_ns === 'string' ? nanosToMs(raw.end_ns) : 0, + attrs: Object.freeze({ ...object(raw.attrs) }), links: Object.freeze(links), updatedSeq: raw.updated_seq, + }; +} +export function emptyTraceEvidence(rootId) { + return { rootId, pageCursor: '0', spans: {}, loaded: false, loading: false, hasMore: false, truncated: false, clockOffsetMs: 0 }; +} +const newer = (a, b) => BigInt(a) > BigInt(b); +function upsert(spans, span) { + const existing = spans[span.id]; + if (existing && !newer(span.updatedSeq, existing.updatedSeq)) + return false; + spans[span.id] = span; + return true; +} +/** Fold one journal event; anything but a span event returns the evidence unchanged. */ +export function observeSpan(evidence, event) { + if (event.kind !== 'span.started' && event.kind !== 'span.ended') + return evidence; + const span = toTraceSpan(event.payload); + if (!span || span.rootId !== evidence.rootId) + return evidence; + const spans = { ...evidence.spans }; + if (!upsert(spans, span)) + return evidence; + return boundTraceEvidence({ ...evidence, spans }); +} +/** Merge one durable page and record the server clock it was read at. */ +export function mergeSpanPage(evidence, page, receivedAtMs = Date.now()) { + const spans = { ...evidence.spans }; + for (const record of page.spans ?? []) { + const span = toTraceSpan(record); + if (span && span.rootId === evidence.rootId) + upsert(spans, span); + } + const serverNow = nanosToMs(page.server_time_ns); + return boundTraceEvidence({ + ...evidence, spans, loaded: true, loading: false, hasMore: page.has_more, error: undefined, + pageCursor: newer(page.next_seq, evidence.pageCursor) ? page.next_seq : evidence.pageCursor, + clockOffsetMs: serverNow > 0 ? serverNow - receivedAtMs : evidence.clockOffsetMs, + }); +} +/** Evict whole traces, oldest first, until the span count and byte budget hold. */ +export function boundTraceEvidence(evidence, maxSpans = MAX_TRACE_SPANS, maxBytes = MAX_TRACE_BYTES) { + let spans = evidence.spans; + let count = Object.keys(spans).length; + let bytes = count > maxSpans ? Infinity : encoder.encode(JSON.stringify(spans)).byteLength; + if (count <= maxSpans && bytes <= maxBytes) + return evidence; + const starts = new Map(); + for (const span of Object.values(spans)) + starts.set(span.traceId, Math.min(starts.get(span.traceId) ?? Infinity, span.startMs)); + const traces = [...starts.entries()].sort((a, b) => a[1] - b[1]).map(([traceId]) => traceId); + let truncated = evidence.truncated; + while (traces.length > 1 && (count > maxSpans || bytes > maxBytes)) { + const oldest = traces.shift(); + spans = Object.fromEntries(Object.entries(spans).filter(([, span]) => span.traceId !== oldest)); + count = Object.keys(spans).length; + bytes = encoder.encode(JSON.stringify(spans)).byteLength; + truncated = true; + } + if (count > maxSpans || bytes > maxBytes) { + // One trace alone is over budget: keep its newest spans, drop closed old ones first. + const ordered = Object.values(spans).sort((a, b) => Number(a.endMs === 0) - Number(b.endMs === 0) || a.startMs - b.startMs); + while (ordered.length && (ordered.length > maxSpans || bytes > maxBytes)) { + ordered.shift(); + bytes = encoder.encode(JSON.stringify(ordered)).byteLength; + } + spans = Object.fromEntries(ordered.map(span => [span.id, span])); + truncated = true; + } + return { ...evidence, spans, truncated }; +} +/** Spans of one trace (or every trace) in start order. */ +export function traceSpans(snapshot, traceId) { + const evidence = snapshot.trace; + if (!evidence) + return []; + return Object.values(evidence.spans) + .filter(span => !traceId || span.traceId === traceId) + .sort((a, b) => a.startMs - b.startMs || a.id.localeCompare(b.id)); +} +/** Turn spans that start a trace, newest first: the trace picker's list. */ +export function traceRoots(snapshot) { + const evidence = snapshot.trace; + if (!evidence) + return []; + return Object.values(evidence.spans) + .filter(span => span.kind === 'agent' && span.parentId === '') + .sort((a, b) => b.startMs - a.startMs || a.id.localeCompare(b.id)); +} +/** The daemon's current time as this client can best estimate it. */ +export function serverNowMs(evidence, clientNowMs = Date.now()) { + return clientNowMs + (evidence?.clockOffsetMs ?? 0); +} +//# sourceMappingURL=trace.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/trace.js.map b/vendor/@whip/sdk/dist/trace.js.map new file mode 100644 index 0000000..d4a511c --- /dev/null +++ b/vendor/@whip/sdk/dist/trace.js.map @@ -0,0 +1 @@ +{"version":3,"file":"trace.js","sourceRoot":"","sources":["../src/trace.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SpanPage, SpanRecord } from '@whip/protocol';\nimport type { DeepReadonly, SessionViewSnapshot } from './state.js';\n\n/**\n * Trace evidence is the client's copy of the daemon's span table for one root:\n * durable pages (`trace.page`) merged with live `span.started` / `span.ended`\n * events by span id. Spans are bounded per root; when the cap is reached the\n * oldest traces are evicted whole so the newest trace stays complete and the\n * view says it is truncated. Timing is server-measured; open spans are drawn\n * to the server's clock through `clockOffsetMs`.\n */\nexport type TraceSpanKind = 'agent' | 'llm' | 'tool' | 'host' | 'wait';\nexport type TraceSpanStatus = 'running' | 'ok' | 'error' | 'cancelled' | 'interrupted';\n\nexport interface TraceSpan {\n readonly id: string;\n readonly traceId: string;\n readonly parentId: string;\n readonly rootId: string;\n readonly agentId: string;\n readonly turnId: string;\n readonly kind: TraceSpanKind;\n readonly name: string;\n readonly status: TraceSpanStatus;\n /** Wall clock in milliseconds since the epoch, fractional. */\n readonly startMs: number;\n /** 0 while the span is open. */\n readonly endMs: number;\n readonly attrs: Readonly>;\n readonly links: readonly { readonly traceId: string; readonly spanId: string }[];\n readonly updatedSeq: string;\n}\n\nexport interface TraceEvidence {\n readonly rootId: string;\n /** Page cursor: the last `updated_seq` a durable page returned. */\n readonly pageCursor: string;\n readonly spans: Readonly>;\n readonly loaded: boolean;\n readonly loading: boolean;\n readonly hasMore: boolean;\n readonly truncated: boolean;\n /** Server clock minus client clock at the last page, so open spans grow against the daemon's time. */\n readonly clockOffsetMs: number;\n readonly error?: Error;\n}\n\nexport const MAX_TRACE_SPANS = 4096;\nexport const MAX_TRACE_BYTES = 2 * 1024 * 1024;\nconst encoder = new TextEncoder();\n\nconst kinds = new Set(['agent', 'llm', 'tool', 'host', 'wait']);\nconst statuses = new Set(['running', 'ok', 'error', 'cancelled', 'interrupted']);\nconst nanosToMs = (value: string): number => {\n if (!/^\\d+$/.test(value)) return 0;\n const nanos = BigInt(value);\n return Number(nanos / 1_000_000n) + Number(nanos % 1_000_000n) / 1_000_000;\n};\nconst object = (value: unknown): Record => value && typeof value === 'object' && !Array.isArray(value) ? value as Record : {};\n\n/** Decode one wire record; undefined when it is not a span. */\nexport function toTraceSpan(record: unknown): TraceSpan | undefined {\n const raw = object(record) as Partial;\n if (typeof raw.id !== 'string' || !raw.id || typeof raw.trace_id !== 'string' || typeof raw.root_id !== 'string'\n || typeof raw.agent_id !== 'string' || typeof raw.start_ns !== 'string' || typeof raw.updated_seq !== 'string') return undefined;\n if (!kinds.has(raw.kind as TraceSpanKind) || !statuses.has(raw.status as TraceSpanStatus)) return undefined;\n const links = Array.isArray(raw.links) ? raw.links.flatMap(link => {\n const value = object(link);\n return typeof value.trace_id === 'string' && typeof value.span_id === 'string' ? [{ traceId: value.trace_id, spanId: value.span_id }] : [];\n }) : [];\n return {\n id: raw.id, traceId: raw.trace_id, parentId: raw.parent_id ?? '', rootId: raw.root_id, agentId: raw.agent_id, turnId: raw.turn_id ?? '',\n kind: raw.kind as TraceSpanKind, name: typeof raw.name === 'string' ? raw.name : '', status: raw.status as TraceSpanStatus,\n startMs: nanosToMs(raw.start_ns), endMs: typeof raw.end_ns === 'string' ? nanosToMs(raw.end_ns) : 0,\n attrs: Object.freeze({ ...object(raw.attrs) }), links: Object.freeze(links), updatedSeq: raw.updated_seq,\n };\n}\n\nexport function emptyTraceEvidence(rootId: string): TraceEvidence {\n return { rootId, pageCursor: '0', spans: {}, loaded: false, loading: false, hasMore: false, truncated: false, clockOffsetMs: 0 };\n}\n\nconst newer = (a: string, b: string): boolean => BigInt(a) > BigInt(b);\n\nfunction upsert(spans: Record, span: TraceSpan): boolean {\n const existing = spans[span.id];\n if (existing && !newer(span.updatedSeq, existing.updatedSeq)) return false;\n spans[span.id] = span;\n return true;\n}\n\n/** Fold one journal event; anything but a span event returns the evidence unchanged. */\nexport function observeSpan(evidence: TraceEvidence, event: { kind: string; payload: unknown }): TraceEvidence {\n if (event.kind !== 'span.started' && event.kind !== 'span.ended') return evidence;\n const span = toTraceSpan(event.payload);\n if (!span || span.rootId !== evidence.rootId) return evidence;\n const spans = { ...evidence.spans };\n if (!upsert(spans, span)) return evidence;\n return boundTraceEvidence({ ...evidence, spans });\n}\n\n/** Merge one durable page and record the server clock it was read at. */\nexport function mergeSpanPage(evidence: TraceEvidence, page: SpanPage, receivedAtMs = Date.now()): TraceEvidence {\n const spans = { ...evidence.spans };\n for (const record of page.spans ?? []) {\n const span = toTraceSpan(record);\n if (span && span.rootId === evidence.rootId) upsert(spans, span);\n }\n const serverNow = nanosToMs(page.server_time_ns);\n return boundTraceEvidence({\n ...evidence, spans, loaded: true, loading: false, hasMore: page.has_more, error: undefined,\n pageCursor: newer(page.next_seq, evidence.pageCursor) ? page.next_seq : evidence.pageCursor,\n clockOffsetMs: serverNow > 0 ? serverNow - receivedAtMs : evidence.clockOffsetMs,\n });\n}\n\n/** Evict whole traces, oldest first, until the span count and byte budget hold. */\nexport function boundTraceEvidence(evidence: TraceEvidence, maxSpans = MAX_TRACE_SPANS, maxBytes = MAX_TRACE_BYTES): TraceEvidence {\n let spans = evidence.spans;\n let count = Object.keys(spans).length;\n let bytes = count > maxSpans ? Infinity : encoder.encode(JSON.stringify(spans)).byteLength;\n if (count <= maxSpans && bytes <= maxBytes) return evidence;\n const starts = new Map();\n for (const span of Object.values(spans)) starts.set(span.traceId, Math.min(starts.get(span.traceId) ?? Infinity, span.startMs));\n const traces = [...starts.entries()].sort((a, b) => a[1] - b[1]).map(([traceId]) => traceId);\n let truncated = evidence.truncated;\n while (traces.length > 1 && (count > maxSpans || bytes > maxBytes)) {\n const oldest = traces.shift()!;\n spans = Object.fromEntries(Object.entries(spans).filter(([, span]) => span.traceId !== oldest));\n count = Object.keys(spans).length;\n bytes = encoder.encode(JSON.stringify(spans)).byteLength;\n truncated = true;\n }\n if (count > maxSpans || bytes > maxBytes) {\n // One trace alone is over budget: keep its newest spans, drop closed old ones first.\n const ordered = Object.values(spans).sort((a, b) => Number(a.endMs === 0) - Number(b.endMs === 0) || a.startMs - b.startMs);\n while (ordered.length && (ordered.length > maxSpans || bytes > maxBytes)) {\n ordered.shift();\n bytes = encoder.encode(JSON.stringify(ordered)).byteLength;\n }\n spans = Object.fromEntries(ordered.map(span => [span.id, span]));\n truncated = true;\n }\n return { ...evidence, spans, truncated };\n}\n\n/** Spans of one trace (or every trace) in start order. */\nexport function traceSpans(snapshot: DeepReadonly, traceId?: string): readonly TraceSpan[] {\n const evidence = snapshot.trace;\n if (!evidence) return [];\n return Object.values(evidence.spans)\n .filter(span => !traceId || span.traceId === traceId)\n .sort((a, b) => a.startMs - b.startMs || a.id.localeCompare(b.id)) as TraceSpan[];\n}\n\n/** Turn spans that start a trace, newest first: the trace picker's list. */\nexport function traceRoots(snapshot: DeepReadonly): readonly TraceSpan[] {\n const evidence = snapshot.trace;\n if (!evidence) return [];\n return Object.values(evidence.spans)\n .filter(span => span.kind === 'agent' && span.parentId === '')\n .sort((a, b) => b.startMs - a.startMs || a.id.localeCompare(b.id)) as TraceSpan[];\n}\n\n/** The daemon's current time as this client can best estimate it. */\nexport function serverNowMs(evidence: DeepReadonly | undefined, clientNowMs = Date.now()): number {\n return clientNowMs + (evidence?.clockOffsetMs ?? 0);\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/transport.d.ts b/vendor/@whip/sdk/dist/transport.d.ts new file mode 100644 index 0000000..5277c16 --- /dev/null +++ b/vendor/@whip/sdk/dist/transport.d.ts @@ -0,0 +1,14 @@ +export interface Transport { + readonly kind: 'websocket' | 'unix'; + readonly bufferedAmount: number; + readonly httpEndpoint?: string; + send(message: string): void; + close(): void; +} +export interface TransportHandlers { + message(message: string): void; + close(error: Error): void; +} +export type TransportFactory = (handlers: TransportHandlers, signal: AbortSignal) => Promise; +/** One native WebSocket text message per JSON-RPC envelope. */ +export declare function webSocket(endpoint: string): TransportFactory; diff --git a/vendor/@whip/sdk/dist/transport.js b/vendor/@whip/sdk/dist/transport.js new file mode 100644 index 0000000..498e7ef --- /dev/null +++ b/vendor/@whip/sdk/dist/transport.js @@ -0,0 +1,72 @@ +import { WhipError, abortError } from './errors.js'; +/** One native WebSocket text message per JSON-RPC envelope. */ +export function webSocket(endpoint) { + const url = new URL(endpoint); + if (url.protocol === 'http:') + url.protocol = 'ws:'; + if (url.protocol === 'https:') + url.protocol = 'wss:'; + if (url.protocol !== 'ws:' && url.protocol !== 'wss:') + throw new TypeError('Expected a WebSocket or HTTP endpoint'); + if (url.username || url.password || url.hash) + throw new TypeError('Endpoint must not contain credentials or a fragment'); + if (url.pathname === '/') + url.pathname = '/api/v3/ws'; + const base = new URL(url); + base.protocol = url.protocol === 'wss:' ? 'https:' : 'http:'; + base.pathname = '/'; + base.search = ''; + return (handlers, signal) => new Promise((resolve, reject) => { + signal.throwIfAborted(); + const socket = new WebSocket(url.href); + let opened = false; + let closed = false; + const finish = (error) => { + if (closed) + return; + closed = true; + signal.removeEventListener('abort', abort); + socket.onopen = socket.onmessage = socket.onerror = socket.onclose = null; + if (socket.readyState < WebSocket.CLOSING) + socket.close(); + if (!opened) + reject(error); + else + handlers.close(error); + }; + const abort = () => finish(abortError(signal)); + signal.addEventListener('abort', abort, { once: true }); + socket.onopen = () => { + opened = true; + resolve({ + kind: 'websocket', httpEndpoint: base.origin, + // React Native declares this property but does not expose queue bytes. + get bufferedAmount() { return socket.bufferedAmount ?? 0; }, + send(message) { + if (closed || socket.readyState !== WebSocket.OPEN) + throw new WhipError('disconnected', 'WebSocket is disconnected'); + socket.send(message); + }, + close() { finish(new WhipError('disconnected', 'WebSocket closed')); }, + }); + }; + socket.onmessage = event => { + if (typeof event.data !== 'string') { + finish(new WhipError('invalid_response', 'Expected a WebSocket text message')); + return; + } + handlers.message(event.data); + }; + // React Native emits a bare error before a synchronous close carrying the + // native failure reason. Give that close priority without leaving other + // implementations that emit only error waiting for initialization timeout. + socket.onerror = () => queueMicrotask(() => finish(new WhipError('disconnected', 'WebSocket connection failed'))); + socket.onclose = event => { + const code = Number.isInteger(event.code) && event.code >= 1000 && event.code <= 4999 ? ` (${event.code})` : ''; + const reason = typeof event.reason === 'string' ? event.reason.slice(0, 256) + .replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, ' ').replace(/\s+/g, ' ').trim().replace(/[\uD800-\uDBFF]$/u, '') : ''; + finish(new WhipError('disconnected', `WebSocket closed${code}${reason ? `: ${reason}` : ''}`)); + }; + }); +} +//# sourceMappingURL=transport.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/transport.js.map b/vendor/@whip/sdk/dist/transport.js.map new file mode 100644 index 0000000..3ae7f9b --- /dev/null +++ b/vendor/@whip/sdk/dist/transport.js.map @@ -0,0 +1 @@ +{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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","sourcesContent":["import { WhipError, abortError } from './errors.js';\n\nexport interface Transport {\n readonly kind: 'websocket' | 'unix';\n readonly bufferedAmount: number;\n readonly httpEndpoint?: string;\n send(message: string): void;\n close(): void;\n}\nexport interface TransportHandlers {\n message(message: string): void;\n close(error: Error): void;\n}\nexport type TransportFactory = (handlers: TransportHandlers, signal: AbortSignal) => Promise;\n\n/** One native WebSocket text message per JSON-RPC envelope. */\nexport function webSocket(endpoint: string): TransportFactory {\n const url = new URL(endpoint);\n if (url.protocol === 'http:') url.protocol = 'ws:';\n if (url.protocol === 'https:') url.protocol = 'wss:';\n if (url.protocol !== 'ws:' && url.protocol !== 'wss:') throw new TypeError('Expected a WebSocket or HTTP endpoint');\n if (url.username || url.password || url.hash) throw new TypeError('Endpoint must not contain credentials or a fragment');\n if (url.pathname === '/') url.pathname = '/api/v3/ws';\n const base = new URL(url);\n base.protocol = url.protocol === 'wss:' ? 'https:' : 'http:';\n base.pathname = '/'; base.search = '';\n return (handlers, signal) => new Promise((resolve, reject) => {\n signal.throwIfAborted();\n const socket = new WebSocket(url.href);\n let opened = false;\n let closed = false;\n const finish = (error: Error) => {\n if (closed) return;\n closed = true;\n signal.removeEventListener('abort', abort);\n socket.onopen = socket.onmessage = socket.onerror = socket.onclose = null;\n if (socket.readyState < WebSocket.CLOSING) socket.close();\n if (!opened) reject(error);\n else handlers.close(error);\n };\n const abort = () => finish(abortError(signal));\n signal.addEventListener('abort', abort, { once: true });\n socket.onopen = () => {\n opened = true;\n resolve({\n kind: 'websocket', httpEndpoint: base.origin,\n // React Native declares this property but does not expose queue bytes.\n get bufferedAmount() { return socket.bufferedAmount ?? 0; },\n send(message) {\n if (closed || socket.readyState !== WebSocket.OPEN) throw new WhipError('disconnected', 'WebSocket is disconnected');\n socket.send(message);\n },\n close() { finish(new WhipError('disconnected', 'WebSocket closed')); },\n });\n };\n socket.onmessage = event => {\n if (typeof event.data !== 'string') { finish(new WhipError('invalid_response', 'Expected a WebSocket text message')); return; }\n handlers.message(event.data);\n };\n // React Native emits a bare error before a synchronous close carrying the\n // native failure reason. Give that close priority without leaving other\n // implementations that emit only error waiting for initialization timeout.\n socket.onerror = () => queueMicrotask(() => finish(new WhipError('disconnected', 'WebSocket connection failed')));\n socket.onclose = event => {\n const code = Number.isInteger(event.code) && event.code >= 1000 && event.code <= 4999 ? ` (${event.code})` : '';\n const reason = typeof event.reason === 'string' ? event.reason.slice(0, 256)\n .replace(/[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}]/gu, ' ').replace(/\\s+/g, ' ').trim().replace(/[\\uD800-\\uDBFF]$/u, '') : '';\n finish(new WhipError('disconnected', `WebSocket closed${code}${reason ? `: ${reason}` : ''}`));\n };\n });\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/turn.d.ts b/vendor/@whip/sdk/dist/turn.d.ts new file mode 100644 index 0000000..60143a2 --- /dev/null +++ b/vendor/@whip/sdk/dist/turn.d.ts @@ -0,0 +1,184 @@ +import type { CommandResult, StreamEvent, SubmitPayload } from '@whip/protocol'; +import type { SdkEvent } from './client.js'; +import type { Session } from './session.js'; +export interface RunOptions { + /** Cancels the turn and stops observing when aborted. */ + signal?: AbortSignal; + /** Also yield the events of agents spawned during this turn, tagged with their agentId. Child lifecycle events are always yielded. */ + includeChildren?: boolean; + /** Bounds the unread event buffer; exceeding it fails the iterable, never the result. */ + maxMessages?: number; + maxBytes?: number; + /** How long result() waits for the end event after the command settled. */ + endTimeoutMs?: number; +} +export type TurnStatus = 'succeeded' | 'failed' | 'cancelled' | 'interrupted'; +export type TurnFailure = NonNullable; +export type TurnUsage = NonNullable; +export interface QuestionOption { + readonly label: string; + readonly description?: string; + readonly recommended?: boolean; +} +export interface QuestionSet { + readonly question: string; + readonly options: readonly QuestionOption[]; + readonly multiple: boolean; +} +/** One entry of a batched answer; null skips that question. */ +export type QuestionAnswer = { + readonly answer: readonly string[]; + readonly dismissed?: boolean; +} | null; +/** An open user.ask prompt. answer and dismiss post once; repeated calls return the first promise. */ +export interface QuestionEvent extends TurnEventBase { + readonly type: 'question'; + readonly id: string; + readonly question: string; + readonly options: readonly QuestionOption[]; + readonly multiple: boolean; + /** Present when the agent asked several questions at once; answer then takes one entry per question. */ + readonly questions?: readonly QuestionSet[]; + answer(answers: readonly string[] | readonly QuestionAnswer[]): Promise; + dismiss(): Promise; +} +/** An open permission prompt. allow and deny post one decision; repeated calls return the first promise. */ +export interface PermissionEvent extends TurnEventBase { + readonly type: 'permission'; + readonly id: string; + readonly operation: string; + readonly command: string; + readonly rule: string; + readonly path: string; + /** remember installs the prompt's rule for the session tree or globally. */ + allow(options?: { + remember?: 'tree' | 'global'; + }): Promise; + deny(reason?: string): Promise; +} +/** Every turn event carries its ordinal and the agent and turn it belongs to. */ +export interface TurnEventBase { + readonly seq: string; + readonly agentId: string; + readonly turnId: string; +} +export type TurnEvent = TurnEventBase & ({ + readonly type: 'text'; + readonly delta: string; +} | { + readonly type: 'reasoning'; + readonly delta: string; +} | { + readonly type: 'cell'; + readonly id: string; + readonly status: 'called' | 'running' | 'completed'; + readonly code?: string; + readonly output?: string; + readonly result?: string; +} | { + readonly type: 'host'; + readonly id: string; + readonly invocationId: string; + readonly operation: string; + readonly summary: string; + readonly status: 'running' | 'completed' | 'failed' | 'cancelled'; + readonly error?: string; + readonly duration?: string; +} | { + readonly type: 'progress'; + readonly id: string; + readonly operation: string; + readonly text: string; +} | { + readonly type: 'hook'; + readonly hook: string; + readonly operation: string; + readonly decision: string; + readonly reason: string; +} | QuestionEvent | PermissionEvent | { + readonly type: 'child'; + readonly childId: string; + readonly kind: string; + readonly status: string; +} | { + readonly type: 'notice'; + readonly text: string; +} +/** The provider stream failed after output; the client discarded `discarded` streamed characters and is regenerating the message. */ + | { + readonly type: 'discard'; + readonly discarded: number; +} | { + readonly type: 'usage'; + readonly usage: TurnUsage; +} | { + readonly type: 'end'; + readonly status: TurnStatus; + readonly error?: string; +} | { + readonly type: 'raw'; + readonly event: SdkEvent; +}); +export type TurnResult = { + readonly status: 'succeeded'; + readonly turnId: string; + readonly text: string; + readonly output: Output; + readonly usage?: TurnUsage; +} | { + readonly status: 'failed' | 'cancelled' | 'interrupted'; + readonly turnId?: string; + readonly text?: string; + readonly failure: TurnFailure; + readonly usage?: TurnUsage; +}; +/** Build a question event whose reply methods post through the session exactly once. */ +export declare function questionEvent(session: Session, base: TurnEventBase, payload: Record): QuestionEvent; +/** Build a permission event whose decision methods post through the client exactly once. */ +export declare function permissionEvent(session: Session, base: TurnEventBase, payload: Record): PermissionEvent; +/** + * One root turn: the submit command plus every event the daemon journals for + * it, mapped to a small typed union. The subscription starts before the + * command so nothing is missed; the turn is identified by the turn.started + * whose inbox sequence is the command's ingress sequence. The command outcome + * is authoritative; the event stream is best effort. + */ +export declare class Turn implements AsyncIterable { + private readonly session; + private readonly payload; + private readonly options; + private readonly queue; + private queuedBytes; + private waiter?; + private iterableError?; + private iterableClosed; + private subscription?; + private command?; + private readonly started; + private readonly pump; + private readonly ended; + private resolveEnded; + private turnIdentity?; + private resolveTurn; + private rejectTurn; + private readonly children; + private accumulated; + private lastUsage?; + private endStatus?; + /** Resolves once the daemon has started the turn; rejects when the command settles without one. */ + readonly turnId: Promise; + constructor(session: Session, payload: SubmitPayload, options?: RunOptions); + private begin; + private map; + private push; + /** Ends iteration; the result still resolves from the command. */ + private fail; + private finish; + [Symbol.asyncIterator](): AsyncIterator; + /** The command's terminal outcome, joined with the end event when the turn ran. */ + result(): Promise>; + /** The final text, or a rejection carrying the failure. */ + text(): Promise; + /** Command-targeted cancellation; resolves once the daemon accepted it. */ + cancel(): Promise; +} diff --git a/vendor/@whip/sdk/dist/turn.js b/vendor/@whip/sdk/dist/turn.js new file mode 100644 index 0000000..5cb932e --- /dev/null +++ b/vendor/@whip/sdk/dist/turn.js @@ -0,0 +1,291 @@ +import { DeliveryUncertainError, WhipError, abortError, asError } from './errors.js'; +import { executionCode } from './executions.js'; +import { object } from './util.js'; +const text = (value) => typeof value === 'string' ? value : ''; +/** Build a question event whose reply methods post through the session exactly once. */ +export function questionEvent(session, base, payload) { + const id = text(payload.question_id); + let posted; + const post = (answers, dismissed) => posted ??= (async () => { + const batched = answers.some(entry => entry === null || typeof entry === 'object'); + const handle = batched + ? session.answerQuestions(id, answers.map(entry => entry ? { answer: [...entry.answer], dismissed: entry.dismissed } : null)) + : session.answerQuestion(id, [...answers], dismissed); + const outcome = await handle.result(); + if (outcome.status !== 'succeeded') + throw new WhipError(outcome.failure?.data?.kind ?? 'execution_failed', outcome.failure?.message ?? `question answer ${outcome.status}`, { cause: outcome.failure ?? undefined }); + })(); + return { + ...base, type: 'question', id, question: text(payload.question), options: Array.isArray(payload.options) ? payload.options : [], multiple: payload.multiple === true, + ...(Array.isArray(payload.questions) && payload.questions.length > 0 ? { questions: payload.questions } : {}), + answer: answers => post(answers, false), + dismiss: () => post([], true), + }; +} +/** Build a permission event whose decision methods post through the client exactly once. */ +export function permissionEvent(session, base, payload) { + const id = text(payload.permission_id) || text(payload.id); + let posted; + const post = (allow, extra) => posted ??= (async () => { + await session.client.permissions.decide({ root_id: session.rootId, permission_id: id, allow, ...extra }); + })(); + return { + ...base, type: 'permission', id, operation: text(payload.operation), command: text(payload.command), rule: text(payload.rule), path: text(payload.canonical_path), + allow: (options = {}) => post(true, options.remember ? { remember: options.remember } : {}), + deny: reason => post(false, reason ? { reason } : {}), + }; +} +const childKinds = ['agent.admitted', 'agent.turn.started', 'agent.turn.succeeded', 'agent.turn.failed', 'agent.turn.cancelled', 'agent.turn.interrupted', 'agent.subtree.stopped', 'agent.subtree.deleted', 'agent.prompt.queued']; +const endKinds = { 'turn.succeeded': 'succeeded', 'turn.failed': 'failed', 'turn.cancelled': 'cancelled', 'turn.interrupted': 'interrupted' }; +/** + * One root turn: the submit command plus every event the daemon journals for + * it, mapped to a small typed union. The subscription starts before the + * command so nothing is missed; the turn is identified by the turn.started + * whose inbox sequence is the command's ingress sequence. The command outcome + * is authoritative; the event stream is best effort. + */ +export class Turn { + session; + payload; + options; + queue = []; + queuedBytes = 0; + waiter; + iterableError; + iterableClosed = false; + subscription; + command; + started; + pump; + ended; + resolveEnded; + turnIdentity; + resolveTurn; + rejectTurn; + children = new Set(); + accumulated = ''; + lastUsage; + endStatus; + /** Resolves once the daemon has started the turn; rejects when the command settles without one. */ + turnId; + constructor(session, payload, options = {}) { + this.session = session; + this.payload = payload; + this.options = options; + this.turnId = new Promise((resolve, reject) => { this.resolveTurn = resolve; this.rejectTurn = reject; }); + void this.turnId.catch(() => { }); + this.ended = new Promise(resolve => { this.resolveEnded = resolve; }); + let resolveStarted; + let rejectStarted; + this.started = new Promise((resolve, reject) => { resolveStarted = resolve; rejectStarted = reject; }); + void this.started.catch(() => { }); + this.pump = this.begin(resolveStarted, rejectStarted).catch(error => { this.fail(asError(error)); }); + if (options.signal) { + const abort = () => { void this.cancel().catch(() => { }); this.fail(abortError(options.signal)); }; + if (options.signal.aborted) + abort(); + else + options.signal.addEventListener('abort', abort, { once: true }); + } + } + async begin(resolveStarted, rejectStarted) { + const { session } = this; + let ingressSeq; + try { + const snapshot = await session.snapshot(); + this.subscription = await session.client.events.subscribe(session.rootId, snapshot.cursor, { maxMessages: this.options.maxMessages, maxBytes: this.options.maxBytes }); + this.command = session.submit(this.payload); + resolveStarted(this.command); + try { + ingressSeq = (await this.command.accepted()).ingress_seq; + } + catch (error) { + if (!(error instanceof DeliveryUncertainError)) + throw error; + } + } + catch (error) { + rejectStarted(asError(error)); + throw error; + } + for await (const event of this.subscription) { + const payload = object(event.payload) ? event.payload : {}; + const agentId = text(payload.agent_id); + const eventTurn = text(payload.turn_id); + if (this.turnIdentity === undefined) { + // The turn whose inbox sequence is this command's ingress sequence; with acceptance unknown, the next root turn. + if (event.kind === 'turn.started' && agentId === session.rootId && (ingressSeq === undefined || text(payload.inbox_seq) === ingressSeq)) { + this.turnIdentity = eventTurn; + this.resolveTurn(eventTurn); + this.push({ type: 'raw', event, seq: event.seq, agentId, turnId: eventTurn }); + } + continue; + } + const isRoot = agentId === session.rootId; + const child = childKinds.includes(event.kind) && agentId !== '' && !isRoot; + if (event.kind === 'agent.admitted' && child) + this.children.add(agentId); + const belongs = eventTurn === this.turnIdentity || (eventTurn === '' && isRoot) || (this.children.has(agentId) && (child || this.options.includeChildren === true)); + if (!belongs) + continue; + const mapped = this.map(event, payload, agentId, eventTurn || this.turnIdentity, child); + if (mapped) + this.push(mapped); + if (mapped?.type === 'end') + break; + } + this.finish(); + } + map(event, payload, agentId, turnId, child) { + const base = { seq: event.seq, agentId, turnId }; + if (child) + return { ...base, type: 'child', childId: agentId, kind: event.kind, status: text(payload.status) }; + const isRoot = agentId === this.session.rootId; + switch (event.kind) { + case 'stream.text': + if (isRoot) + this.accumulated += text(payload.text); + return { ...base, type: 'text', delta: text(payload.text) }; + case 'stream.reasoning': return { ...base, type: 'reasoning', delta: text(payload.text) }; + case 'stream.tool.call': + case 'stream.tool.started': + case 'stream.tool.output': + case 'stream.tool.completed': { + if (event.kind !== 'stream.tool.output' && payload.name !== 'rlm_exec') + return { ...base, type: 'raw', event }; + const id = text(payload.id); + if (event.kind === 'stream.tool.call') + return { ...base, type: 'cell', id, status: 'called', code: executionCode(text(payload.args)) }; + if (event.kind === 'stream.tool.started') + return { ...base, type: 'cell', id, status: 'running', code: executionCode(text(payload.args)) }; + if (event.kind === 'stream.tool.output') + return { ...base, type: 'cell', id, status: 'running', output: text(payload.text) }; + return { ...base, type: 'cell', id, status: 'completed', result: text(payload.result) }; + } + case 'stream.cell.host.started': + return { ...base, type: 'host', id: text(payload.id), invocationId: text(payload.invocation_id), operation: text(payload.name), summary: text(payload.args), status: 'running' }; + case 'stream.cell.host': { + const hostStatus = text(payload.host_status); + const status = hostStatus === 'cancelled' ? 'cancelled' : hostStatus === 'failed' || text(payload.result) !== '' ? 'failed' : 'completed'; + return { ...base, type: 'host', id: text(payload.id), invocationId: text(payload.invocation_id), operation: text(payload.name), summary: text(payload.args), status, ...(text(payload.result) ? { error: text(payload.result) } : {}), duration: text(payload.text) }; + } + case 'stream.tool.progress': return { ...base, type: 'progress', id: text(payload.id), operation: text(payload.name), text: text(payload.text) }; + case 'stream.hook.decision': return { ...base, type: 'hook', hook: text(payload.name), operation: text(payload.args), decision: text(payload.text), reason: text(payload.result) }; + case 'question.pending': return questionEvent(this.session, base, payload); + case 'permission.pending': return permissionEvent(this.session, base, payload); + case 'stream.notice': return { ...base, type: 'notice', text: text(payload.text) }; + case 'stream.discard': + if (isRoot) + this.accumulated = ''; + return { ...base, type: 'discard', discarded: Number(text(payload.text)) || 0 }; + case 'stream.usage': { + if (!object(payload.usage)) + return { ...base, type: 'raw', event }; + const usage = payload.usage; + if (isRoot) + this.lastUsage = usage; + return { ...base, type: 'usage', usage }; + } + default: { + const status = endKinds[event.kind]; + if (status && isRoot) { + this.endStatus = status; + return { ...base, type: 'end', status, ...(text(payload.error) ? { error: text(payload.error) } : {}) }; + } + return { ...base, type: 'raw', event }; + } + } + } + push(event) { + if (this.iterableClosed || this.iterableError) + return; + if (this.waiter) { + const waiter = this.waiter; + this.waiter = undefined; + waiter.resolve({ done: false, value: event }); + return; + } + const bytes = JSON.stringify(event).length; + if (this.queue.length >= (this.options.maxMessages ?? 1024) || this.queuedBytes + bytes > (this.options.maxBytes ?? 8 << 20)) { + this.iterableError = new WhipError('resynchronization_required', 'Turn event consumer fell behind; the result still settles from the command'); + this.queue.length = 0; + this.queuedBytes = 0; + return; + } + this.queue.push(event); + this.queuedBytes += bytes; + } + /** Ends iteration; the result still resolves from the command. */ + fail(error) { + if (!this.iterableError && !this.iterableClosed) { + this.iterableError = error; + this.waiter?.reject(error); + this.waiter = undefined; + } + if (this.turnIdentity === undefined) + this.rejectTurn(error); + void this.subscription?.dispose().catch(() => { }); + this.resolveEnded(); + } + finish() { + this.iterableClosed = true; + this.waiter?.resolve({ done: true, value: undefined }); + this.waiter = undefined; + if (this.turnIdentity === undefined) + this.rejectTurn(new WhipError('execution_failed', 'The turn ended before it started')); + void this.subscription?.dispose().catch(() => { }); + this.resolveEnded(); + } + [Symbol.asyncIterator]() { + return { + next: () => { + const next = this.queue.shift(); + if (next) { + this.queuedBytes -= JSON.stringify(next).length; + return Promise.resolve({ done: false, value: next }); + } + if (this.iterableError) + return Promise.reject(this.iterableError); + if (this.iterableClosed) + return Promise.resolve({ done: true, value: undefined }); + if (this.waiter) + return Promise.reject(new WhipError('invalid_arguments', 'A turn supports one iterator consumer')); + return new Promise((resolve, reject) => { this.waiter = { resolve, reject }; }); + }, + return: async () => { this.iterableClosed = true; this.waiter = undefined; return { done: true, value: undefined }; }, + }; + } + /** The command's terminal outcome, joined with the end event when the turn ran. */ + async result() { + const command = await this.started; + const outcome = await command.result({ signal: this.options.signal }); + if (this.turnIdentity !== undefined && !this.endStatus) { + await Promise.race([this.ended, this.pump, new Promise(resolve => setTimeout(resolve, this.options.endTimeoutMs ?? 5_000))]); + } + // The command is the truth: once it has settled, nothing further can start + // this turn, and an end event that has not arrived by now is not waited for. + if (!this.iterableClosed && !this.iterableError) + this.finish(); + const result = object(outcome.result) ? outcome.result : {}; + const usage = this.lastUsage ? { usage: this.lastUsage } : {}; + if (outcome.status === 'succeeded') { + return { status: 'succeeded', turnId: this.turnIdentity ?? '', text: text(result.text) || this.accumulated, output: result.output, ...usage }; + } + const status = outcome.status === 'cancelled' || outcome.status === 'interrupted' ? outcome.status : 'failed'; + const failure = outcome.failure ?? { code: -32000, message: `turn ${outcome.status}`, data: { kind: 'execution_failed' } }; + return { status, ...(this.turnIdentity !== undefined ? { turnId: this.turnIdentity } : {}), ...(this.accumulated ? { text: this.accumulated } : {}), failure, ...usage }; + } + /** The final text, or a rejection carrying the failure. */ + async text() { + const result = await this.result(); + if (result.status === 'succeeded') + return result.text; + throw new WhipError(result.failure.data?.kind ?? 'execution_failed', result.failure.message, { cause: result.failure }); + } + /** Command-targeted cancellation; resolves once the daemon accepted it. */ + async cancel() { + const command = await this.started; + await command.cancel().accepted(); + } +} +//# sourceMappingURL=turn.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/turn.js.map b/vendor/@whip/sdk/dist/turn.js.map new file mode 100644 index 0000000..11c9715 --- /dev/null +++ b/vendor/@whip/sdk/dist/turn.js.map @@ -0,0 +1 @@ +{"version":3,"file":"turn.js","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CommandResult, StreamEvent, SubmitPayload } from '@whip/protocol';\nimport type { SdkEvent } from './client.js';\nimport type { CommandHandle } from './command.js';\nimport { DeliveryUncertainError, WhipError, abortError, asError } from './errors.js';\nimport { executionCode } from './executions.js';\nimport type { Session } from './session.js';\nimport type { Subscription } from './subscription.js';\nimport { object } from './util.js';\n\nexport interface RunOptions {\n /** Cancels the turn and stops observing when aborted. */\n signal?: AbortSignal;\n /** Also yield the events of agents spawned during this turn, tagged with their agentId. Child lifecycle events are always yielded. */\n includeChildren?: boolean;\n /** Bounds the unread event buffer; exceeding it fails the iterable, never the result. */\n maxMessages?: number;\n maxBytes?: number;\n /** How long result() waits for the end event after the command settled. */\n endTimeoutMs?: number;\n}\n\nexport type TurnStatus = 'succeeded' | 'failed' | 'cancelled' | 'interrupted';\nexport type TurnFailure = NonNullable;\nexport type TurnUsage = NonNullable;\nexport interface QuestionOption { readonly label: string; readonly description?: string; readonly recommended?: boolean }\nexport interface QuestionSet { readonly question: string; readonly options: readonly QuestionOption[]; readonly multiple: boolean }\n/** One entry of a batched answer; null skips that question. */\nexport type QuestionAnswer = { readonly answer: readonly string[]; readonly dismissed?: boolean } | null;\n\n/** An open user.ask prompt. answer and dismiss post once; repeated calls return the first promise. */\nexport interface QuestionEvent extends TurnEventBase {\n readonly type: 'question';\n readonly id: string;\n readonly question: string;\n readonly options: readonly QuestionOption[];\n readonly multiple: boolean;\n /** Present when the agent asked several questions at once; answer then takes one entry per question. */\n readonly questions?: readonly QuestionSet[];\n answer(answers: readonly string[] | readonly QuestionAnswer[]): Promise;\n dismiss(): Promise;\n}\n/** An open permission prompt. allow and deny post one decision; repeated calls return the first promise. */\nexport interface PermissionEvent extends TurnEventBase {\n readonly type: 'permission';\n readonly id: string;\n readonly operation: string;\n readonly command: string;\n readonly rule: string;\n readonly path: string;\n /** remember installs the prompt's rule for the session tree or globally. */\n allow(options?: { remember?: 'tree' | 'global' }): Promise;\n deny(reason?: string): Promise;\n}\n\n/** Every turn event carries its ordinal and the agent and turn it belongs to. */\nexport interface TurnEventBase { readonly seq: string; readonly agentId: string; readonly turnId: string }\nexport type TurnEvent = TurnEventBase & (\n | { readonly type: 'text'; readonly delta: string }\n | { readonly type: 'reasoning'; readonly delta: string }\n | { readonly type: 'cell'; readonly id: string; readonly status: 'called' | 'running' | 'completed'; readonly code?: string; readonly output?: string; readonly result?: string }\n | { readonly type: 'host'; readonly id: string; readonly invocationId: string; readonly operation: string; readonly summary: string; readonly status: 'running' | 'completed' | 'failed' | 'cancelled'; readonly error?: string; readonly duration?: string }\n | { readonly type: 'progress'; readonly id: string; readonly operation: string; readonly text: string }\n | { readonly type: 'hook'; readonly hook: string; readonly operation: string; readonly decision: string; readonly reason: string }\n | QuestionEvent\n | PermissionEvent\n | { readonly type: 'child'; readonly childId: string; readonly kind: string; readonly status: string }\n | { readonly type: 'notice'; readonly text: string }\n /** The provider stream failed after output; the client discarded `discarded` streamed characters and is regenerating the message. */\n | { readonly type: 'discard'; readonly discarded: number }\n | { readonly type: 'usage'; readonly usage: TurnUsage }\n | { readonly type: 'end'; readonly status: TurnStatus; readonly error?: string }\n | { readonly type: 'raw'; readonly event: SdkEvent }\n);\n\nexport type TurnResult =\n | { readonly status: 'succeeded'; readonly turnId: string; readonly text: string; readonly output: Output; readonly usage?: TurnUsage }\n | { readonly status: 'failed' | 'cancelled' | 'interrupted'; readonly turnId?: string; readonly text?: string; readonly failure: TurnFailure; readonly usage?: TurnUsage };\n\nconst text = (value: unknown): string => typeof value === 'string' ? value : '';\n\n/** Build a question event whose reply methods post through the session exactly once. */\nexport function questionEvent(session: Session, base: TurnEventBase, payload: Record): QuestionEvent {\n const id = text(payload.question_id);\n let posted: Promise | undefined;\n const post = (answers: readonly string[] | readonly QuestionAnswer[], dismissed: boolean) => posted ??= (async () => {\n const batched = answers.some(entry => entry === null || typeof entry === 'object');\n const handle = batched\n ? session.answerQuestions(id, (answers as readonly QuestionAnswer[]).map(entry => entry ? { answer: [...entry.answer], dismissed: entry.dismissed } : null))\n : session.answerQuestion(id, [...(answers as readonly string[])], dismissed);\n const outcome = await handle.result();\n if (outcome.status !== 'succeeded') throw new WhipError(outcome.failure?.data?.kind ?? 'execution_failed', outcome.failure?.message ?? `question answer ${outcome.status}`, { cause: outcome.failure ?? undefined });\n })();\n return {\n ...base, type: 'question', id, question: text(payload.question), options: Array.isArray(payload.options) ? payload.options as QuestionOption[] : [], multiple: payload.multiple === true,\n ...(Array.isArray(payload.questions) && payload.questions.length > 0 ? { questions: payload.questions as QuestionSet[] } : {}),\n answer: answers => post(answers, false),\n dismiss: () => post([], true),\n };\n}\n\n/** Build a permission event whose decision methods post through the client exactly once. */\nexport function permissionEvent(session: Session, base: TurnEventBase, payload: Record): PermissionEvent {\n const id = text(payload.permission_id) || text(payload.id);\n let posted: Promise | undefined;\n const post = (allow: boolean, extra: { reason?: string; remember?: string }) => posted ??= (async () => {\n await session.client.permissions.decide({ root_id: session.rootId, permission_id: id, allow, ...extra });\n })();\n return {\n ...base, type: 'permission', id, operation: text(payload.operation), command: text(payload.command), rule: text(payload.rule), path: text(payload.canonical_path),\n allow: (options = {}) => post(true, options.remember ? { remember: options.remember } : {}),\n deny: reason => post(false, reason ? { reason } : {}),\n };\n}\nconst childKinds = ['agent.admitted', 'agent.turn.started', 'agent.turn.succeeded', 'agent.turn.failed', 'agent.turn.cancelled', 'agent.turn.interrupted', 'agent.subtree.stopped', 'agent.subtree.deleted', 'agent.prompt.queued'];\nconst endKinds: Record = { 'turn.succeeded': 'succeeded', 'turn.failed': 'failed', 'turn.cancelled': 'cancelled', 'turn.interrupted': 'interrupted' };\n\n/**\n * One root turn: the submit command plus every event the daemon journals for\n * it, mapped to a small typed union. The subscription starts before the\n * command so nothing is missed; the turn is identified by the turn.started\n * whose inbox sequence is the command's ingress sequence. The command outcome\n * is authoritative; the event stream is best effort.\n */\nexport class Turn implements AsyncIterable {\n private readonly queue: TurnEvent[] = [];\n private queuedBytes = 0;\n private waiter?: { resolve(value: IteratorResult): void; reject(error: Error): void };\n private iterableError?: Error;\n private iterableClosed = false;\n private subscription?: Subscription;\n private command?: CommandHandle<'submit'>;\n private readonly started: Promise>;\n private readonly pump: Promise;\n private readonly ended: Promise;\n private resolveEnded!: () => void;\n private turnIdentity?: string;\n private resolveTurn!: (turnId: string) => void;\n private rejectTurn!: (error: Error) => void;\n private readonly children = new Set();\n private accumulated = '';\n private lastUsage?: TurnUsage;\n private endStatus?: TurnStatus;\n /** Resolves once the daemon has started the turn; rejects when the command settles without one. */\n readonly turnId: Promise;\n\n constructor(private readonly session: Session, private readonly payload: SubmitPayload, private readonly options: RunOptions = {}) {\n this.turnId = new Promise((resolve, reject) => { this.resolveTurn = resolve; this.rejectTurn = reject; });\n void this.turnId.catch(() => {});\n this.ended = new Promise(resolve => { this.resolveEnded = resolve; });\n let resolveStarted!: (command: CommandHandle<'submit'>) => void;\n let rejectStarted!: (error: Error) => void;\n this.started = new Promise((resolve, reject) => { resolveStarted = resolve; rejectStarted = reject; });\n void this.started.catch(() => {});\n this.pump = this.begin(resolveStarted, rejectStarted).catch(error => { this.fail(asError(error)); });\n if (options.signal) {\n const abort = () => { void this.cancel().catch(() => {}); this.fail(abortError(options.signal)); };\n if (options.signal.aborted) abort();\n else options.signal.addEventListener('abort', abort, { once: true });\n }\n }\n\n private async begin(resolveStarted: (command: CommandHandle<'submit'>) => void, rejectStarted: (error: Error) => void): Promise {\n const { session } = this;\n let ingressSeq: string | undefined;\n try {\n const snapshot = await session.snapshot();\n this.subscription = await session.client.events.subscribe(session.rootId, snapshot.cursor, { maxMessages: this.options.maxMessages, maxBytes: this.options.maxBytes });\n this.command = session.submit(this.payload);\n resolveStarted(this.command);\n try { ingressSeq = (await this.command.accepted()).ingress_seq; }\n catch (error) { if (!(error instanceof DeliveryUncertainError)) throw error; }\n } catch (error) {\n rejectStarted(asError(error));\n throw error;\n }\n for await (const event of this.subscription) {\n const payload = object(event.payload) ? event.payload : {};\n const agentId = text(payload.agent_id);\n const eventTurn = text(payload.turn_id);\n if (this.turnIdentity === undefined) {\n // The turn whose inbox sequence is this command's ingress sequence; with acceptance unknown, the next root turn.\n if (event.kind === 'turn.started' && agentId === session.rootId && (ingressSeq === undefined || text(payload.inbox_seq) === ingressSeq)) {\n this.turnIdentity = eventTurn;\n this.resolveTurn(eventTurn);\n this.push({ type: 'raw', event, seq: event.seq, agentId, turnId: eventTurn });\n }\n continue;\n }\n const isRoot = agentId === session.rootId;\n const child = childKinds.includes(event.kind) && agentId !== '' && !isRoot;\n if (event.kind === 'agent.admitted' && child) this.children.add(agentId);\n const belongs = eventTurn === this.turnIdentity || (eventTurn === '' && isRoot) || (this.children.has(agentId) && (child || this.options.includeChildren === true));\n if (!belongs) continue;\n const mapped = this.map(event, payload, agentId, eventTurn || this.turnIdentity, child);\n if (mapped) this.push(mapped);\n if (mapped?.type === 'end') break;\n }\n this.finish();\n }\n\n private map(event: SdkEvent, payload: Record, agentId: string, turnId: string, child: boolean): TurnEvent | undefined {\n const base = { seq: event.seq, agentId, turnId };\n if (child) return { ...base, type: 'child', childId: agentId, kind: event.kind, status: text(payload.status) };\n const isRoot = agentId === this.session.rootId;\n switch (event.kind) {\n case 'stream.text':\n if (isRoot) this.accumulated += text(payload.text);\n return { ...base, type: 'text', delta: text(payload.text) };\n case 'stream.reasoning': return { ...base, type: 'reasoning', delta: text(payload.text) };\n case 'stream.tool.call': case 'stream.tool.started': case 'stream.tool.output': case 'stream.tool.completed': {\n if (event.kind !== 'stream.tool.output' && payload.name !== 'rlm_exec') return { ...base, type: 'raw', event };\n const id = text(payload.id);\n if (event.kind === 'stream.tool.call') return { ...base, type: 'cell', id, status: 'called', code: executionCode(text(payload.args)) };\n if (event.kind === 'stream.tool.started') return { ...base, type: 'cell', id, status: 'running', code: executionCode(text(payload.args)) };\n if (event.kind === 'stream.tool.output') return { ...base, type: 'cell', id, status: 'running', output: text(payload.text) };\n return { ...base, type: 'cell', id, status: 'completed', result: text(payload.result) };\n }\n case 'stream.cell.host.started':\n return { ...base, type: 'host', id: text(payload.id), invocationId: text(payload.invocation_id), operation: text(payload.name), summary: text(payload.args), status: 'running' };\n case 'stream.cell.host': {\n const hostStatus = text(payload.host_status);\n const status = hostStatus === 'cancelled' ? 'cancelled' : hostStatus === 'failed' || text(payload.result) !== '' ? 'failed' : 'completed';\n return { ...base, type: 'host', id: text(payload.id), invocationId: text(payload.invocation_id), operation: text(payload.name), summary: text(payload.args), status, ...(text(payload.result) ? { error: text(payload.result) } : {}), duration: text(payload.text) };\n }\n case 'stream.tool.progress': return { ...base, type: 'progress', id: text(payload.id), operation: text(payload.name), text: text(payload.text) };\n case 'stream.hook.decision': return { ...base, type: 'hook', hook: text(payload.name), operation: text(payload.args), decision: text(payload.text), reason: text(payload.result) };\n case 'question.pending': return questionEvent(this.session, base, payload);\n case 'permission.pending': return permissionEvent(this.session, base, payload);\n case 'stream.notice': return { ...base, type: 'notice', text: text(payload.text) };\n case 'stream.discard':\n if (isRoot) this.accumulated = '';\n return { ...base, type: 'discard', discarded: Number(text(payload.text)) || 0 };\n case 'stream.usage': {\n if (!object(payload.usage)) return { ...base, type: 'raw', event };\n const usage = payload.usage as unknown as TurnUsage;\n if (isRoot) this.lastUsage = usage;\n return { ...base, type: 'usage', usage };\n }\n default: {\n const status = endKinds[event.kind];\n if (status && isRoot) {\n this.endStatus = status;\n return { ...base, type: 'end', status, ...(text(payload.error) ? { error: text(payload.error) } : {}) };\n }\n return { ...base, type: 'raw', event };\n }\n }\n }\n\n private push(event: TurnEvent): void {\n if (this.iterableClosed || this.iterableError) return;\n if (this.waiter) { const waiter = this.waiter; this.waiter = undefined; waiter.resolve({ done: false, value: event }); return; }\n const bytes = JSON.stringify(event).length;\n if (this.queue.length >= (this.options.maxMessages ?? 1024) || this.queuedBytes + bytes > (this.options.maxBytes ?? 8 << 20)) {\n this.iterableError = new WhipError('resynchronization_required', 'Turn event consumer fell behind; the result still settles from the command');\n this.queue.length = 0; this.queuedBytes = 0;\n return;\n }\n this.queue.push(event); this.queuedBytes += bytes;\n }\n /** Ends iteration; the result still resolves from the command. */\n private fail(error: Error): void {\n if (!this.iterableError && !this.iterableClosed) { this.iterableError = error; this.waiter?.reject(error); this.waiter = undefined; }\n if (this.turnIdentity === undefined) this.rejectTurn(error);\n void this.subscription?.dispose().catch(() => {});\n this.resolveEnded();\n }\n private finish(): void {\n this.iterableClosed = true;\n this.waiter?.resolve({ done: true, value: undefined }); this.waiter = undefined;\n if (this.turnIdentity === undefined) this.rejectTurn(new WhipError('execution_failed', 'The turn ended before it started'));\n void this.subscription?.dispose().catch(() => {});\n this.resolveEnded();\n }\n\n [Symbol.asyncIterator](): AsyncIterator {\n return {\n next: (): Promise> => {\n const next = this.queue.shift();\n if (next) { this.queuedBytes -= JSON.stringify(next).length; return Promise.resolve({ done: false, value: next }); }\n if (this.iterableError) return Promise.reject(this.iterableError);\n if (this.iterableClosed) return Promise.resolve({ done: true, value: undefined });\n if (this.waiter) return Promise.reject(new WhipError('invalid_arguments', 'A turn supports one iterator consumer'));\n return new Promise((resolve, reject) => { this.waiter = { resolve, reject }; });\n },\n return: async (): Promise> => { this.iterableClosed = true; this.waiter = undefined; return { done: true, value: undefined }; },\n };\n }\n\n /** The command's terminal outcome, joined with the end event when the turn ran. */\n async result(): Promise> {\n const command = await this.started;\n const outcome = await command.result({ signal: this.options.signal });\n if (this.turnIdentity !== undefined && !this.endStatus) {\n await Promise.race([this.ended, this.pump, new Promise(resolve => setTimeout(resolve, this.options.endTimeoutMs ?? 5_000))]);\n }\n // The command is the truth: once it has settled, nothing further can start\n // this turn, and an end event that has not arrived by now is not waited for.\n if (!this.iterableClosed && !this.iterableError) this.finish();\n const result: Record = object(outcome.result) ? outcome.result : {};\n const usage = this.lastUsage ? { usage: this.lastUsage } : {};\n if (outcome.status === 'succeeded') {\n return { status: 'succeeded', turnId: this.turnIdentity ?? '', text: text(result.text) || this.accumulated, output: result.output as Output, ...usage };\n }\n const status: TurnStatus = outcome.status === 'cancelled' || outcome.status === 'interrupted' ? outcome.status : 'failed';\n const failure: TurnFailure = outcome.failure ?? { code: -32000, message: `turn ${outcome.status}`, data: { kind: 'execution_failed' } };\n return { status, ...(this.turnIdentity !== undefined ? { turnId: this.turnIdentity } : {}), ...(this.accumulated ? { text: this.accumulated } : {}), failure, ...usage };\n }\n /** The final text, or a rejection carrying the failure. */\n async text(): Promise {\n const result = await this.result();\n if (result.status === 'succeeded') return result.text;\n throw new WhipError(result.failure.data?.kind ?? 'execution_failed', result.failure.message, { cause: result.failure });\n }\n /** Command-targeted cancellation; resolves once the daemon accepted it. */\n async cancel(): Promise {\n const command = await this.started;\n await command.cancel().accepted();\n }\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/util.d.ts b/vendor/@whip/sdk/dist/util.d.ts new file mode 100644 index 0000000..05502a4 --- /dev/null +++ b/vendor/@whip/sdk/dist/util.d.ts @@ -0,0 +1,11 @@ +export declare const utf8: TextEncoder; +export declare const byteLength: (text: string) => number; +export declare function object(value: unknown): value is Record; +export declare function frozen(value: T): T; +export declare function withSignal(promise: Promise, signal?: AbortSignal): Promise; +export declare function decodeBase64(value: string): Uint8Array; +export declare function encodeBase64(bytes: Uint8Array): string; +export declare function sha256(bytes: Uint8Array): Promise>; +export declare function digestHex(bytes: Uint8Array): Promise; +export declare function uuid(): string; +export declare function notify(listeners: Set<(value: T) => void>, value: T): void; diff --git a/vendor/@whip/sdk/dist/util.js b/vendor/@whip/sdk/dist/util.js new file mode 100644 index 0000000..a9b6982 --- /dev/null +++ b/vendor/@whip/sdk/dist/util.js @@ -0,0 +1,67 @@ +import { abortError, WhipError } from './errors.js'; +export const utf8 = new TextEncoder(); +export const byteLength = (text) => utf8.encode(text).byteLength; +export function object(value) { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} +export function frozen(value) { + if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) { + Object.freeze(value); + for (const child of Object.values(value)) + frozen(child); + } + return value; +} +export function withSignal(promise, signal) { + if (!signal) + return promise; + if (signal.aborted) { + void promise.catch(() => { }); + return Promise.reject(abortError(signal)); + } + return new Promise((resolve, reject) => { + const abort = () => { cleanup(); reject(abortError(signal)); }; + const cleanup = () => signal.removeEventListener('abort', abort); + signal.addEventListener('abort', abort, { once: true }); + promise.then(value => { cleanup(); resolve(value); }, error => { cleanup(); reject(error); }); + }); +} +export function decodeBase64(value) { + try { + return Uint8Array.from(atob(value), char => char.charCodeAt(0)); + } + catch { + throw new WhipError('invalid_response', 'Invalid base64 data'); + } +} +export function encodeBase64(bytes) { + let text = ''; + for (let start = 0; start < bytes.length; start += 8192) { + text += String.fromCharCode(...bytes.subarray(start, start + 8192)); + } + return btoa(text); +} +export async function sha256(bytes) { + if (!globalThis.crypto?.subtle) + throw new WhipError('unavailable_capability', 'WebCrypto is unavailable. Use a secure browser context for content integrity checks.'); + return new Uint8Array(await crypto.subtle.digest('SHA-256', bytes)); +} +export async function digestHex(bytes) { + return Array.from(await sha256(bytes), byte => byte.toString(16).padStart(2, '0')).join(''); +} +export function uuid() { + if (!globalThis.crypto?.randomUUID) + throw new WhipError('unavailable_capability', 'A secure context with crypto.randomUUID is required to create command identities.'); + return crypto.randomUUID(); +} +export function notify(listeners, value) { + for (const listener of [...listeners]) { + try { + listener(value); + } + catch (error) { + queueMicrotask(() => { throw error; }); + } + } +} +//# sourceMappingURL=util.js.map \ No newline at end of file diff --git a/vendor/@whip/sdk/dist/util.js.map b/vendor/@whip/sdk/dist/util.js.map new file mode 100644 index 0000000..1195ce4 --- /dev/null +++ b/vendor/@whip/sdk/dist/util.js.map @@ -0,0 +1 @@ +{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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","sourcesContent":["import { abortError, WhipError } from './errors.js';\n\nexport const utf8 = new TextEncoder();\nexport const byteLength = (text: string): number => utf8.encode(text).byteLength;\nexport function object(value: unknown): value is Record {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\nexport function frozen(value: T): T {\n if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {\n Object.freeze(value);\n for (const child of Object.values(value)) frozen(child);\n }\n return value;\n}\nexport function withSignal(promise: Promise, signal?: AbortSignal): Promise {\n if (!signal) return promise;\n if (signal.aborted) {\n void promise.catch(() => {});\n return Promise.reject(abortError(signal));\n }\n return new Promise((resolve, reject) => {\n const abort = () => { cleanup(); reject(abortError(signal)); };\n const cleanup = () => signal.removeEventListener('abort', abort);\n signal.addEventListener('abort', abort, { once: true });\n promise.then(value => { cleanup(); resolve(value); }, error => { cleanup(); reject(error); });\n });\n}\nexport function decodeBase64(value: string): Uint8Array {\n try { return Uint8Array.from(atob(value), char => char.charCodeAt(0)); }\n catch { throw new WhipError('invalid_response', 'Invalid base64 data'); }\n}\nexport function encodeBase64(bytes: Uint8Array): string {\n let text = '';\n for (let start = 0; start < bytes.length; start += 8192) {\n text += String.fromCharCode(...bytes.subarray(start, start + 8192));\n }\n return btoa(text);\n}\nexport async function sha256(bytes: Uint8Array): Promise> {\n if (!globalThis.crypto?.subtle) throw new WhipError('unavailable_capability', 'WebCrypto is unavailable. Use a secure browser context for content integrity checks.');\n return new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));\n}\nexport async function digestHex(bytes: Uint8Array): Promise {\n return Array.from(await sha256(bytes), byte => byte.toString(16).padStart(2, '0')).join('');\n}\nexport function uuid(): string {\n if (!globalThis.crypto?.randomUUID) throw new WhipError('unavailable_capability', 'A secure context with crypto.randomUUID is required to create command identities.');\n return crypto.randomUUID();\n}\nexport function notify(listeners: Set<(value: T) => void>, value: T): void {\n for (const listener of [...listeners]) {\n try { listener(value); }\n catch (error) { queueMicrotask(() => { throw error; }); }\n }\n}\n"]} \ No newline at end of file diff --git a/vendor/@whip/sdk/package.json b/vendor/@whip/sdk/package.json new file mode 100644 index 0000000..0fa4fea --- /dev/null +++ b/vendor/@whip/sdk/package.json @@ -0,0 +1,42 @@ +{ + "name": "@whip/sdk", + "version": "0.1.0", + "type": "module", + "files": [ + "dist", + "README.md" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./node": { + "types": "./dist/node.d.ts", + "import": "./dist/node.js" + }, + "./state": { + "types": "./dist/state.d.ts", + "import": "./dist/state.js" + }, + "./react": { + "types": "./dist/react.d.ts", + "import": "./dist/react.js" + }, + "./agents": { + "types": "./dist/agents.d.ts", + "import": "./dist/agents.js" + }, + "./testing": { + "types": "./dist/testing.d.ts", + "import": "./dist/testing.js" + }, + "./testing/node": { + "types": "./dist/testing-node.d.ts", + "import": "./dist/testing-node.js" + } + }, + "dependencies": { + "@whip/protocol": "workspace:*" + } +} From af82c81fa1a1775eb294270bc1703b9362955754 Mon Sep 17 00:00:00 2001 From: Sam Heutmaker Date: Sat, 26 Sep 2026 17:21:40 -0700 Subject: [PATCH 2/6] Drive whip through the SDK behind the harness seam The harness package keeps its name and its `Harness` seam, with one implementation: `whipHarness(client)`. `review(ctx)` now names which loupe agent to run (reviewer, verifier, chat, fixer) and the output contract its final message must match, builds that agent definition, serves it from this process, runs one turn on a session pinned to it on the shared daemon, and returns `{ text, output }`: the final message and the value the daemon validated against the contract. No subprocess, no NDJSON scraping; typed turn events feed the run log and, through `turnEventToTrace`, the same trace pipeline the step summary renders. Claude, codex, the registry, the cache-key retry, and the whip binary identity check go. Core's two call sites (the review with its headless fallback, and verify) pass `agent` and `output`; `parseReviewOutput` and `parseVerification` read the validated output, or the final text when the turn cap answered past the check, so salvage and the malformed counts keep working. Reviewers are read-only whip agents with the `files` module (no shell), so the prompt's tool hints say list/read/search instead of ls/cat/grep and the sentence about subagents goes. The whip block's provider is named on every session because a desktop daemon's default provider may not carry the model, and `reasoning` rides on the session (`session.effort`) since one daemon now serves every reviewer. The action brings whip up once per process (`resolveHarness`: credential chain, `ensureDaemon`, `connect`) and lets it go at exit (`releaseWhip`); concurrent reviewers share that daemon and client. `--harness` is gone; `harness: "whip"` in a config and `LOUPE_HARNESS=whip` are still accepted, anything else is an error. Co-Authored-By: Claude Fable 5.1 --- action.yml | 12 +- bun.lock | 6 +- package.json | 2 +- packages/action/src/cli.ts | 18 +- packages/action/src/config.ts | 13 +- packages/action/src/main.ts | 6 +- packages/action/src/orchestrate.ts | 3 +- packages/action/src/respond.ts | 27 +- packages/action/src/reviewers.ts | 9 +- packages/action/src/run.ts | 105 +++- packages/core/package.json | 2 +- packages/core/src/index.ts | 28 +- packages/core/src/output.ts | 70 +++ packages/core/src/parse.ts | 14 + packages/core/src/prompt.ts | 13 +- packages/core/tests/retry.test.ts | 8 +- packages/core/tests/run-review.test.ts | 13 +- packages/harness/src/agents.ts | 124 ++++ packages/harness/src/client.ts | 238 ++++++++ packages/harness/src/daemon.ts | 354 +++++++++++ packages/harness/src/hooks.ts | 87 +++ packages/harness/src/index.ts | 645 +++++---------------- packages/harness/src/progress.ts | 227 ++++++++ packages/harness/src/whip-binary.ts | 176 ------ packages/harness/tests/agents.test.ts | 104 ++++ packages/harness/tests/client.test.ts | 218 +++++++ packages/harness/tests/daemon.test.ts | 122 ++++ packages/harness/tests/hooks.test.ts | 76 +++ packages/harness/tests/progress.test.ts | 181 ++++++ packages/harness/tests/recorder.ts | 20 + packages/harness/tests/trace.test.ts | 138 ++--- packages/harness/tests/whip-binary.test.ts | 24 - packages/harness/tests/whip-config.test.ts | 136 ----- 33 files changed, 2195 insertions(+), 1024 deletions(-) create mode 100644 packages/core/src/output.ts create mode 100644 packages/harness/src/agents.ts create mode 100644 packages/harness/src/client.ts create mode 100644 packages/harness/src/daemon.ts create mode 100644 packages/harness/src/hooks.ts create mode 100644 packages/harness/src/progress.ts delete mode 100644 packages/harness/src/whip-binary.ts create mode 100644 packages/harness/tests/agents.test.ts create mode 100644 packages/harness/tests/client.test.ts create mode 100644 packages/harness/tests/daemon.test.ts create mode 100644 packages/harness/tests/hooks.test.ts create mode 100644 packages/harness/tests/progress.test.ts create mode 100644 packages/harness/tests/recorder.ts delete mode 100644 packages/harness/tests/whip-binary.test.ts delete mode 100644 packages/harness/tests/whip-config.test.ts diff --git a/action.yml b/action.yml index adec9f0..cfcd489 100644 --- a/action.yml +++ b/action.yml @@ -1,5 +1,5 @@ name: "loupe" -description: "AI PR reviewer that posts inline comments, harness-agnostic, reads your repo's own conventions." +description: "AI PR reviewer that posts inline comments, runs on whip, reads your repo's own conventions." branding: icon: "search" color: "purple" @@ -8,19 +8,19 @@ inputs: description: "Token used to read the PR and post the review." required: true default: ${{ github.token }} - # harness/model/reasoning/profile/timezone/dir default to empty so a value in + # model/reasoning/profile/timezone/dir default to empty so a value in # .loupe.json wins; set an input here only to override the file. Built-in - # fallbacks (whip / kimi-k3 / harness-default effort / chill / UTC) apply when neither is set. + # fallbacks (kimi-k3 / whip's default effort / chill / UTC) apply when neither is set. harness: - description: "Which agent CLI to review with (whip, claude, codex). Overrides .loupe.json; default whip." + description: "Deprecated: loupe reviews through whip only. Accepted as \"whip\" for older workflows; leave empty." required: false default: "" model: - description: "Model id for the harness (e.g. kimi-k3, claude-opus-4-8). Overrides .loupe.json; default kimi-k3." + description: "Model id (e.g. kimi-k3, glm-5.3). Overrides .loupe.json; default kimi-k3." required: false default: "" reasoning: - description: "Reasoning effort: low, medium, or high, passed to the harness natively. Overrides .loupe.json; unset = the harness's own default." + description: "Reasoning effort: low, medium, or high, set on each whip session. Overrides .loupe.json; unset = whip's own default." required: false default: "" prompt-file: diff --git a/bun.lock b/bun.lock index 8875aae..efb050d 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "loupe", "dependencies": { "@octokit/rest": "22.0.0", - "zod": "3.25.76", + "zod": "4.6.2", }, "devDependencies": { "@types/bun": "1.3.14", @@ -46,7 +46,7 @@ "@octokit/rest": "22.0.0", "jsonrepair": "3.15.0", "picomatch": "4.0.7", - "zod": "3.25.76", + "zod": "4.6.2", }, "devDependencies": { "bun-types": "1.3.14", @@ -542,7 +542,7 @@ "winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="], - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "zod": ["zod@4.6.2", "", {}, "sha512-lh5RCAGFa1Cm2hjtNwLQhSs/AsqdWnTQaBER9fEwN/88pSh7KOtJavtBx/0VlkN/uFd61SwYmljLMDAsHlvzBQ=="], "@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@15.0.2", "", { "dependencies": { "@octokit/openapi-types": "^26.0.0" } }, "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q=="], diff --git a/package.json b/package.json index 5ae00af..7536a03 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "@octokit/rest": "22.0.0", - "zod": "3.25.76" + "zod": "4.6.2" }, "devDependencies": { "@types/bun": "1.3.14", diff --git a/packages/action/src/cli.ts b/packages/action/src/cli.ts index 5ba7cb3..f7d62a6 100644 --- a/packages/action/src/cli.ts +++ b/packages/action/src/cli.ts @@ -8,7 +8,12 @@ import { Command } from "commander"; import { resolveProviders } from "./config"; import { asDirs, loadReviewers, loadSettings } from "./reviewers"; -import { formatResult, renderReview, reviewPullRequest } from "./run"; +import { + formatResult, + releaseWhip, + renderReview, + reviewPullRequest, +} from "./run"; const REASONING: readonly ReasoningEffort[] = ["low", "medium", "high"]; const PRIOR_COMMENTS: readonly PriorComments[] = ["resolve", "delete", "keep"]; @@ -81,11 +86,10 @@ program .command("review") .description("Review a pull request and post inline comments") .argument("", "PR URL (github.com/owner/repo/pull/N) or owner/repo#N") - .option("-H, --harness ", "agent CLI to review with (default whip)") - .option("-m, --model ", "model id for the harness (default kimi-k3)") + .option("-m, --model ", "model id (default kimi-k3)") .option( "-r, --reasoning ", - "reasoning effort: low|medium|high (default: harness default)", + "reasoning effort: low|medium|high (default: whip's own default)", ) .option( "--prompt-file ", @@ -95,7 +99,7 @@ program .option("-t, --token ", "GitHub token (else GITHUB_TOKEN or gh)") .option( "-w, --workdir ", - "repo checkout the harness may read", + "repo checkout the reviewer may read", process.cwd(), ) .option( @@ -168,7 +172,6 @@ program async ( pr: string, opts: { - harness?: string; model?: string; reasoning?: string; promptFile?: string; @@ -201,7 +204,6 @@ program // Top-level review defaults from .loupe.json; a flag overrides the file, // the file overrides loupe's built-in default. const settings = opts.config ? loadSettings(opts.config) : {}; - const harnessName = opts.harness ?? settings.harness ?? "whip"; const model = opts.model ?? settings.model ?? "kimi-k3"; const reasoningRaw = opts.reasoning ?? settings.reasoning; const reasoning = reasoningRaw @@ -238,7 +240,6 @@ program owner, repo, pullNumber, - harnessName, workdir: opts.workdir, conventionPaths: opts.conventions .split(",") @@ -329,6 +330,7 @@ program logger.info(formatResult(result)); if (opts.dryRun) console.log(renderReview(result)); } finally { + await releaseWhip(); await shutdownLogger(); } }, diff --git a/packages/action/src/config.ts b/packages/action/src/config.ts index 8a901d7..e569c5d 100644 --- a/packages/action/src/config.ts +++ b/packages/action/src/config.ts @@ -120,14 +120,13 @@ export type Config = { readonly owner: string; readonly repo: string; readonly pullNumber: number; - readonly harnessName: string; readonly workdir: string; readonly conventionPaths: readonly string[]; readonly providers: readonly CredentialProvider[]; /** Directories in scope; several are reviewed together from the repo root. */ readonly dirs?: readonly string[]; readonly model: string; - /** Unset = harness default effort and no reasoning note in the prompt. */ + /** Unset = whip's default effort and no reasoning note in the prompt. */ readonly reasoning?: ReasoningEffort; readonly guidance?: string; readonly configPath?: string; @@ -167,13 +166,21 @@ export function loadConfig(): Config { // Top-level review defaults from .loupe.json. Precedence for the movable // settings: Action input (explicit) → file → loupe's built-in default. const file: LoupeSettings = configPath ? loadSettings(configPath) : {}; + // loupe is whip-only. `harness: "whip"` in a .loupe.json and a `harness` + // input of "whip" are still accepted so existing configs keep working; any + // other value names a harness loupe no longer has. + const harness = env.LOUPE_HARNESS ?? file.harness; + if (harness !== undefined && harness !== "whip") { + throw new Error( + `Unknown harness "${harness}": loupe reviews through whip only. Remove the harness setting or set it to "whip".`, + ); + } return { token: env.GITHUB_TOKEN, owner, repo, pullNumber: resolvePullNumber(env.LOUPE_PR_NUMBER, env.GITHUB_EVENT_PATH), - harnessName: env.LOUPE_HARNESS ?? file.harness ?? "whip", workdir, conventionPaths: env.LOUPE_CONVENTION_PATHS.split(",") .map((p) => p.trim()) diff --git a/packages/action/src/main.ts b/packages/action/src/main.ts index 3ed6993..e63f96a 100644 --- a/packages/action/src/main.ts +++ b/packages/action/src/main.ts @@ -5,6 +5,7 @@ import { loadConfig } from "./config"; import { setOutput, statusForError, statusForOutcomes } from "./output"; import { runReviews, CombinedSummaryPublicationError } from "./orchestrate"; import { handleComment } from "./respond"; +import { releaseWhip } from "./run"; const logger = createRootLogger("loupe-action"); @@ -47,4 +48,7 @@ main() ); process.exitCode = 1; }) - .finally(() => shutdownLogger()); + .finally(async () => { + await releaseWhip(); + await shutdownLogger(); + }); diff --git a/packages/action/src/orchestrate.ts b/packages/action/src/orchestrate.ts index edfb524..01dbac0 100644 --- a/packages/action/src/orchestrate.ts +++ b/packages/action/src/orchestrate.ts @@ -168,7 +168,6 @@ export async function runReviews( owner: config.owner, repo: config.repo, pullNumber: config.pullNumber, - harnessName: config.harnessName, workdir: config.workdir, conventionPaths: config.conventionPaths, providers: config.providers, @@ -256,7 +255,7 @@ export async function runReviews( readTrace: () => ReviewerTrace; }[] = []; const collectors = inputs.map(({ label, input }) => { - const collector = createTraceCollector(label, config.harnessName); + const collector = createTraceCollector(label, "whip"); return { label, input, collector }; }); const settled = await Promise.all( diff --git a/packages/action/src/respond.ts b/packages/action/src/respond.ts index 6ffc914..f70205d 100644 --- a/packages/action/src/respond.ts +++ b/packages/action/src/respond.ts @@ -16,8 +16,6 @@ import { type PullRef, type ReviewResult, } from "@loupe/core"; -import { resolveCredentials } from "@loupe/credentials"; -import { getHarness } from "@loupe/harness"; import type { Logger } from "@loupe/logger"; import type { Octokit } from "@octokit/rest"; import { z } from "zod"; @@ -30,6 +28,7 @@ import { type ReviewerOutcome, } from "./orchestrate"; import { loadReviewers } from "./reviewers"; +import { resolveHarness } from "./run"; const MENTION = /@loupe\b/i; @@ -179,23 +178,19 @@ async function runFix( } git(cwd, ["checkout", "-B", headRef, originalHead]); - const harness = getHarness(config.harnessName); - const env = await resolveCredentials( - harness.credentialKeys, - config.providers, - ); + const harness = await resolveHarness({ ...config, logger }); const pull = await fetchPullContext(octokit, ref); - logger.info("Fix: running agentic harness", { chars: instruction.length }); + logger.info("Fix: running the fixer agent", { chars: instruction.length }); await harness.review({ + agent: "fixer", systemPrompt: buildFixSystemPrompt(), userPrompt: findings ? buildFixFindingsUserPrompt(findings, pull.files) : buildFixUserPrompt(instruction, pull.files), model: config.model, + provider: config.whipConfig?.provider.name, agentic: true, workdir: cwd, - env, - whipConfig: config.whipConfig, maxTurns: config.maxTurns, reasoning: config.reasoning, cacheKey: @@ -425,20 +420,16 @@ export async function handleComment( // Free-form question → answer from the diff. logger.info("Chat question", { chars: instruction.length }); try { - const harness = getHarness(config.harnessName); - const env = await resolveCredentials( - harness.credentialKeys, - config.providers, - ); + const harness = await resolveHarness({ ...config, logger }); const pull = await fetchPullContext(octokit, ref); - const stdout = await harness.review({ + const { text: stdout } = await harness.review({ + agent: "chat", systemPrompt: buildChatSystemPrompt(), userPrompt: buildChatUserPrompt(instruction, pull.files), model: config.model, + provider: config.whipConfig?.provider.name, agentic: false, workdir: config.workdir, - env, - whipConfig: config.whipConfig, maxTurns: config.maxTurns, reasoning: config.reasoning, cacheKey: diff --git a/packages/action/src/reviewers.ts b/packages/action/src/reviewers.ts index 6714265..84df789 100644 --- a/packages/action/src/reviewers.ts +++ b/packages/action/src/reviewers.ts @@ -73,9 +73,10 @@ const configSchema = z.object({ /** Skills applied to every reviewer (merged with each reviewer's own). */ skills: z.array(z.string()).optional(), // Top-level review defaults. Action inputs / CLI flags override these; these - // in turn override loupe's built-in defaults. Keeps harness/model/timezone/dir - // with the repo's review policy instead of duplicated across workflow files. - harness: z.string().optional(), + // in turn override loupe's built-in defaults. Keeps model/timezone/dir with + // the repo's review policy instead of duplicated across workflow files. + /** Accepted for older configs; whip is the only harness. */ + harness: z.literal("whip").optional(), model: z.string().optional(), reasoning: z.enum(["low", "medium", "high"]).optional(), profile: z.enum(["quiet", "chill", "assertive"]).optional(), @@ -95,7 +96,7 @@ const configSchema = z.object({ /** The top-level, non-reviewer settings from a .loupe.json — review defaults a * repo declares once instead of repeating them in every workflow file. */ export type LoupeSettings = { - readonly harness?: string; + readonly harness?: "whip"; readonly model?: string; readonly reasoning?: ReasoningEffort; readonly profile?: Profile; diff --git a/packages/action/src/run.ts b/packages/action/src/run.ts index ac0982b..e8794ff 100644 --- a/packages/action/src/run.ts +++ b/packages/action/src/run.ts @@ -20,8 +20,14 @@ import { type CredentialProvider, } from "@loupe/credentials"; import { - getHarness, + connect, + ensureDaemon, + envSecretValues, + whipHarness, + type DaemonHandle, + type Harness, type HarnessTraceEvent, + type WhipClient, type WhipConfig, } from "@loupe/harness"; import type { Logger } from "@loupe/logger"; @@ -31,7 +37,6 @@ export type RunInput = { readonly owner: string; readonly repo: string; readonly pullNumber: number; - readonly harnessName: string; readonly workdir: string; readonly conventionPaths: readonly string[]; readonly providers: readonly CredentialProvider[]; @@ -63,38 +68,75 @@ export type RunInput = { readonly logger: Logger; }; -/** Resolve the harness, verify it is installed, and forward its env. */ -async function resolveHarness(input: RunInput): Promise<{ - harness: ReturnType; - harnessEnv: Record; -}> { - const { logger } = input; - const harness = getHarness(input.harnessName); - if (!(await harness.available())) { - throw new Error(`Harness "${harness.name}" CLI is not installed.`); - } - // Best-effort: forward whatever credential keys the providers can supply. - // We don't hard-fail on a missing key — harnesses often self-authenticate - // from a local login (whip via ~/.whip, claude via its own login). If a key - // is genuinely required and absent, the harness surfaces its own auth error. - const harnessEnv = await resolveCredentials( - harness.credentialKeys, - input.providers, - ); - const missing = harness.credentialKeys.filter((k) => !(k in harnessEnv)); - logger.debug("Harness ready", { - harness: harness.name, - forwardedKeys: Object.keys(harnessEnv), - missingKeys: missing, - providers: input.providers.map((p) => p.name), +/** What bringing whip up needs: the `whip` block and where its key comes from. */ +export type WhipOptions = { + readonly whipConfig?: WhipConfig; + readonly providers: readonly CredentialProvider[]; + readonly logger: Logger; +}; + +type Whip = { + readonly harness: Harness; + readonly client: WhipClient; + readonly daemon: DaemonHandle; +}; + +/** + * One daemon and one client per process, shared by every reviewer that runs + * concurrently in it. The first caller brings whip up: resolve the provider's + * API key through the credential chain, make sure a daemon is running (a + * dedicated one when the `whip` block's key is available), and connect. The + * memoized promise makes every caller await the same bring-up; a failed one + * clears so the next attempt is fresh. `releaseWhip` lets go of it all. + */ +let shared: Promise | undefined; + +export function resolveHarness(opts: WhipOptions): Promise { + shared ??= bringUp(opts).catch((err: unknown) => { + shared = undefined; + throw err; + }); + return shared.then((w) => w.harness); +} + +async function bringUp(opts: WhipOptions): Promise { + const credentials = opts.whipConfig + ? await resolveCredentials( + [opts.whipConfig.provider.apiKeyEnv], + opts.providers, + ) + : {}; + const daemon = await ensureDaemon({ + whipConfig: opts.whipConfig, + credentials, + logger: opts.logger, }); - return { harness, harnessEnv }; + const client = await connect(daemon, opts.logger); + // Known secrets (the resolved credential values handed to the daemon) are + // scrubbed from every trace payload so an API key that surfaces in a tool + // result or reasoning chunk never lands in the summary. + return { + harness: whipHarness(client, envSecretValues(daemon.env)), + client, + daemon, + }; +} + +/** Close the shared client and stop a daemon loupe started; a no-op otherwise. */ +export async function releaseWhip(): Promise { + const pending = shared; + shared = undefined; + if (!pending) return; + const whip = await pending.catch(() => undefined); + if (!whip) return; + whip.client.close(); + await whip.daemon.stop(); } /** Build a ReviewRequest from a RunInput (shared by produce/publish/run). */ async function buildRequest(input: RunInput): Promise { const { logger } = input; - const { harness, harnessEnv } = await resolveHarness(input); + const harness = await resolveHarness(input); return { octokit: makeOctokit(input.token, logger), ref: { @@ -104,8 +146,9 @@ async function buildRequest(input: RunInput): Promise { }, harness, workdir: input.workdir, - harnessEnv, - whipConfig: input.whipConfig, + // The desktop daemon's default provider may not carry the model, so every + // session names the whip block's provider explicitly. + provider: input.whipConfig?.provider.name, conventionPaths: input.conventionPaths, dirs: input.dirs, dryRun: input.dryRun, @@ -134,7 +177,7 @@ async function buildRequest(input: RunInput): Promise { }; } -/** Resolve the harness + credentials, then review end-to-end. Shared by Action and CLI. */ +/** Bring whip up, then review end-to-end. Shared by Action and CLI. */ export async function reviewPullRequest( input: RunInput, ): Promise { diff --git a/packages/core/package.json b/packages/core/package.json index 98d566f..e059744 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -16,7 +16,7 @@ "@octokit/rest": "22.0.0", "jsonrepair": "3.15.0", "picomatch": "4.0.7", - "zod": "3.25.76" + "zod": "4.6.2" }, "devDependencies": { "bun-types": "1.3.14", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1993cd8..b715ee2 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -13,7 +13,6 @@ import { isNonRetryableHarnessError, type Harness, type HarnessTraceEvent, - type WhipConfig, } from "@loupe/harness"; import type { Logger } from "@loupe/logger"; import type { Octokit } from "@octokit/rest"; @@ -39,7 +38,8 @@ import { mergeEnsemble, SIMILARITY_THRESHOLD, } from "./ensemble"; -import { parseReviewOutput, parseVerification } from "./parse"; +import { reviewOutput, verdictsOutput } from "./output"; +import { outcomeText, parseReviewOutput, parseVerification } from "./parse"; import { severityRank, severitiesForProfile, @@ -89,6 +89,7 @@ export * from "./validate"; export * from "./github"; export * from "./ensemble"; export * from "./callsites"; +export * from "./output"; export type ReviewRequest = { /** Authenticated GitHub client; see makeOctokit. */ @@ -96,10 +97,8 @@ export type ReviewRequest = { readonly ref: PullRef; readonly harness: Harness; readonly workdir: string; - /** Secrets to inject into the harness subprocess (e.g. ANTHROPIC_API_KEY). */ - readonly harnessEnv: Record; - /** whip provider/model catalog to materialize into a throwaway WHIP_HOME. */ - readonly whipConfig?: WhipConfig; + /** Provider the models are routed through (the whip block's); empty uses the daemon's default. */ + readonly provider?: string; /** Convention doc paths to pull from the target repo, in priority order. */ readonly conventionPaths: readonly string[]; /** @@ -816,15 +815,17 @@ export async function produceReview( const run = (useAgentic: boolean, phase: string) => req.harness .review({ + agent: "reviewer", + output: reviewOutput, + name: req.reviewerName, systemPrompt: useAgentic ? systemPrompt : buildSystemPrompt({ ...promptOpts, agentic: false }), userPrompt: useAgentic ? agenticUserPrompt : headlessUserPrompt, model, + provider: req.provider, agentic: useAgentic, workdir: harnessCwd, - env: req.harnessEnv, - whipConfig: req.whipConfig, maxTurns: req.maxTurns, reasoning: req.reasoning, cacheKey, @@ -832,7 +833,7 @@ export async function produceReview( phase, logger, }) - .then(parseReviewOutput); + .then((outcome) => parseReviewOutput(outcomeText(outcome))); let parsed; try { parsed = await run(agentic, model ? `${tag}:${model}` : tag); @@ -1116,16 +1117,17 @@ async function verifyInline( ): Promise<{ kept: Finding[]; status: ReviewDiagnostics["verify"] }> { const verifyAgentic = agentic && hasCheckout; try { - const stdout = await req.harness.review({ + const outcome = await req.harness.review({ + agent: "verifier", + output: verdictsOutput, systemPrompt: buildVerifySystemPrompt({ agentic: verifyAgentic }), userPrompt: buildVerifyUserPrompt(findings, files, { cwdSubdir: verifyAgentic ? cwdSubdir : undefined, }), model: req.model, + provider: req.provider, agentic: verifyAgentic, workdir: harnessCwd, - env: req.harnessEnv, - whipConfig: req.whipConfig, maxTurns: req.maxTurns, reasoning: req.reasoning, cacheKey: @@ -1136,7 +1138,7 @@ async function verifyInline( phase: req.model ? `verify:${req.model}` : "verify", logger: req.logger, }); - const result = parseVerification(stdout, findings.length); + const result = parseVerification(outcomeText(outcome), findings.length); if (!result.valid) { req.logger.warn("Verification output invalid; keeping all findings", { reasons: result.reasons, diff --git a/packages/core/src/output.ts b/packages/core/src/output.ts new file mode 100644 index 0000000..0f1cf7c --- /dev/null +++ b/packages/core/src/output.ts @@ -0,0 +1,70 @@ +import { z } from "zod"; + +/** + * The wire contracts loupe's agents answer under. The daemon derives JSON + * Schema from these, tells the model the shape in its runtime guide, validates + * the final message, and corrects it once. Severities stay strings here and + * are normalized loupe-side (see types.ts), because an enum mismatch would + * cost a correction round for a synonym we already understand. + */ + +const severity = z.string().describe('"blocker" | "warning" | "nit"'); + +export const findingOutput = z.object({ + path: z + .string() + .describe("Repo-relative file path, exactly as shown in the diff"), + line: z + .number() + .int() + .describe( + "Line number in the NEW version of the file; must be a changed or context line shown in the diff", + ), + severity: severity.optional(), + body: z.string().describe("The problem on THIS line and the fix"), +}); + +export const concernOutput = z.object({ + title: z + .string() + .describe("Short title of a PR-level issue not tied to any single line"), + detail: z.string().describe("The problem and the fix"), + severity: severity.optional(), +}); + +/** A reviewer's final message. */ +export const reviewOutput = z.object({ + summary: z.string().describe("What this PR does + your verdict"), + concerns: z + .array(concernOutput) + .optional() + .describe( + "PR-level issues not tied to any single line. Usually empty; prefer findings.", + ), + highlights: z + .array(z.string()) + .optional() + .describe("Short clauses; only if genuinely notable. Usually empty."), + diagram: z + .string() + .optional() + .describe( + "Mermaid diagram body (no code fences); ONLY for a genuinely complex new control/data flow. Usually omit.", + ), + findings: z + .array(findingOutput) + .describe("Line-anchored issues; each becomes an inline comment."), +}); + +/** The verification pass's final message. */ +export const verdictsOutput = z.object({ + verdicts: z + .array( + z.object({ + index: z.number().int(), + real: z.boolean(), + reason: z.string().optional(), + }), + ) + .describe("One verdict per finding index."), +}); diff --git a/packages/core/src/parse.ts b/packages/core/src/parse.ts index ee2b1ab..2c7d58a 100644 --- a/packages/core/src/parse.ts +++ b/packages/core/src/parse.ts @@ -24,6 +24,20 @@ function parseLenient(text: string): unknown { } } +/** + * The text a turn's outcome is parsed from. Under an output contract the + * daemon hands back the validated value, so its JSON is what gets parsed; + * when a turn ends without one (no contract, or the turn cap forced a final + * answer past the check) the final message is parsed as before, so salvage + * and the malformed counts keep working on that path. + */ +export function outcomeText(outcome: { + readonly text: string; + readonly output: unknown; +}): string { + return outcome.output != null ? JSON.stringify(outcome.output) : outcome.text; +} + /** A parsed review plus what the parser had to discard to produce it. */ export type ParsedReview = { readonly review: ReviewOutput; diff --git a/packages/core/src/prompt.ts b/packages/core/src/prompt.ts index 2e92896..bd5e1de 100644 --- a/packages/core/src/prompt.ts +++ b/packages/core/src/prompt.ts @@ -95,8 +95,8 @@ Procedure — do these before writing any finding: change too. 3. NEVER assert that something is missing, absent, not created, not updated, not imported, not registered, not called, or not handled without CONFIRMING - it against the real checkout first — \`ls\` the directory, \`cat\`/grep the - file, read the sibling files. The diff is not the filesystem: on an + it against the real checkout first — list the directory, read or search + the file, read the sibling files. The diff is not the filesystem: on an incremental run it shows only what changed since the last review, so a file or edit that is NOT in the diff may still exist on disk from an earlier commit. "Missing snapshot.json", "head file not updated", "no test added", @@ -164,10 +164,9 @@ migrations, model and query code, and existing indexes/constraints the diff interacts with. Ground each finding in what you actually found, not a guess. Your tool budget is limited. Spend it on the change's callers and contracts -first. Use subagents when several independent investigations would otherwise run -serially; do not spawn them for a single grep. If you suspect a blocker and have -budget left, get one independent confirmation before reporting it. When you have -what you need, STOP and respond with ONLY the final JSON object.`.trim(); +first. If you suspect a blocker and have budget left, get one independent +confirmation before reporting it. When you have what you need, STOP and respond +with ONLY the final JSON object.`.trim(); const REASONING_NOTE: Record = { low: "Reasoning effort: low. Do a quick pass; flag only obvious, high-confidence issues.", @@ -307,7 +306,7 @@ export function buildUserPrompt(input: UserPromptInput): string { ? [ "Changed files (the full diff is NOT inlined — explore it yourself):", renderFileTree(input.files), - `The complete unified diff is written to \`${input.diffPath}\`. For each file you review, read its hunks from that file (e.g. with grep/sed by the \`### \` header) and inspect the surrounding code in the checkout. Read only what the change touches — do not read the whole diff up front.`, + `The complete unified diff is written to \`${input.diffPath}\`. For each file you review, read its hunks from that file (search it for the \`### \` header, then read from there; a large file comes back as a {handle, preview, size} object you read with context.read) and inspect the surrounding code in the checkout. Read only what the change touches — do not read the whole diff up front.`, ].join("\n\n") : ["Diff under review:", renderDiff(input.files)].join("\n\n"); return [ diff --git a/packages/core/tests/retry.test.ts b/packages/core/tests/retry.test.ts index fe57a8d..7368bf6 100644 --- a/packages/core/tests/retry.test.ts +++ b/packages/core/tests/retry.test.ts @@ -38,12 +38,13 @@ function fakeHarness(throwOnFirst: unknown): { const calls: { agentic?: boolean }[] = []; const harness: Harness = { name: "fake", - credentialKeys: [], - available: () => Promise.resolve(true), review: async (ctx) => { calls.push({ agentic: ctx.agentic }); if (calls.length === 1) throw throwOnFirst; - return JSON.stringify({ summary: "", findings: [], concerns: [] }); + return { + text: JSON.stringify({ summary: "", findings: [], concerns: [] }), + output: undefined, + }; }, }; return { harness, calls }; @@ -56,7 +57,6 @@ async function runReviewWith(harness: Harness): Promise { ref: { owner: "o", repo: "r", pull_number: 1 }, harness, workdir: ".", - harnessEnv: {}, conventionPaths: [], dryRun: true, // don't post full: true, // skip the incremental path (getLastReviewedSha/changedFilesBetween) diff --git a/packages/core/tests/run-review.test.ts b/packages/core/tests/run-review.test.ts index fdca3b3..9565b4a 100644 --- a/packages/core/tests/run-review.test.ts +++ b/packages/core/tests/run-review.test.ts @@ -227,8 +227,6 @@ function fakeHarness(script: { const contexts: HarnessContext[] = []; const harness: Harness = { name: "fake", - credentialKeys: [], - available: async () => true, review: async (ctx) => { contexts.push(ctx); const verifying = ctx.systemPrompt.startsWith( @@ -248,7 +246,7 @@ function fakeHarness(script: { model: ctx.model, phase: ctx.phase, }); - return output; + return { text: output, output: undefined }; } if (ctx.agentic && script.agentic instanceof Error) { ctx.trace?.({ @@ -268,7 +266,7 @@ function fakeHarness(script: { model: ctx.model, phase: ctx.phase, }); - return output; + return { text: output, output: undefined }; }, }; return { harness, contexts }; @@ -293,8 +291,6 @@ function fakePerModelHarness( const contexts: HarnessContext[] = []; const harness: Harness = { name: "fake", - credentialKeys: [], - available: async () => true, review: async (ctx) => { contexts.push(ctx); const verifying = ctx.systemPrompt.startsWith( @@ -308,7 +304,7 @@ function fakePerModelHarness( model: ctx.model, phase: ctx.phase, }); - return output; + return { text: output, output: undefined }; } const script = ctx.model ? byModel[ctx.model] : undefined; if (script instanceof Error) { @@ -327,7 +323,7 @@ function fakePerModelHarness( model: ctx.model, phase: ctx.phase, }); - return output; + return { text: output, output: undefined }; }, }; return { harness, contexts }; @@ -344,7 +340,6 @@ function request( ref, harness, workdir, - harnessEnv: {}, conventionPaths: ["AGENTS.md"], dirs: ["svc"], reviewerName: "code", diff --git a/packages/harness/src/agents.ts b/packages/harness/src/agents.ts new file mode 100644 index 0000000..695a503 --- /dev/null +++ b/packages/harness/src/agents.ts @@ -0,0 +1,124 @@ +import { createHash } from "node:crypto"; + +import type { Schema } from "@whip/sdk"; +import { + defineAgent, + type AgentDefinition, + type AgentInput, +} from "@whip/sdk/agents"; + +import { + FIXER_HOOK_OPERATIONS, + REVIEWER_HOOK_OPERATIONS, + fixerBeforeTool, + reviewerBeforeTool, +} from "./hooks"; + +/** `loupe-` fitted to whip's definition id rules. */ +export function definitionId(name: string): string { + const slug = name + .toLowerCase() + .replace(/[^a-z0-9-]+/g, "-") + .replace(/^-+|-+$/g, ""); + return `loupe-${slug || "review"}`.slice(0, 64).replace(/-+$/, ""); +} + +/** + * A definition id that changes with the definition's content. A new session + * takes the id's most recently *created* revision, and registration is + * idempotent on content, so two variants sharing an id (agentic and one-shot, + * or a reviewer before and after its conventions changed) would hand a + * session the wrong one; the runtime refuses that instead of running it, and + * a content hash in the id keeps it from ever happening. + */ +export function contentId(name: string, ...parts: unknown[]): string { + const hash = createHash("sha256") + .update(JSON.stringify(parts)) + .digest("hex") + .slice(0, 8); + return `${definitionId(name).slice(0, 64 - 9)}-${hash}`; +} + +/** Define an agent whose id carries the hash of its canonical document. */ +function defineNamed( + name: string, + input: Omit, "id">, +): AgentDefinition { + const draft = defineAgent({ ...input, id: "loupe-draft" }); + const id = contentId(name, { ...draft.document, id: "" }); + return defineAgent({ ...input, id }); +} + +const readOnlyHook = { + beforeTool: { + operations: [...REVIEWER_HOOK_OPERATIONS], + handler: reviewerBeforeTool, + }, +}; + +/** + * A reviewer: loupe's system prompt as the persona, read-only authority, the + * file module only when the review is agentic, and an output contract the + * daemon enforces on its final message. whip appends its own runtime guide, + * so the model learns the modules it may call and the shape it must answer + * with. + */ +export function reviewerAgent(spec: { + readonly name?: string; + readonly systemPrompt: string; + readonly agentic: boolean; + /** The review schema the final message must match. */ + readonly output?: Schema; +}): AgentDefinition { + return defineNamed(spec.name ?? "review", { + instructions: { persona: spec.systemPrompt }, + modules: spec.agentic ? ["context", "files"] : ["context"], + capabilities: ["read"], + output: spec.output, + hooks: spec.agentic ? readOnlyHook : undefined, + }); +} + +/** Second opinion on findings, answering under a contract: reads the + * checkout when the review was agentic, the diff alone otherwise. */ +export function verifierAgent(spec: { + readonly systemPrompt: string; + readonly agentic: boolean; + readonly output?: Schema; +}): AgentDefinition { + return defineNamed("verifier", { + instructions: { persona: spec.systemPrompt }, + modules: spec.agentic ? ["context", "files"] : ["context"], + capabilities: ["read"], + output: spec.output, + hooks: spec.agentic ? readOnlyHook : undefined, + }); +} + +/** Prose answers to `@loupe `; diff only. */ +export function chatAgent(systemPrompt: string): AgentDefinition { + return defineNamed("chat", { + instructions: { persona: systemPrompt }, + modules: ["context"], + capabilities: ["read"], + }); +} + +/** + * `@loupe fix`: edits the checkout at `cwd` and may run tests there; a hook + * keeps edits inside the checkout and git out of its hands. loupe commits and + * pushes afterwards. + */ +export function fixerAgent(systemPrompt: string, cwd: string): AgentDefinition { + return defineNamed("fixer", { + instructions: { persona: systemPrompt }, + modules: ["context", "files", "shell"], + capabilities: ["read", "write", "shell"], + hooks: { + beforeTool: { + operations: [...FIXER_HOOK_OPERATIONS], + handler: (event) => fixerBeforeTool(cwd, event), + }, + }, + }); +} diff --git a/packages/harness/src/client.ts b/packages/harness/src/client.ts new file mode 100644 index 0000000..09a9d7e --- /dev/null +++ b/packages/harness/src/client.ts @@ -0,0 +1,238 @@ +import { randomUUID } from "node:crypto"; + +import type { Logger } from "@loupe/logger"; +import type { + Session, + Turn, + TurnEvent, + TurnResult, + TurnUsage, +} from "@whip/sdk"; +import type { AgentDefinition } from "@whip/sdk/agents"; +import type { WhipClient } from "@whip/sdk/node"; +import { createWhipClient, unixSocket } from "@whip/sdk/node"; + +import type { DaemonHandle } from "./daemon"; +import { RunProgress } from "./progress"; + +/** Attach to the daemon over its Unix socket. */ +export async function connect( + daemon: DaemonHandle, + logger: Logger, +): Promise { + const client = createWhipClient({ + endpoint: unixSocket(daemon.socket), + clientId: `loupe-${randomUUID()}`, + clientKind: "automation", + }); + await client.connect(); + const info = client.getSnapshot().info; + logger.debug("Connected to whip daemon", { + socket: daemon.socket, + build: info?.build_id, + protocol: `${info?.protocol_major}.${info?.protocol_minor}`, + }); + return client; +} + +/** How one session on a served agent is set up. */ +export type SessionParams = { + /** Working directory the session's host modules operate in. */ + readonly cwd: string; + /** Model id; empty uses the daemon's default. */ + readonly model?: string; + /** Provider the model is routed through; empty uses the daemon's default. */ + readonly provider?: string; + /** Reasoning effort for this session; unset leaves the daemon's default. */ + readonly effort?: string; + /** Cap on tool-call rounds per turn; 0 or omitted is uncapped. */ + readonly maxTurns?: number; + /** Stable prompt-cache key (repo/reviewer) shared across runs. */ + readonly cacheKey?: string; +}; + +export type RunSpec = SessionParams & { + readonly agent: AgentDefinition; + readonly prompt: string; + /** Sees every turn event (after the log has); a trace sink hangs here. */ + readonly onEvent?: (event: TurnEvent) => void; + readonly logger: Logger; +}; + +/** What one turn hands back: the final reply and, under an output contract, the validated value. */ +export type RunOutcome = { + readonly text: string; + /** The daemon-validated output; undefined without a contract or when the + * turn cap answered without the check (see core's outcomeText). */ + readonly output: unknown; + readonly usage?: TurnUsage; +}; + +/** A session on a served agent that can take several turns. */ +export interface AgentRun { + readonly rootId: string; + /** Run one turn and return its outcome; a failed turn throws. */ + run( + prompt: string, + onEvent?: (event: TurnEvent) => void, + ): Promise; + /** Delete the session so one-off runs don't pile up in the daemon. */ + close(): Promise; +} + +/** A definition this process serves (or has registered) and can open sessions on. */ +export interface ServedAgent { + readonly definition: string; + session(params: SessionParams): Promise; + /** Stop serving; open sessions may still be closed afterwards. */ + close(): void; +} + +function failed( + operation: string, + outcome: { status: string; failure?: null | { message: string } }, +): Error { + return new Error( + `whip ${operation} failed: ${outcome.failure?.message ?? outcome.status}`, + ); +} + +/** An agent with tools or hooks needs this process bound as its executor; a + * plain definition only needs registering, and `serve` refuses it. */ +function needsExecutor(agent: AgentDefinition): boolean { + return ( + agent.handlers.size > 0 || + Boolean( + agent.hooks.before_tool ?? + agent.hooks.before_spawn ?? + agent.hooks.turn_start, + ) + ); +} + +/** Feed a turn's events to the log and settle its result; a consumer that + * falls behind fails the iterable, never the result. */ +async function observe( + turn: Turn, + progress: RunProgress, + onEvent: ((event: TurnEvent) => void) | undefined, + log: Logger, +): Promise { + try { + for await (const event of turn) { + progress.observe(event); + onEvent?.(event); + } + } catch (err: unknown) { + log.debug("turn event stream ended early", { + error: err instanceof Error ? err.message : String(err), + }); + } + return turn.result(); +} + +/** + * Serve an agent from this process (or only register it when it has no tools + * or hooks) and hand back a handle that opens sessions pinned to it. Each + * session is created in automatic permission mode, configured headless with + * its turn cap and cache key, and runs turns through `session.run`, whose + * typed events feed the log and whose result carries the output contract's + * validated value. + */ +export async function serveAgent( + client: WhipClient, + agent: AgentDefinition, + logger: Logger, +): Promise { + const log = logger.child("whip"); + const runtime = needsExecutor(agent) + ? await client.agents.serve(agent) + : undefined; + const definition = runtime + ? runtime.definition + : (await client.agents.register(agent)).id; + log.debug("Serving agent", { definition, executor: Boolean(runtime) }); + + const open = async (params: SessionParams): Promise => { + const create = { + cwd: params.cwd, + model: params.model ?? "", + provider: params.provider ?? "", + permission_mode: "automatic", + }; + if (runtime) return runtime.sessions.create(create); + const created = await client.sessions + .create({ ...create, definition }) + .result(); + if (created.status !== "succeeded" || !created.result) { + throw failed("session.create", created); + } + return client.session(created.result.root_id); + }; + + return { + definition, + async session(params) { + const session = await open(params); + const configured = await session + .configure({ + headless: true, + ...(params.maxTurns ? { max_turns: params.maxTurns } : {}), + ...(params.cacheKey ? { cache_key: params.cacheKey } : {}), + }) + .result(); + if (configured.status !== "succeeded") { + throw failed("run.configure", configured); + } + if (params.effort) { + const effort = await session.setEffort(params.effort, false).result(); + if (effort.status !== "succeeded") + throw failed("session.effort", effort); + } + return { + rootId: session.rootId, + async run(prompt, onEvent) { + const progress = new RunProgress(log, session.rootId); + const result = await observe( + session.run(prompt, { includeChildren: true }), + progress, + onEvent, + log, + ); + progress.finish(result.usage); + if (result.status !== "succeeded") throw failed("turn", result); + return { + text: result.text, + output: result.output, + usage: result.usage, + }; + }, + close: () => + client.sessions + .delete(session.rootId) + .result() + .then(() => undefined) + .catch(() => undefined), + }; + }, + close: () => runtime?.close(), + }; +} + +/** Serve an agent, run one turn on a fresh session, and let go of both. */ +export async function runAgent( + client: WhipClient, + spec: RunSpec, +): Promise { + const served = await serveAgent(client, spec.agent, spec.logger); + try { + const session = await served.session(spec); + try { + return await session.run(spec.prompt, spec.onEvent); + } finally { + await session.close(); + } + } finally { + served.close(); + } +} diff --git a/packages/harness/src/daemon.ts b/packages/harness/src/daemon.ts new file mode 100644 index 0000000..69f8452 --- /dev/null +++ b/packages/harness/src/daemon.ts @@ -0,0 +1,354 @@ +import { spawn, spawnSync } from "node:child_process"; +import { + accessSync, + chmodSync, + mkdirSync, + mkdtempSync, + renameSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { basename, join } from "node:path"; + +import type { Logger } from "@loupe/logger"; + +/** + * A whip provider + model catalog, declared in loupe's config so the review + * workflow doesn't have to hand-write `~/.whipcode/config.json` in a CI step. + * When present and its API key is available, loupe materializes it into a + * throwaway home and starts a dedicated daemon there — never touching a + * developer's real `~/.whipcode`. + */ +export type WhipConfig = { + /** OpenAI-compatible provider whip routes through. */ + readonly provider: { + /** Provider key (e.g. "inference-net"). */ + readonly name: string; + readonly baseUrl: string; + /** Env var whip reads the API key from at runtime (e.g. "INFERENCE_API_KEY"). */ + readonly apiKeyEnv: string; + /** Display label; defaults to `name`. */ + readonly label?: string; + }; + /** Models whip may route to (the panel). The first is the default if unset. */ + readonly models: readonly string[]; + /** Default model; defaults to the review's model, else the first in `models`. */ + readonly defaultModel?: string; +}; + +/** + * The whip release loupe installs on demand when no `whipcode` is on PATH. + * Pinned so the vendored SDK (vendor/@whip/VERSION) and the daemon it talks to + * come from the same release; bump both together. + */ +export const PINNED_WHIP_TAG = "v1.0.0"; + +/** Minimal logging surface so the installer works without a real logger. */ +type WhipLogger = Pick; + +/** The cache path where the pinned binary lives once installed. */ +export function pinnedWhipPath(): string { + const home = process.env.HOME ?? tmpdir(); + return join(home, ".loupe", "bin", "whipcode"); +} + +/** The release asset for this platform (`whipcode-linux-x64` and friends). */ +export function pinnedWhipAsset( + platform = process.platform, + arch = process.arch, +): string { + const os = platform === "darwin" ? "darwin" : "linux"; + return `whipcode-${os}-${arch === "arm64" ? "arm64" : "x64"}`; +} + +/** True when a complete, executable binary is already at the cache path. */ +function isInstalled(dest: string): boolean { + try { + accessSync(dest); + chmodSync(dest, 0o755); + return true; + } catch { + return false; + } +} + +/** + * Single-flight wrapper: reviewers run concurrently inside one loupe process, + * so simultaneous callers must share one download. The memoized promise makes + * every caller await the same install; on completion or failure the memo + * clears so the next attempt is a fresh one. + */ +let inFlight: Promise | null = null; + +export function installPinnedWhip(log: WhipLogger): Promise { + if (inFlight) return inFlight; + const attempt = downloadPinnedWhip(log).then( + (result) => { + if (inFlight === attempt) inFlight = null; + return result; + }, + (err: unknown) => { + if (inFlight === attempt) inFlight = null; + throw err; + }, + ); + inFlight = attempt; + return attempt; +} + +/** + * One actual install attempt. Downloads into a unique temp dir and renames + * into place, so the destination only ever exists complete; if another + * attempt (or another process) won the rename while we were mid-flight, the + * destination holds a valid binary and we return it. + */ +function downloadPinnedWhip(log: WhipLogger): Promise { + const dest = pinnedWhipPath(); + const binDir = join(dest, ".."); + mkdirSync(binDir, { recursive: true }); + if (isInstalled(dest)) return Promise.resolve(dest); + const url = `https://github.com/context-labs/whip/releases/download/${PINNED_WHIP_TAG}/${pinnedWhipAsset()}`; + // Unique staging dir per attempt: safe against same-process concurrency and + // a same-machine second process. Inside binDir so rename stays on one fs. + const stagingDir = mkdtempSync(join(binDir, ".download-")); + const staging = join(stagingDir, "whipcode"); + return new Promise((resolve) => { + const cleanup = (): void => { + try { + rmSync(stagingDir, { recursive: true, force: true }); + } catch { + // best-effort; an orphaned .download-* temp dir is harmless + } + }; + const child = spawn("curl", ["-fsSL", "--retry", "2", "-o", staging, url]); + child.on("error", (err) => { + cleanup(); + log.warn("pinned whip download failed to start", { error: String(err) }); + resolve(null); + }); + child.on("close", (code) => { + if (code !== 0) { + // A concurrent winner may have finished while we were downloading. + cleanup(); + log.warn("pinned whip download failed", { code, url }); + resolve(isInstalled(dest) ? dest : null); + return; + } + try { + chmodSync(staging, 0o755); + // Atomic within the same filesystem: dest only ever exists complete. + // If another attempt renamed first, its binary is valid - use it. + try { + renameSync(staging, dest); + } catch { + if (!isInstalled(dest)) throw new Error("rename failed and no dest"); + } + resolve(dest); + } catch (err) { + log.warn("failed to stage pinned whip", { error: String(err) }); + resolve(isInstalled(dest) ? dest : null); + } finally { + cleanup(); + } + }); + }); +} + +function onPath(cmd: string): Promise { + return new Promise((resolve) => { + const p = spawn("which", [cmd], { stdio: "ignore" }); + p.on("error", () => resolve(false)); + p.on("close", (code) => resolve(code === 0)); + }); +} + +/** + * The binary loupe drives: LOUPE_WHIP_BIN wins, then a `whipcode` on PATH, + * else the pinned release from the private cache (installed on demand). + */ +export async function resolveWhipBinary(log: WhipLogger): Promise { + const override = process.env.LOUPE_WHIP_BIN; + if (override) return override; + if (await onPath("whipcode")) return "whipcode"; + const installed = await installPinnedWhip(log); + if (!installed) { + throw new Error( + `whipcode is not installed and the pinned ${PINNED_WHIP_TAG} download failed (set LOUPE_WHIP_BIN to a whipcode binary).`, + ); + } + log.info( + `no whipcode on PATH; using pinned release ${PINNED_WHIP_TAG} from ${installed}`, + ); + return installed; +} + +/** + * The env var a whip binary reads its home from: WHIPCODE_HOME for `whipcode`, + * WHIP_HOME for an older `whip`. Derived from the binary name the same way + * whip derives it. + */ +export function homeEnvName(bin: string): string { + return `${basename(bin) + .toUpperCase() + .replace(/[^A-Z0-9]/g, "_")}_HOME`; +} + +/** + * Materialize a WhipConfig into a throwaway config dir and return the env + * that points whip at it. Keeps loupe's `whip` block out of a developer's real + * home and removes the hand-written config step from CI. + */ +export function materializeWhipHome( + cfg: WhipConfig, + bin = "whipcode", +): Record { + const dir = mkdtempSync(join(tmpdir(), "loupe-whip-")); + const providerKey = cfg.provider.name; + const defaultModel = cfg.defaultModel ?? cfg.models[0]; + const config = { + defaultModel, + defaultProvider: providerKey, + providers: { + [providerKey]: { + name: cfg.provider.label ?? providerKey, + baseUrl: cfg.provider.baseUrl, + api: "openai-completions", + apiKeyEnv: cfg.provider.apiKeyEnv, + }, + }, + models: Object.fromEntries( + cfg.models.map((m) => [m, { providers: [providerKey] }]), + ), + }; + writeFileSync(join(dir, "config.json"), JSON.stringify(config, null, 2)); + return { [homeEnvName(bin)]: dir }; +} + +/** A daemon loupe can attach to, and how to let go of it afterwards. */ +export type DaemonHandle = { + readonly bin: string; + /** Unix socket path the SDK connects to. */ + readonly socket: string; + /** Env the daemon was started with (a throwaway home when materialized). */ + readonly env: Record; + /** Stops the daemon only if loupe started it in a throwaway home. */ + stop(): Promise; +}; + +type DaemonStatus = { + state: string; + socket: string; + error?: string; + daemon_build?: string; +}; + +function run( + bin: string, + args: readonly string[], + env: Record, +): string { + const res = spawnSync(bin, args, { encoding: "utf8", env }); + if (res.error) { + const code = (res.error as NodeJS.ErrnoException).code; + if (code === "ENOENT") { + throw new Error( + `whip CLI "${bin}" is not installed (set LOUPE_WHIP_BIN to override).`, + ); + } + throw res.error; + } + if (res.status !== 0) { + throw new Error( + `${bin} ${args.join(" ")} exited ${res.status}: ${res.stderr.slice(0, 2000)}`, + ); + } + return res.stdout; +} + +function status(bin: string, env: Record): DaemonStatus { + return JSON.parse( + run(bin, ["daemon", "status", "--json"], env), + ) as DaemonStatus; +} + +/** Decide where the daemon lives: a throwaway home when the whip block's API + * key is available, else the local login. Pure, so it is testable. */ +export function planDaemonEnv(opts: { + bin: string; + whipConfig?: WhipConfig; + env: Record; +}): { env: Record; throwaway: boolean; warning?: string } { + if (!opts.whipConfig) return { env: opts.env, throwaway: false }; + const keyEnv = opts.whipConfig.provider.apiKeyEnv; + if (!opts.env[keyEnv]) { + return { + env: opts.env, + throwaway: false, + warning: `${keyEnv} is not set; ignoring the whip provider block and using the local whip login`, + }; + } + return { + env: { ...opts.env, ...materializeWhipHome(opts.whipConfig, opts.bin) }, + throwaway: true, + }; +} + +/** + * Make sure a whip daemon is running and return how to reach it. With a + * `whipConfig` whose API key is present (CI), a dedicated daemon starts in a + * throwaway home and is stopped by `stop()`. Otherwise the local daemon is + * used (started if needed) and left running, like the desktop app leaves it. + */ +export async function ensureDaemon(opts: { + whipConfig?: WhipConfig; + /** Resolved secrets to expose to the daemon (e.g. the provider's API key). */ + credentials?: Record; + logger: Logger; +}): Promise { + const log = opts.logger.child("whip-daemon"); + const bin = await resolveWhipBinary(log); + const base: Record = {}; + for (const [k, v] of Object.entries(process.env)) + if (v !== undefined) base[k] = v; + Object.assign(base, opts.credentials ?? {}); + const plan = planDaemonEnv({ bin, whipConfig: opts.whipConfig, env: base }); + if (plan.warning) log.warn(plan.warning); + if (plan.throwaway) { + log.info("Starting a dedicated whip daemon", { + home: plan.env[homeEnvName(bin)], + provider: opts.whipConfig?.provider.name, + }); + } + + let current = status(bin, plan.env); + if (current.state !== "running") { + log.info("Starting whip daemon", { bin, state: current.state }); + run(bin, ["daemon", "start"], plan.env); + current = status(bin, plan.env); + if (current.state !== "running") { + throw new Error( + `whip daemon did not start: ${current.error ?? current.state}`, + ); + } + } + log.debug("whip daemon ready", { + socket: current.socket, + build: current.daemon_build, + }); + return { + bin, + socket: current.socket, + env: plan.env, + stop: async () => { + if (!plan.throwaway) return; + try { + run(bin, ["daemon", "stop"], plan.env); + } catch (err) { + log.warn("Could not stop the dedicated whip daemon", { + error: err instanceof Error ? err.message : String(err), + }); + } + }, + }; +} diff --git a/packages/harness/src/hooks.ts b/packages/harness/src/hooks.ts new file mode 100644 index 0000000..1c63c93 --- /dev/null +++ b/packages/harness/src/hooks.ts @@ -0,0 +1,87 @@ +import { resolve, sep } from "node:path"; + +import type { BeforeToolEvent, BeforeToolResult } from "@whip/sdk/agents"; + +type ToolCall = Pick; + +const deny = (reason: string): BeforeToolResult => ({ + decision: "deny", + reason, +}); + +/** A string argument, or "" when absent or not a string. */ +const text = (value: unknown): string => + typeof value === "string" ? value : ""; + +/** Is `path` (relative to `root` when not absolute) inside `root`? */ +export function inside(root: string, path: string): boolean { + const base = resolve(root); + const target = resolve(base, path); + return target === base || target.startsWith(base + sep); +} + +/** + * A reviewer already lacks the write capability; this hook makes an attempted + * edit visible as a hook decision in the run log instead of a bare capability + * error, so a prompt that is failing to keep the model read-only shows up. + */ +export function reviewerBeforeTool( + call: ToolCall, +): BeforeToolResult | undefined { + if (call.operation === "files.write" || call.operation === "files.patch") { + return deny("reviewers are read-only"); + } + return undefined; +} + +/** Shell commands the fixer may never run: loupe owns git, and nothing may + * escalate or delete recursively. */ +const FORBIDDEN_SHELL: readonly RegExp[] = [ + /\bgit\s+(commit|push|reset|checkout|switch|rebase|merge|clean|stash|tag|branch\s+-[dD])\b/, + /\brm\s+-[a-zA-Z]*[rR]/, + /\bsudo\b/, + /\b(curl|wget)\b[^|]*\|\s*(ba|z)?sh\b/, +]; + +/** The operations the fixer hook inspects; everything else passes untouched. */ +export const FIXER_HOOK_OPERATIONS = [ + "shell.run", + "shell.start", + "files.write", + "files.patch", +] as const; + +/** The operations the reviewer hook inspects. */ +export const REVIEWER_HOOK_OPERATIONS = ["files.write", "files.patch"] as const; + +/** + * `@loupe fix` holds write and shell so it can edit and run tests, but edits + * stay inside the checkout and it never touches git: loupe commits and pushes. + */ +export function fixerBeforeTool( + cwd: string, + call: ToolCall, +): BeforeToolResult | undefined { + const args = call.arguments; + switch (call.operation) { + case "shell.run": + case "shell.start": { + const command = text(args["command"]); + const hit = FORBIDDEN_SHELL.find((re) => re.test(command)); + if (hit) + return deny( + `the fixer may not run this command: ${command.slice(0, 120)}`, + ); + return undefined; + } + case "files.write": + case "files.patch": { + if (!inside(cwd, text(args["path"]))) { + return deny("edits must stay inside the checkout"); + } + return undefined; + } + default: + return undefined; + } +} diff --git a/packages/harness/src/index.ts b/packages/harness/src/index.ts index aecf7b5..134721e 100644 --- a/packages/harness/src/index.ts +++ b/packages/harness/src/index.ts @@ -1,14 +1,17 @@ -import { spawn } from "node:child_process"; -import { mkdtempSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - import type { Logger } from "@loupe/logger"; +import type { Schema } from "@whip/sdk"; +import type { AgentDefinition } from "@whip/sdk/agents"; +import type { WhipClient } from "@whip/sdk/node"; +import { chatAgent, fixerAgent, reviewerAgent, verifierAgent } from "./agents"; +import { runAgent, type RunOutcome } from "./client"; import { HarnessError, classifyHarnessError } from "./errors"; -import type { HarnessTraceEvent } from "./trace"; -import { envSecretValues, redactSecrets } from "./trace"; -import { resolveWhipBinary } from "./whip-binary"; +import { turnEventToTrace } from "./progress"; +import { + envSecretValues, + redactSecrets, + type HarnessTraceEvent, +} from "./trace"; export { HarnessError, @@ -17,65 +20,86 @@ export { type HarnessErrorKind, } from "./errors"; export * from "./trace"; +export { + PINNED_WHIP_TAG, + ensureDaemon, + homeEnvName, + installPinnedWhip, + materializeWhipHome, + pinnedWhipAsset, + pinnedWhipPath, + planDaemonEnv, + resolveWhipBinary, +} from "./daemon"; +export type { DaemonHandle, WhipConfig } from "./daemon"; +export { connect, runAgent, serveAgent } from "./client"; +export type { + AgentRun, + RunOutcome, + RunSpec, + ServedAgent, + SessionParams, +} from "./client"; +export { RunProgress, turnEventToTrace } from "./progress"; +export { + FIXER_HOOK_OPERATIONS, + REVIEWER_HOOK_OPERATIONS, + fixerBeforeTool, + inside, + reviewerBeforeTool, +} from "./hooks"; +export { + chatAgent, + contentId, + definitionId, + fixerAgent, + reviewerAgent, + verifierAgent, +} from "./agents"; +export type { WhipClient } from "@whip/sdk/node"; +export type { AgentDefinition } from "@whip/sdk/agents"; + +/** Reasoning effort set on each session natively (whip's `session.effort`). */ +export type ReasoningEffort = "low" | "medium" | "high"; -/** - * A whip provider + model catalog, declared in loupe's config so the review - * workflow doesn't have to hand-write `~/.whip/config.json` in a CI step. When - * present, the whip harness materializes it into a throwaway config dir and - * points `WHIP_HOME` at it — never touching a developer's real `~/.whip`. - */ -export type WhipConfig = { - /** OpenAI-compatible provider whip routes through. */ - readonly provider: { - /** Provider key (e.g. "inference-net"). */ - readonly name: string; - readonly baseUrl: string; - /** Env var whip reads the API key from at runtime (e.g. "INFERENCE_API_KEY"). */ - readonly apiKeyEnv: string; - /** Display label; defaults to `name`. */ - readonly label?: string; - }; - /** Models whip may route to (the panel). The first is the default if unset. */ - readonly models: readonly string[]; - /** Default model; defaults to the review's model, else the first in `models`. */ - readonly defaultModel?: string; -}; +/** The loupe agents: which definition a context runs under. */ +export type AgentKind = "reviewer" | "verifier" | "chat" | "fixer"; /** - * What a harness needs to run a review: the system prompt (reviewer persona + - * output contract) and the user prompt (the diff), the model to use, a working - * directory it may read from, the resolved secrets to inject into its subprocess - * env (e.g. ANTHROPIC_API_KEY), and a logger so subprocess output is observable. + * What a harness needs to run one turn: which loupe agent, the system prompt + * that becomes its persona, the user prompt for the turn, the output contract + * its final message must match (none for prose), the model and provider, + * a working directory its file tools operate in, and a logger so the run is + * observable. The daemon is process-wide; nothing here chooses it. */ -/** Reasoning effort passed to the harness natively (whip defaultEffort, claude --effort, codex model_reasoning_effort). */ -export type ReasoningEffort = "low" | "medium" | "high"; - export type HarnessContext = { + readonly agent: AgentKind; + /** Output contract the daemon validates the final message against; the + * validated value comes back as `RunOutcome.output`. Absent for prose. */ + readonly output?: Schema; + /** Label folded into the definition id (e.g. the reviewer name). */ + readonly name?: string; readonly systemPrompt: string; readonly userPrompt: string; - /** Model id, harness-specific (e.g. "kimi-k3", "claude-opus-4-8"). */ + /** Model id (e.g. "kimi-k3"); empty uses the daemon's default. */ readonly model?: string; + /** Provider the model is routed through (the whip block's); empty uses the + * daemon's default, which on a desktop daemon may not carry the model. */ + readonly provider?: string; /** Allow the agent to use tools and explore the checkout (vs diff-only). */ readonly agentic?: boolean; readonly workdir: string; - readonly env: Record; - /** whip provider/model catalog to materialize into a throwaway WHIP_HOME. */ - readonly whipConfig?: WhipConfig; /** Cap on the agentic tool loop; harness default (10) when unset. */ readonly maxTurns?: number; - /** - * Native reasoning effort. Unset leaves the harness's own default in place - * (whip picks a model-aware default; claude/codex use their config). - */ + /** Native reasoning effort. Unset leaves the daemon's own default in place. */ readonly reasoning?: ReasoningEffort; /** Stable prompt-cache key (e.g. repo/reviewer) so the provider reuses the - * cached system prefix across runs. Passed to whip as -cache-key. */ + * cached system prefix across runs. Passed to whip as `cache_key`. */ readonly cacheKey?: string; /** * Optional trace sink. When supplied, every harness event (reasoning deltas, * text, tool_start/tool_end, done/error) is normalized and emitted here so a - * caller can observe/record the run's progress and outcome without parsing - * raw subprocess output. No-op when unset. + * caller can observe/record the run's progress and outcome. No-op when unset. */ readonly trace?: (event: HarnessTraceEvent) => void; /** @@ -88,477 +112,94 @@ export type HarnessContext = { }; /** - * A coding-agent CLI wrapped as a reviewer. `review` runs the CLI to completion - * and returns its raw stdout; parsing findings out of that text is the core's - * job, not the harness's — keeps adapters dumb and swappable. + * The seam the review pipeline drives. `review` runs one turn to completion + * and returns its final text and, under an output contract, the validated + * value; turning that into findings is the core's job, not the harness's. */ export type Harness = { readonly name: string; - /** Which secret keys this harness expects to find in `ctx.env`. */ - readonly credentialKeys: readonly string[]; - /** Is the CLI actually installed and runnable? */ - available(): Promise; - review(ctx: HarnessContext): Promise; + review(ctx: HarnessContext): Promise; }; -function commandExists(cmd: string): Promise { - return new Promise((resolve) => { - const p = spawn("which", [cmd], { stdio: "ignore" }); - p.on("error", () => resolve(false)); - p.on("close", (code) => resolve(code === 0)); - }); -} - -/** - * Run a command, feed `stdin` in, resolve with stdout. stderr is streamed to the - * logger at debug (live visibility into what the agent is doing), and the full - * stdout is logged at debug on completion so an empty or non-JSON response is - * diagnosable. A non-zero exit rejects with stderr. - */ -function runCli( - cmd: string, - args: readonly string[], - stdin: string, - ctx: HarnessContext, -): Promise { - const log = ctx.logger.child(cmd); - const secrets = envSecretValues({ ...process.env, ...ctx.env }); - const emit = (event: HarnessTraceEvent): void => - ctx.trace?.({ - ...event, - ...(event.type === "done" - ? { text: redactSecrets(event.text, secrets) } - : event.type === "error" - ? { error: redactSecrets(event.error, secrets) } - : {}), - model: event.model ?? ctx.model, - phase: event.phase ?? ctx.phase, - }); - log.debug("Spawning harness", { args, cwd: ctx.workdir, model: ctx.model }); - return new Promise((resolve, reject) => { - const child = spawn(cmd, args, { - cwd: ctx.workdir, - env: { ...process.env, ...ctx.env }, - }); - let stdout = ""; - let stderr = ""; - child.stdout.on("data", (d: Buffer) => { - const chunk = d.toString(); - stdout += chunk; - log.debug(chunk.trimEnd()); - }); - child.stderr.on("data", (d: Buffer) => { - const chunk = d.toString(); - stderr += chunk; - log.debug(chunk.trimEnd()); - }); - child.on("error", (err) => { - emit({ type: "error", error: err.message }); - reject( - new HarnessError(err instanceof Error ? err.message : String(err)), - ); - }); - child.on("close", (code) => { - log.debug("Harness exited", { code, stdoutChars: stdout.length }); - log.debug("Harness stdout", { stdout }); - if (code === 0) { - emit({ type: "done", text: stdout }); - resolve(stdout); - } else { - const message = `${cmd} exited ${code}: ${stderr.slice(0, 2000)}`; - emit({ type: "error", error: message }); - reject(new HarnessError(message, classifyHarnessError(message))); - } - }); - child.stdin.write(stdin); - child.stdin.end(); - }); -} - -/** Argument list for `claude -p`; exported so the flag mapping is testable. */ -export function buildClaudeArgs( - ctx: Pick, -): string[] { - const args = [ - "-p", - "--permission-mode", - "plan", - "--append-system-prompt", - ctx.systemPrompt, - ]; - if (ctx.model) args.push("--model", ctx.model); - if (ctx.reasoning) args.push("--effort", ctx.reasoning); - return args; -} - -/** Claude Code CLI: `claude -p` reads the user prompt from stdin. */ -export function claudeHarness(): Harness { - return { - name: "claude", - credentialKeys: ["ANTHROPIC_API_KEY"], - available: () => commandExists("claude"), - review: (ctx) => - runCli("claude", buildClaudeArgs(ctx), ctx.userPrompt, ctx), - }; -} - -/** Argument list for `codex exec`; exported so the flag mapping is testable. */ -export function buildCodexArgs( - ctx: Pick, -): string[] { - const args = ["exec"]; - if (ctx.model) args.push("--model", ctx.model); - if (ctx.reasoning) args.push("-c", `model_reasoning_effort=${ctx.reasoning}`); - args.push("-"); - return args; -} - -/** OpenAI Codex CLI: `codex exec` runs a one-shot prompt from stdin. */ -export function codexHarness(): Harness { - return { - name: "codex", - credentialKeys: ["OPENAI_API_KEY"], - available: () => commandExists("codex"), - review: (ctx) => { - // Codex has no system-prompt flag; prepend it to the piped input. - const stdin = `${ctx.systemPrompt}\n\n---\n\n${ctx.userPrompt}`; - return runCli("codex", buildCodexArgs(ctx), stdin, ctx); - }, - }; -} - -/** - * Stream whip's `--format json` NDJSON event stream, logging activity live - * (tool calls at info, reply text as it arrives at debug) so a long run is - * observable instead of silent. Resolves with the assembled assistant text - * (the review JSON), so the core parses it the same as any other harness. - */ -function runWhipStreaming( - args: readonly string[], - ctx: HarnessContext, - binary = "whipcode", -): Promise { - const log = ctx.logger.child("whip"); - // Known secrets (resolved credential values handed to the subprocess via env) - // are scrubbed from every trace payload downstream so an API key that happens - // to surface in a tool result or reasoning chunk never lands in the summary. - const secrets = envSecretValues({ ...process.env, ...ctx.env }); - const scrub = (s: string | undefined): string | undefined => - s === undefined ? undefined : redactSecrets(s, secrets); - // Scrub every string field on an event (delta/args/result/text/error). - const scrubEvent = (event: HarnessTraceEvent): HarnessTraceEvent => - ({ - ...event, - ...("delta" in event ? { delta: scrub(event.delta) } : {}), - ...("args" in event ? { args: scrub(event.args) } : {}), - ...("result" in event ? { result: scrub(event.result) } : {}), - ...("text" in event ? { text: scrub(event.text) } : {}), - ...("error" in event ? { error: scrub(event.error) } : {}), - }) as HarnessTraceEvent; - // Normalize and forward a raw NDJSON event to the optional trace sink, - // tagging it with the run's model/phase so renderers can label provenance. - const emit = (event: HarnessTraceEvent): void => - ctx.trace?.({ - ...scrubEvent(event), - model: event.model ?? ctx.model, - phase: event.phase ?? ctx.phase, - }); - log.debug("Spawning harness", { args, cwd: ctx.workdir, model: ctx.model }); - return new Promise((resolve, reject) => { - const child = spawn(binary, args, { - cwd: ctx.workdir, - env: { ...process.env, ...ctx.env }, - }); - let buffer = ""; // partial NDJSON line - let text = ""; // accumulated assistant reply - let logged = 0; // chars already flushed to the log - let thinking = 0; // reasoning chars seen - let thinkingLogged = 0; // reasoning chars already flushed to the log - let stderr = ""; - let final: string | undefined; - - const handle = (line: string): void => { - const trimmed = line.trim(); - if (!trimmed) return; - let event: Record; - try { - event = JSON.parse(trimmed) as Record; - } catch { - log.debug(trimmed); // non-JSON note — surface it raw - return; - } - for (const t of whipEventToTrace(event)) emit(t); - switch (event["type"]) { - case "reasoning": - if (typeof event["delta"] === "string") { - thinking += event["delta"].length; - } - if (thinking - thinkingLogged >= 200) { - log.info("thinking", { chars: thinking }); - thinkingLogged = thinking; - } - break; - case "text": - if (typeof event["delta"] === "string") text += event["delta"]; - if (text.length - logged >= 120) { - log.info("streaming reply", { chars: text.length }); - logged = text.length; - } - break; - case "tool_start": - log.info("tool call", { name: event["name"], args: event["args"] }); - break; - case "tool_end": - log.info("tool result", { - name: event["name"], - result: - typeof event["result"] === "string" - ? event["result"].slice(0, 300) - : undefined, - }); - break; - case "done": - final = typeof event["text"] === "string" ? event["text"] : text; - break; - case "error": { - const message = `whip error: ${JSON.stringify(event["error"])}`; - reject(new HarnessError(message, classifyHarnessError(message))); - break; - } - default: - log.debug("event", event); - } - }; - - child.stdout.on("data", (d: Buffer) => { - buffer += d.toString(); - const lines = buffer.split("\n"); - buffer = lines.pop() ?? ""; - for (const line of lines) handle(line); - }); - child.stderr.on("data", (d: Buffer) => { - const chunk = d.toString(); - stderr += chunk; - log.debug(chunk.trimEnd()); - }); - child.on("error", (e) => - reject(new HarnessError(e instanceof Error ? e.message : String(e))), - ); - child.on("close", (code) => { - if (buffer.trim()) handle(buffer); - const out = final ?? text; - log.debug("Harness exited", { code, replyChars: out.length }); - if (code === 0) resolve(out); - else { - const message = `whip exited ${code}: ${stderr.slice(0, 2000)}`; - reject(new HarnessError(message, classifyHarnessError(message))); - } - }); - child.stdin.write(ctx.userPrompt); - child.stdin.end(); - }); -} - -/** - * Normalize one raw whip NDJSON event into the normalized trace events it - * represents. Exported so the mapping is unit-testable without spawning a whip - * process. Unknown event types and malformed payloads map to an empty list. - * Tool args/results are truncated here so a trace consumer never sees an - * unbounded blob. - */ -export function whipEventToTrace(raw: unknown): HarnessTraceEvent[] { - if (!raw || typeof raw !== "object") return []; - const event = raw as Record; - const name = - typeof event["name"] === "string" ? (event["name"] as string) : "tool"; - const serialize = (value: unknown): string | undefined => { - if (value === undefined) return undefined; - if (typeof value === "string") return value.slice(0, 2000); - try { - return JSON.stringify(value).slice(0, 2000); - } catch { - return "[unserializable tool payload]"; - } - }; - switch (event["type"]) { - case "reasoning": - return typeof event["delta"] === "string" - ? [{ type: "reasoning", delta: event["delta"] as string }] - : []; - case "text": - return typeof event["delta"] === "string" - ? [{ type: "text", delta: event["delta"] as string }] - : []; - case "tool_start": - return [ - { - type: "tool_start", - name, - args: serialize(event["args"]), - }, - ]; - case "tool_end": - return [ - { - type: "tool_end", - name, - result: serialize(event["result"]), - }, - ]; - case "done": - return [ - { - type: "done", - text: - typeof event["text"] === "string" ? (event["text"] as string) : "", - }, - ]; - case "error": - return [ - { type: "error", error: JSON.stringify(event["error"] ?? event) }, - ]; - default: - return []; +/** The definition a context runs under: the prompt as persona, the contract + * on the final message, and the modules and hooks its kind calls for. */ +export function agentFor(ctx: HarnessContext): AgentDefinition { + switch (ctx.agent) { + case "reviewer": + return reviewerAgent({ + name: ctx.name, + systemPrompt: ctx.systemPrompt, + agentic: ctx.agentic ?? false, + output: ctx.output, + }); + case "verifier": + return verifierAgent({ + systemPrompt: ctx.systemPrompt, + agentic: ctx.agentic ?? false, + output: ctx.output, + }); + case "chat": + return chatAgent(ctx.systemPrompt); + case "fixer": + return fixerAgent(ctx.systemPrompt, ctx.workdir); } } /** - * Materialize a WhipConfig into a throwaway config dir and return the env - * (WHIP_HOME) that points whip at it. Keeps loupe's `whip` block out of a - * developer's real ~/.whip and removes the hand-written config step from CI. + * whip through its SDK: each `review` serves the context's definition from + * this process, opens one session pinned to it on the shared daemon, runs the + * turn, and lets both go. The daemon validates the final message against the + * definition's output contract and corrects it once; the turn's typed events + * feed the log and the trace sink. Known secrets are scrubbed from every + * trace payload so an API key surfacing in a tool result never lands in a + * step summary. */ -export function materializeWhipHome( - cfg: WhipConfig, - reasoning?: ReasoningEffort, -): Record { - const dir = mkdtempSync(join(tmpdir(), "loupe-whip-")); - const providerKey = cfg.provider.name; - const defaultModel = cfg.defaultModel ?? cfg.models[0]; - const config = { - defaultModel, - defaultProvider: providerKey, - // An explicit effort pins whip's reasoning_effort for every request. Left - // out, whip picks its model-aware default. - ...(reasoning ? { defaultEffort: reasoning } : {}), - providers: { - [providerKey]: { - name: cfg.provider.label ?? providerKey, - baseUrl: cfg.provider.baseUrl, - api: "openai-completions", - apiKeyEnv: cfg.provider.apiKeyEnv, - }, - }, - models: Object.fromEntries( - cfg.models.map((m) => [m, { providers: [providerKey] }]), - ), - }; - writeFileSync(join(dir, "config.json"), JSON.stringify(config, null, 2)); - return { WHIP_HOME: dir }; -} - -/** - * Decide whether a whip failure is a prompt-cache-key incompatibility worth one - * retry without the key. Two cases, both requiring a key was actually sent: - * 1. an older whip that predates the `-cache-key` flag ("flag provided but - * not defined: -cache-key"); and - * 2. a provider that accepts the flag but rejects the resulting - * `prompt_cache_key` argument as unrecognized — some OpenAI-compatible - * endpoints strict-validate unknown fields. Those providers cache the - * stable system prefix automatically by prefix match, so dropping the key - * costs nothing and unblocks the model. - * Exported so the retry condition is unit-testable without spawning whip. - */ -export function shouldRetryWithoutCacheKey( - err: unknown, - cacheKey?: string, -): boolean { - if (!cacheKey) return false; - const msg = String(err); - return ( - /flag provided but not defined: -cache-key/.test(msg) || - /prompt_cache_key/i.test(msg) - ); -} - -/** - * whip (context-labs custom harness): runs `whip run --format json` and streams - * the event log live. `-system` sets the reviewer/output/headless instructions. - * By default it self-authenticates from its own local login (~/.whip/); when a - * `whipConfig` is supplied, loupe writes a throwaway WHIP_HOME config declaring - * the provider + model panel instead. `-max-turns` caps the tool loop as a - * safety net in case the model ignores the headless directive. - * - * Binary resolution: LOUPE_WHIP_BIN, else `whipcode` on PATH, else the pinned - * release (see whip-binary.ts) downloaded on demand. - */ -export function whipHarness(): Harness { +export function whipHarness( + client: WhipClient, + secrets: readonly string[] = envSecretValues(process.env), +): Harness { return { name: "whip", - credentialKeys: [], - available: () => resolveWhipBinary(null).then((b) => b !== null), review: async (ctx) => { - const binary = (await resolveWhipBinary(ctx.logger)) ?? "whipcode"; + const scrub = (s: string | undefined): string | undefined => + s === undefined ? undefined : redactSecrets(s, secrets); + const emit = (event: HarnessTraceEvent): void => + ctx.trace?.({ + ...event, + ...("delta" in event ? { delta: scrub(event.delta) } : {}), + ...("args" in event ? { args: scrub(event.args) } : {}), + ...("result" in event ? { result: scrub(event.result) } : {}), + ...("text" in event ? { text: scrub(event.text) } : {}), + ...("error" in event ? { error: scrub(event.error) } : {}), + model: event.model ?? ctx.model, + phase: event.phase ?? ctx.phase, + } as HarnessTraceEvent); // Agentic reviews need room to explore the checkout with tools; headless // diff-only reviews should answer in one turn, capped as a safety net. // The agentic cap is configurable (config.json maxTurns / --max-turns). - const maxTurns = ctx.agentic ? String(ctx.maxTurns ?? 10) : "10"; - const args = [ - "run", - "--format", - "json", - "-quiet", - "-no-session", - "-max-turns", - maxTurns, - "-system", - ctx.systemPrompt, - ]; - if (ctx.model) args.push("-m", ctx.model); - const whipEnv: Record = ctx.whipConfig - ? materializeWhipHome(ctx.whipConfig, ctx.reasoning) - : {}; - if (ctx.reasoning && !ctx.whipConfig) { - ctx.logger - .child("whip") - .warn( - "reasoning is set but no whip config block is materialized; whip's own default effort applies", - ); + const maxTurns = ctx.agentic ? (ctx.maxTurns ?? 10) : 10; + try { + const outcome = await runAgent(client, { + agent: agentFor(ctx), + cwd: ctx.workdir, + prompt: ctx.userPrompt, + model: ctx.model, + provider: ctx.provider, + effort: ctx.reasoning, + maxTurns, + cacheKey: ctx.cacheKey, + onEvent: (event) => { + for (const t of turnEventToTrace(event)) emit(t); + }, + logger: ctx.logger, + }); + emit({ type: "done", text: outcome.text }); + return outcome; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + emit({ type: "error", error: message }); + throw new HarnessError(message, classifyHarnessError(message)); } - const runCtx = { ...ctx, env: { ...ctx.env, ...whipEnv } }; - // Stable cache key → the provider reuses the cached prefix across runs. - // Tolerate an older whip that predates the flag: on "flag not defined: - // -cache-key", retry without it (caching off, but the review still runs). - const withKey = ctx.cacheKey - ? [...args, "-cache-key", ctx.cacheKey] - : args; - return runWhipStreaming(withKey, runCtx, binary).catch((err: unknown) => { - // A reviewer that opted out via promptCache:false has cacheKey - // undefined, so this never fires for it — no wasted retry. - if (shouldRetryWithoutCacheKey(err, ctx.cacheKey)) { - ctx.logger - .child("whip") - .warn( - "prompt cache key rejected; retrying without it. " + - "Set promptCache:false to skip this retry.", - ); - return runWhipStreaming(args, runCtx, binary); - } - throw err; - }); }, }; } - -export function registry(): Map { - const harnesses = [claudeHarness(), codexHarness(), whipHarness()]; - return new Map(harnesses.map((h) => [h.name, h])); -} - -export function getHarness(name: string): Harness { - const h = registry().get(name); - if (!h) { - const known = [...registry().keys()].join(", "); - throw new Error(`Unknown harness "${name}". Known: ${known}`); - } - return h; -} diff --git a/packages/harness/src/progress.ts b/packages/harness/src/progress.ts new file mode 100644 index 0000000..cdc72ad --- /dev/null +++ b/packages/harness/src/progress.ts @@ -0,0 +1,227 @@ +import type { Logger } from "@loupe/logger"; +import type { TurnEvent, TurnUsage } from "@whip/sdk"; + +import type { HarnessTraceEvent } from "./trace"; + +const clip = (s: string | undefined, n = 200): string | undefined => + s === undefined ? undefined : s.length > n ? `${s.slice(0, n)}…` : s; + +type Accounting = { + reported_cost_micros: string; + estimated_cost_micros: string; +}; + +/** + * Normalize one typed turn event into the trace events it represents, so the + * Actions step summary keeps rendering reasoning, visible text, tool calls + * and results without knowing the SDK. Cells (the model's Starlark) and host + * calls (module operations) are both "tools" to the trace. `done` and `error` + * are the harness's to emit: the turn's end event carries no text, and its + * failure is what the harness throws. Tool payloads are truncated here so a + * trace consumer never sees an unbounded blob. + */ +export function turnEventToTrace(event: TurnEvent): HarnessTraceEvent[] { + switch (event.type) { + case "reasoning": + return [{ type: "reasoning", delta: event.delta }]; + case "text": + return [{ type: "text", delta: event.delta }]; + case "cell": + if (event.status === "running" && event.code !== undefined) { + return [ + { type: "tool_start", name: "cell", args: clip(event.code, 2000) }, + ]; + } + if (event.status === "completed") { + return [ + { + type: "tool_end", + name: "cell", + result: clip(event.result ?? event.output, 2000), + }, + ]; + } + return []; + case "host": + if (event.status === "running") { + return [ + { + type: "tool_start", + name: event.operation, + args: clip(event.summary, 2000), + }, + ]; + } + return [ + { + type: "tool_end", + name: event.operation, + result: clip(event.error ?? event.status, 2000), + }, + ]; + default: + return []; + } +} + +/** + * Turn a run's typed turn events into the live log a `whip run` gave: + * throttled `thinking` / `streaming reply` counters, one line per cell and + * host call, hook decisions, child lifecycle, and a `run finished` line with + * cost. Events from a child agent carry its id. Pure over events, so it is + * unit-testable with scripted ones. + */ +export class RunProgress { + private text = 0; + private textLogged = 0; + private thinking = 0; + private thinkingLogged = 0; + private costMicros: bigint | undefined; + /** Sum of the per-call cost carried on usage events, in dollars. */ + private usageCost = 0; + + constructor( + private readonly log: Logger, + private readonly rootId: string, + ) {} + + observe(event: TurnEvent): void { + const who = + event.agentId && event.agentId !== this.rootId + ? { agent: event.agentId } + : {}; + switch (event.type) { + case "reasoning": + if (who.agent) return; + this.thinking += event.delta.length; + if (this.thinking - this.thinkingLogged >= 200) { + this.log.info("thinking", { chars: this.thinking }); + this.thinkingLogged = this.thinking; + } + return; + case "text": + if (who.agent) return; + this.text += event.delta.length; + if (this.text - this.textLogged >= 120) { + this.log.info("streaming reply", { chars: this.text }); + this.textLogged = this.text; + } + return; + case "cell": + if (event.status === "running" && event.code !== undefined) { + this.log.info("cell", { ...who, code: clip(event.code) }); + } else if (event.status === "completed") { + this.log.debug("cell result", { + ...who, + result: clip(event.result, 600), + }); + } else if (event.output !== undefined) { + this.log.debug("cell output", { + ...who, + text: clip(event.output, 600), + }); + } + return; + case "host": + if (event.status === "running") { + this.log.info("host call", { + ...who, + operation: event.operation, + args: clip(event.summary), + }); + } else if (event.status === "failed") { + this.log.warn("host call failed", { + ...who, + operation: event.operation, + error: clip(event.error, 300), + }); + } else { + this.log.debug("host result", { + ...who, + operation: event.operation, + status: event.status, + }); + } + return; + case "progress": + this.log.info("tool progress", { + ...who, + operation: event.operation, + text: clip(event.text), + }); + return; + case "hook": + this.log.info("hook decision", { + ...who, + hook: event.hook, + decision: event.decision, + operation: event.operation, + reason: clip(event.reason), + }); + return; + case "child": + if (event.kind === "agent.admitted") { + this.log.info("child agent spawned", { agent: event.childId }); + } else if (event.kind === "agent.turn.failed") { + this.log.warn("child turn failed", { + agent: event.childId, + status: event.status, + }); + } else { + this.log.debug("child", { + agent: event.childId, + kind: event.kind, + status: event.status, + }); + } + return; + case "usage": + this.usageCost += event.usage.usage.cost ?? 0; + return; + case "end": + if (event.error) { + this.log.warn(who.agent ? "child turn failed" : "turn failed", { + ...who, + error: clip(event.error, 300), + }); + } + return; + case "raw": { + const payload = event.event.payload as + | { accounting?: Accounting | null } + | undefined; + if (event.event.kind === "stream.accounting" && payload?.accounting) { + // Providers without a price list report 0; the estimate still counts. + const reported = BigInt(payload.accounting.reported_cost_micros); + this.costMicros = + reported > 0n + ? reported + : BigInt(payload.accounting.estimated_cost_micros); + } else { + this.log.debug("event", { ...who, kind: event.event.kind }); + } + return; + } + default: + this.log.debug("event", { ...who, kind: event.type }); + } + } + + /** Log the run's totals once the turn has settled. */ + finish(usage?: TurnUsage): void { + const props: Record = { replyChars: this.text }; + if (this.thinking > 0) props["thinkingChars"] = this.thinking; + if (usage) { + props["promptTokens"] = usage.usage.prompt_tokens; + props["completionTokens"] = usage.usage.completion_tokens; + } + // Accounting events, when they fall inside the turn window, are authoritative; + // otherwise the per-call cost on usage events adds up to the same number. + if (this.costMicros !== undefined) { + props["costUsd"] = Number(this.costMicros) / 1_000_000; + } else if (this.usageCost > 0) { + props["costUsd"] = this.usageCost; + } + this.log.info("run finished", props); + } +} diff --git a/packages/harness/src/whip-binary.ts b/packages/harness/src/whip-binary.ts deleted file mode 100644 index 655a556..0000000 --- a/packages/harness/src/whip-binary.ts +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Pinned-whip installer: which `whipcode` binary loupe drives. The harness in - * index.ts only calls resolveWhipBinary(), which prefers LOUPE_WHIP_BIN, then a - * `whipcode` on PATH, then a pinned release (PINNED_WHIP_TAG) downloaded on - * demand into a private cache dir. The download is single-flighted so - * concurrent reviewers inside one loupe process share one install. - */ - -import { spawn } from "node:child_process"; -import { - accessSync, - chmodSync, - mkdirSync, - mkdtempSync, - renameSync, - rmSync, -} from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -import type { Logger } from "@loupe/logger"; - -/** - * The whip release loupe installs on demand when no `whipcode` is on PATH. - * Pinned so the vendored SDK (vendor/@whip/VERSION) and the daemon it talks to - * come from the same release; bump both together. - */ -export const PINNED_WHIP_TAG = "v1.0.0"; - -/** Minimal logging surface so this module works without a real logger. */ -type WhipLogger = Pick; - -const noLogger: WhipLogger = { - info: () => {}, - warn: () => {}, -}; - -/** The cache path where the pinned binary lives once installed. */ -export function pinnedWhipPath(): string { - const home = process.env.HOME ?? tmpdir(); - return join(home, ".loupe", "bin", "whipcode"); -} - -/** The release asset for this platform (`whipcode-linux-x64` and friends). */ -export function pinnedWhipAsset( - platform = process.platform, - arch = process.arch, -): string { - const os = platform === "darwin" ? "darwin" : "linux"; - return `whipcode-${os}-${arch === "arm64" ? "arm64" : "x64"}`; -} - -/** True when a complete, executable binary is already at the cache path. */ -function isInstalled(dest: string): boolean { - try { - accessSync(dest); - chmodSync(dest, 0o755); - return true; - } catch { - return false; - } -} - -/** - * Single-flight wrapper: reviewers run concurrently inside one loupe process, - * so simultaneous callers must share one download. The memoized promise makes - * every caller await the same install; on completion or failure the memo - * clears so the next attempt is a fresh one. - */ -let inFlight: Promise | null = null; - -export function installPinnedWhip( - logger: WhipLogger | null, -): Promise { - if (inFlight) return inFlight; - const log = logger ?? noLogger; - const attempt = downloadPinnedWhip(log).then( - (result) => { - if (inFlight === attempt) inFlight = null; - return result; - }, - (err: unknown) => { - if (inFlight === attempt) inFlight = null; - throw err; - }, - ); - inFlight = attempt; - return attempt; -} - -/** - * One actual install attempt. Downloads into a unique temp dir and renames - * into place, so the destination only ever exists complete; if another - * attempt (or another process) won the rename while we were mid-flight, the - * destination holds a valid binary and we return it. - */ -function downloadPinnedWhip(log: WhipLogger): Promise { - const dest = pinnedWhipPath(); - const binDir = join(dest, ".."); - mkdirSync(binDir, { recursive: true }); - if (isInstalled(dest)) return Promise.resolve(dest); - const url = `https://github.com/context-labs/whip/releases/download/${PINNED_WHIP_TAG}/${pinnedWhipAsset()}`; - // Unique staging dir per attempt: safe against same-process concurrency and - // a same-machine second process. Inside binDir so rename stays on one fs. - const stagingDir = mkdtempSync(join(binDir, ".download-")); - const staging = join(stagingDir, "whipcode"); - return new Promise((resolve) => { - const cleanup = (): void => { - try { - rmSync(stagingDir, { recursive: true, force: true }); - } catch { - // best-effort; an orphaned .download-* temp dir is harmless - } - }; - const child = spawn("curl", ["-fsSL", "--retry", "2", "-o", staging, url]); - child.on("error", (err) => { - cleanup(); - log.warn("pinned whip download failed to start", { error: String(err) }); - resolve(null); - }); - child.on("close", (code) => { - if (code !== 0) { - // A concurrent winner may have finished while we were downloading. - cleanup(); - log.warn("pinned whip download failed", { code, url }); - resolve(isInstalled(dest) ? dest : null); - return; - } - try { - chmodSync(staging, 0o755); - // Atomic within the same filesystem: dest only ever exists complete. - // If another attempt renamed first, its binary is valid - use it. - try { - renameSync(staging, dest); - } catch { - if (!isInstalled(dest)) throw new Error("rename failed and no dest"); - } - resolve(dest); - } catch (err) { - log.warn("failed to stage pinned whip", { error: String(err) }); - resolve(isInstalled(dest) ? dest : null); - } finally { - cleanup(); - } - }); - }); -} - -function onPath(cmd: string): Promise { - return new Promise((resolve) => { - const p = spawn("which", [cmd], { stdio: "ignore" }); - p.on("error", () => resolve(false)); - p.on("close", (code) => resolve(code === 0)); - }); -} - -/** - * The one entry point the whip harness uses: resolve which binary to spawn. - * LOUPE_WHIP_BIN wins, then a `whipcode` on PATH, else the pinned release from - * the private cache (installed on demand). Returns null only when nothing is - * on PATH and the download failed. - */ -export async function resolveWhipBinary( - logger: WhipLogger | null, -): Promise { - const override = process.env.LOUPE_WHIP_BIN; - if (override) return override; - if (await onPath("whipcode")) return "whipcode"; - const installed = await installPinnedWhip(logger); - if (installed) { - logger?.info( - `no whipcode on PATH; using pinned release ${PINNED_WHIP_TAG} from ${installed}`, - ); - } - return installed; -} diff --git a/packages/harness/tests/agents.test.ts b/packages/harness/tests/agents.test.ts new file mode 100644 index 0000000..9075ef6 --- /dev/null +++ b/packages/harness/tests/agents.test.ts @@ -0,0 +1,104 @@ +import { z } from "zod"; +import { describe, expect, it } from "vitest"; + +import { + chatAgent, + contentId, + definitionId, + fixerAgent, + reviewerAgent, + verifierAgent, +} from "../src/agents"; + +const output = z.object({ summary: z.string() }); + +describe("definitionId", () => { + it("fits reviewer names to whip's id rules", () => { + expect(definitionId("code")).toBe("loupe-code"); + expect(definitionId("Migrations Risk")).toBe("loupe-migrations-risk"); + expect(definitionId("")).toBe("loupe-review"); + expect(definitionId("x".repeat(80))).toMatch(/^loupe-x{58}$/); + }); +}); + +describe("contentId", () => { + it("changes with the content and stays within whip's id rules", () => { + const a = contentId("bugs", "prompt A", ["context"]); + const b = contentId("bugs", "prompt B", ["context"]); + expect(a).toMatch(/^loupe-bugs-[0-9a-f]{8}$/); + expect(a).not.toBe(b); + expect(contentId("bugs", "prompt A", ["context"])).toBe(a); + expect(contentId("x".repeat(80), "p")).toMatch(/^loupe-x{49}-[0-9a-f]{8}$/); + }); +}); + +describe("agent definitions", () => { + it("gives an agentic reviewer read-only file access, a write-attempt hook, and its output contract", () => { + const agent = reviewerAgent({ + name: "bugs", + systemPrompt: "Find bugs.", + agentic: true, + output, + }); + expect(agent.document.id).toMatch(/^loupe-bugs-[0-9a-f]{8}$/); + expect(agent.document.instructions.persona).toBe("Find bugs."); + expect(agent.document.modules).toEqual(["context", "files"]); + expect(agent.document.capabilities).toEqual(["read"]); + expect(agent.document.children).toEqual({}); + expect(agent.document.hooks?.before_tool?.operations).toEqual([ + "files.write", + "files.patch", + ]); + expect(agent.document.hooks?.before_spawn).toBeNull(); + expect(typeof agent.hooks.before_tool).toBe("function"); + expect(agent.document.output).toMatchObject({ + type: "object", + required: ["summary"], + }); + }); + + it("derives the id from the whole document, so a different prompt or mode is a different agent", () => { + const a = reviewerAgent({ systemPrompt: "p", agentic: false, output }); + expect(a.document.id).toMatch(/^loupe-review-[0-9a-f]{8}$/); + expect(a.document.modules).toEqual(["context"]); + expect(a.document.hooks).toBeNull(); + expect(a.document.id).not.toBe( + reviewerAgent({ systemPrompt: "p", agentic: true, output }).document.id, + ); + expect(a.document.id).not.toBe( + reviewerAgent({ systemPrompt: "q", agentic: false, output }).document.id, + ); + expect(a.document.id).toBe( + reviewerAgent({ systemPrompt: "p", agentic: false, output }).document.id, + ); + }); + + it("gives the verifier the checkout only when the review was agentic, keeps chat diff-only, and lets only the fixer write", () => { + const headless = verifierAgent({ + systemPrompt: "v", + agentic: false, + output, + }); + expect(headless.document.modules).toEqual(["context"]); + expect(headless.document.output).toMatchObject({ type: "object" }); + expect(headless.document.hooks).toBeNull(); + const agentic = verifierAgent({ systemPrompt: "v", agentic: true, output }); + expect(agentic.document.modules).toEqual(["context", "files"]); + expect(agentic.document.capabilities).toEqual(["read"]); + expect(typeof agentic.hooks.before_tool).toBe("function"); + expect(chatAgent("c").document.modules).toEqual(["context"]); + expect(chatAgent("c").document.output).toBeNull(); + const fixer = fixerAgent("f", "/work/repo"); + expect(fixer.document.modules).toEqual(["context", "files", "shell"]); + expect(fixer.document.capabilities).toEqual(["read", "write", "shell"]); + expect(fixer.document.hooks?.before_tool?.operations).toContain( + "shell.run", + ); + expect( + fixer.hooks.before_tool?.({ + operation: "shell.run", + arguments: { command: "git push" }, + } as never), + ).toMatchObject({ decision: "deny" }); + }); +}); diff --git a/packages/harness/tests/client.test.ts b/packages/harness/tests/client.test.ts new file mode 100644 index 0000000..f7b52f2 --- /dev/null +++ b/packages/harness/tests/client.test.ts @@ -0,0 +1,218 @@ +import { createWhipClient } from "@whip/sdk"; +import { defineAgent } from "@whip/sdk/agents"; +import { scriptedDaemon, type ScriptedDaemon } from "@whip/sdk/testing"; +import { describe, expect, it } from "vitest"; + +import { reviewerAgent } from "../src/agents"; +import { runAgent, serveAgent } from "../src/client"; +import { z } from "zod"; + +const output = z.object({ ok: z.boolean() }); +import { recorder } from "./recorder"; + +/** The command.submit requests the runner sent, in order. */ +function commands(daemon: ScriptedDaemon) { + return daemon.current.requests + .filter((r) => r.method === "command.submit") + .map((r) => ({ + operation: String(r.params["operation"]), + payload: r.params["payload"] as Record, + })); +} + +/** The scripted daemon answers every non-submit command with `{}`; the effort + * result must carry the effort back, so script that one reply. */ +function answerEffort(daemon: ScriptedDaemon): ScriptedDaemon { + return daemon.reply("command.submit", (request) => { + if (request.params["operation"] !== "session.effort") return undefined; + const payload = request.params["payload"] as { effort: string }; + return { + operation: "session.effort", + command_id: String(request.params["command_id"]), + ingress_seq: "900", + status: "succeeded", + result: { effort: payload.effort }, + }; + }); +} + +async function connected(daemon: ScriptedDaemon) { + const client = createWhipClient({ + endpoint: daemon.factory, + clientId: "loupe-runner-test", + commandPollMs: 5, + }); + await client.connect(); + return client; +} + +describe("runAgent", () => { + it("serves an agent with hooks, creates a pinned session, configures it, runs the turn, and cleans up", async () => { + const daemon = answerEffort(scriptedDaemon().serveSessions()); + const client = await connected(daemon); + const { log, lines } = recorder(); + const agent = reviewerAgent({ + name: "t", + systemPrompt: "p", + agentic: true, + output, + }); + const played = daemon.turn("root-1", { + steps: [ + { text: "Looking. " }, + { host: { id: "i1", operation: "tools.pr_hunks", summary: "path=x" } }, + { text: "Done." }, + ], + text: "Looking. Done.", + output: { ok: true }, + }); + const seen: string[] = []; + const outcome = await runAgent(client, { + agent, + cwd: "/repo", + prompt: "review", + model: "glm-5.3", + provider: "inference-net", + effort: "high", + maxTurns: 3, + cacheKey: "loupe/o/r/t", + onEvent: (event) => seen.push(event.type), + logger: log, + }); + await played; + client.close(); + + expect(outcome.text).toBe("Looking. Done."); + expect(outcome.output).toEqual({ ok: true }); + const sent = commands(daemon); + expect(sent.map((c) => c.operation)).toEqual([ + "session.create", + "run.configure", + "session.effort", + "submit", + "session.delete", + ]); + expect(sent[2]?.payload).toEqual({ + effort: "high", + persist_default: false, + }); + expect(seen).toContain("host"); + expect(sent[0]?.payload).toMatchObject({ + definition: agent.document.id, + model: "glm-5.3", + provider: "inference-net", + permission_mode: "automatic", + cwd: "/repo", + }); + expect(sent[1]?.payload).toEqual({ + headless: true, + max_turns: 3, + cache_key: "loupe/o/r/t", + }); + expect( + daemon.current.requests.some((r) => r.method === "executor.bind"), + ).toBe(true); + expect(lines.map((l) => l.message)).toContain("host call"); + expect(lines.at(-1)?.message).toBe("run finished"); + }); + + it("only registers an agent without tools or hooks and still runs it", async () => { + const daemon = scriptedDaemon().serveSessions(); + const client = await connected(daemon); + const { log } = recorder(); + const plain = defineAgent({ + id: "loupe-plain", + modules: ["context"], + instructions: { persona: "p" }, + }); + const played = daemon.turn("root-1", { text: "answer" }); + const outcome = await runAgent(client, { + agent: plain, + cwd: "/repo", + prompt: "q", + logger: log, + }); + await played; + client.close(); + expect(outcome.text).toBe("answer"); + expect( + daemon.current.requests.some((r) => r.method === "definitions.register"), + ).toBe(true); + expect( + daemon.current.requests.some((r) => r.method === "executor.bind"), + ).toBe(false); + }); + + it("runs several turns and sessions on one served agent, then stops serving", async () => { + const daemon = scriptedDaemon().serveSessions(); + const client = await connected(daemon); + const { log } = recorder(); + const served = await serveAgent( + client, + reviewerAgent({ name: "t", systemPrompt: "p", agentic: true, output }), + log, + ); + const first = daemon.turn("root-1", { text: "one", output: { ok: true } }); + const second = daemon.turn("root-1", { + text: "two", + output: { ok: false }, + }); + const other = daemon.turn("root-2", { + text: "other", + output: { ok: true }, + }); + const a = await served.session({ + cwd: "/repo", + maxTurns: 4, + cacheKey: "k", + }); + const b = await served.session({ cwd: "/repo" }); + const [r1, r2, r3] = await Promise.all([ + a.run("first"), + a.run("second").catch(() => undefined), + b.run("third"), + ]); + await Promise.all([first, other]); + // A second turn on the same session queues behind the first in the daemon; here it plays next. + const r2b = r2 ?? (await a.run("second")); + await second; + await Promise.all([a.close(), b.close()]); + served.close(); + client.close(); + expect(r1.output).toEqual({ ok: true }); + expect(r2b.output).toEqual({ ok: false }); + expect(r3.text).toBe("other"); + const sent = commands(daemon); + expect(sent.filter((c) => c.operation === "session.create")).toHaveLength( + 2, + ); + expect(sent.filter((c) => c.operation === "session.delete")).toHaveLength( + 2, + ); + expect( + daemon.current.requests.filter((r) => r.method === "executor.bind"), + ).toHaveLength(1); + }); + + it("surfaces a failed turn as an error and still deletes the session", async () => { + const daemon = scriptedDaemon().serveSessions(); + const client = await connected(daemon); + const { log } = recorder(); + const agent = reviewerAgent({ + name: "t", + systemPrompt: "p", + agentic: true, + output, + }); + const played = daemon.turn("root-1", { + status: "failed", + failure: { code: -32000, message: "model unavailable" }, + }); + await expect( + runAgent(client, { agent, cwd: "/repo", prompt: "review", logger: log }), + ).rejects.toThrow(/whip turn failed: model unavailable/); + await played; + client.close(); + expect(commands(daemon).at(-1)?.operation).toBe("session.delete"); + }); +}); diff --git a/packages/harness/tests/daemon.test.ts b/packages/harness/tests/daemon.test.ts new file mode 100644 index 0000000..0354f31 --- /dev/null +++ b/packages/harness/tests/daemon.test.ts @@ -0,0 +1,122 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +import { describe, expect, it } from "vitest"; + +import { + PINNED_WHIP_TAG, + homeEnvName, + materializeWhipHome, + pinnedWhipAsset, + pinnedWhipPath, + planDaemonEnv, + type WhipConfig, +} from "../src/daemon"; + +const cfg: WhipConfig = { + provider: { + name: "inference-net", + baseUrl: "https://api.inference.net/v1", + apiKeyEnv: "INFERENCE_API_KEY", + }, + models: ["kimi-k3"], +}; + +describe("homeEnvName", () => { + it("derives the home variable from the binary name like whip does", () => { + expect(homeEnvName("whip")).toBe("WHIP_HOME"); + expect(homeEnvName("/usr/local/bin/whipcode")).toBe("WHIPCODE_HOME"); + }); +}); + +describe("planDaemonEnv", () => { + it("uses the local daemon when there is no whip block", () => { + const plan = planDaemonEnv({ bin: "whip", env: { PATH: "/bin" } }); + expect(plan).toEqual({ env: { PATH: "/bin" }, throwaway: false }); + }); + + it("falls back to the local login, with a warning, when the key is absent", () => { + const plan = planDaemonEnv({ bin: "whip", whipConfig: cfg, env: {} }); + expect(plan.throwaway).toBe(false); + expect(plan.warning).toMatch(/INFERENCE_API_KEY is not set/); + expect(plan.env["WHIP_HOME"]).toBeUndefined(); + }); + + it("materializes a throwaway home for the binary when the key is present", () => { + const plan = planDaemonEnv({ + bin: "whipcode", + whipConfig: cfg, + env: { INFERENCE_API_KEY: "k" }, + }); + expect(plan.throwaway).toBe(true); + const home = plan.env["WHIPCODE_HOME"]; + expect(home).toBeTruthy(); + const written = JSON.parse( + readFileSync(join(home!, "config.json"), "utf8"), + ) as { defaultProvider: string }; + expect(written.defaultProvider).toBe("inference-net"); + }); +}); + +describe("pinned whipcode release", () => { + it("is the release the vendored SDK came from", () => { + expect(PINNED_WHIP_TAG).toBe("v1.0.0"); + }); + + it("names the v1 release assets per platform", () => { + expect(pinnedWhipAsset("linux", "x64")).toBe("whipcode-linux-x64"); + expect(pinnedWhipAsset("linux", "arm64")).toBe("whipcode-linux-arm64"); + expect(pinnedWhipAsset("darwin", "arm64")).toBe("whipcode-darwin-arm64"); + expect(pinnedWhipAsset("darwin", "x64")).toBe("whipcode-darwin-x64"); + }); + + it("caches the binary under its v1 name", () => { + expect(pinnedWhipPath()).toMatch(/\/\.loupe\/bin\/whipcode$/); + }); +}); + +describe("materializeWhipHome", () => { + const full: WhipConfig = { + ...cfg, + provider: { ...cfg.provider, label: "Inference.net" }, + models: ["kimi-k3", "glm-5.3-flash", "gpt-5.6-luna"], + defaultModel: "kimi-k3", + }; + const written = (c: WhipConfig): Record => { + const env = materializeWhipHome(c); + return JSON.parse( + readFileSync(join(env["WHIPCODE_HOME"]!, "config.json"), "utf8"), + ) as Record; + }; + + it("writes a WHIPCODE_HOME config with the provider and full model panel", () => { + const config = written(full); + expect(config["defaultModel"]).toBe("kimi-k3"); + expect(config["defaultProvider"]).toBe("inference-net"); + expect(config["providers"]).toEqual({ + "inference-net": { + name: "Inference.net", + baseUrl: "https://api.inference.net/v1", + api: "openai-completions", + apiKeyEnv: "INFERENCE_API_KEY", + }, + }); + expect(Object.keys(config["models"] as object)).toEqual([ + "kimi-k3", + "glm-5.3-flash", + "gpt-5.6-luna", + ]); + expect( + (config["models"] as Record)["gpt-5.6-luna"], + ).toEqual({ providers: ["inference-net"] }); + }); + + it("defaults defaultModel to the first model and label to the provider key", () => { + const config = written({ ...cfg, models: ["glm-5.3-flash", "kimi-k3"] }); + expect(config["defaultModel"]).toBe("glm-5.3-flash"); + expect( + (config["providers"] as Record)["inference-net"] + ?.name, + ).toBe("inference-net"); + }); +}); diff --git a/packages/harness/tests/hooks.test.ts b/packages/harness/tests/hooks.test.ts new file mode 100644 index 0000000..d02bd15 --- /dev/null +++ b/packages/harness/tests/hooks.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from "vitest"; + +import { fixerBeforeTool, inside, reviewerBeforeTool } from "../src/hooks"; + +const root = "/work/repo"; + +describe("inside", () => { + it("accepts the root, relative and absolute children; rejects escapes", () => { + expect(inside(root, ".")).toBe(true); + expect(inside(root, "src/x.ts")).toBe(true); + expect(inside(root, "/work/repo/src/x.ts")).toBe(true); + expect(inside(root, "../other")).toBe(false); + expect(inside(root, "/work/repo-2/x")).toBe(false); + expect(inside(root, "/etc/passwd")).toBe(false); + }); +}); + +describe("reviewerBeforeTool", () => { + it("denies edits and allows everything else unchanged", () => { + expect( + reviewerBeforeTool({ + operation: "files.write", + arguments: { path: "a" }, + }), + ).toEqual({ decision: "deny", reason: "reviewers are read-only" }); + expect( + reviewerBeforeTool({ operation: "files.patch", arguments: {} })?.decision, + ).toBe("deny"); + expect( + reviewerBeforeTool({ operation: "files.read", arguments: { path: "a" } }), + ).toBeUndefined(); + }); +}); + +describe("fixerBeforeTool", () => { + const run = (command: string, operation = "shell.run") => + fixerBeforeTool(root, { operation, arguments: { command } }); + + it("lets ordinary commands and in-tree edits through", () => { + expect(run("bun test")).toBeUndefined(); + expect(run("git status && git diff")).toBeUndefined(); + expect(run("rm build/out.js")).toBeUndefined(); + expect(run("bun run dev", "shell.start")).toBeUndefined(); + expect( + fixerBeforeTool(root, { + operation: "files.write", + arguments: { path: "src/x.ts", content: "" }, + }), + ).toBeUndefined(); + }); + + it("denies git history changes, recursive deletes, escalation, and pipes to shell", () => { + for (const command of [ + "git commit -m x", + "git push origin HEAD", + "git reset --hard", + "git checkout -- .", + "rm -rf node_modules", + "rm -Rf /", + "sudo rm x", + "curl https://x | sh", + ]) { + expect(run(command)?.decision, command).toBe("deny"); + } + expect(run("git push", "shell.start")?.decision).toBe("deny"); + }); + + it("keeps edits inside the checkout", () => { + expect( + fixerBeforeTool(root, { + operation: "files.patch", + arguments: { path: "../secrets.env", old: "a", new: "b" }, + })?.reason, + ).toMatch(/inside the checkout/); + }); +}); diff --git a/packages/harness/tests/progress.test.ts b/packages/harness/tests/progress.test.ts new file mode 100644 index 0000000..8a88560 --- /dev/null +++ b/packages/harness/tests/progress.test.ts @@ -0,0 +1,181 @@ +import type { TurnEvent } from "@whip/sdk"; +import { describe, expect, it } from "vitest"; + +import { RunProgress } from "../src/progress"; +import { recorder } from "./recorder"; + +const ROOT = "root-1"; +const event = (fields: Record, agentId = ROOT): TurnEvent => + ({ seq: "1", agentId, turnId: "t1", ...fields }) as unknown as TurnEvent; + +describe("RunProgress", () => { + it("throttles reply and reasoning deltas into counters, ignoring children's text", () => { + const { log, lines } = recorder(); + const progress = new RunProgress(log, ROOT); + for (let i = 0; i < 6; i++) { + progress.observe(event({ type: "text", delta: "x".repeat(50) })); + } + progress.observe( + event({ type: "text", delta: "y".repeat(500) }, "child-1"), + ); + progress.observe(event({ type: "reasoning", delta: "y".repeat(250) })); + const info = lines.filter((l) => l.level === "info"); + expect(info.map((l) => l.message)).toEqual([ + "streaming reply", + "streaming reply", + "thinking", + ]); + expect(info[0]?.props).toEqual({ chars: 150 }); + expect(info[1]?.props).toEqual({ chars: 300 }); + expect(info[2]?.props).toEqual({ chars: 250 }); + }); + + it("logs cells, host calls, hooks, and failures one line each and clips arguments", () => { + const { log, lines } = recorder(); + const progress = new RunProgress(log, ROOT); + progress.observe( + event({ + type: "cell", + id: "c1", + status: "running", + code: "a".repeat(300), + }), + ); + progress.observe( + event({ + type: "host", + id: "h1", + invocationId: "i", + operation: "files.read", + summary: "path=x", + status: "running", + }), + ); + progress.observe( + event({ + type: "host", + id: "h1", + invocationId: "i", + operation: "files.read", + summary: "path=x", + status: "failed", + error: "denied", + }), + ); + progress.observe( + event({ + type: "hook", + hook: "before_tool", + operation: "shell.run", + decision: "deny", + reason: "reviewers are read-only", + }), + ); + progress.observe(event({ type: "end", status: "failed", error: "boom" })); + expect(lines.map((l) => [l.level, l.message])).toEqual([ + ["info", "cell"], + ["info", "host call"], + ["warn", "host call failed"], + ["info", "hook decision"], + ["warn", "turn failed"], + ]); + expect(String(lines[0]?.props?.["code"]).length).toBe(201); + expect(lines[3]?.props).toEqual({ + hook: "before_tool", + decision: "deny", + operation: "shell.run", + reason: "reviewers are read-only", + }); + }); + + it("reports children by id: spawn, their host calls, and their failures", () => { + const { log, lines } = recorder(); + const progress = new RunProgress(log, ROOT); + progress.observe( + event( + { + type: "child", + childId: "c1", + kind: "agent.admitted", + status: "running", + }, + "c1", + ), + ); + progress.observe( + event( + { + type: "host", + id: "h", + invocationId: "i", + operation: "tools.pr_hunks", + summary: "", + status: "running", + }, + "c1", + ), + ); + progress.observe( + event({ type: "end", status: "failed", error: "budget exhausted" }, "c1"), + ); + expect(lines.map((l) => [l.level, l.message, l.props?.["agent"]])).toEqual([ + ["info", "child agent spawned", "c1"], + ["info", "host call", "c1"], + ["warn", "child turn failed", "c1"], + ]); + expect(lines[2]?.props?.["error"]).toBe("budget exhausted"); + }); + + it("reports cost from accounting events and tokens from the result's usage", () => { + const { log, lines } = recorder(); + const progress = new RunProgress(log, ROOT); + const accounting = (reported: string, estimated: string) => + event({ + type: "raw", + event: { + kind: "stream.accounting", + payload: { + accounting: { + reported_cost_micros: reported, + estimated_cost_micros: estimated, + }, + }, + }, + }); + progress.observe(accounting("1500", "9")); + progress.observe(accounting("0", "4200")); + progress.finish({ + used: 1, + size: 2, + usage: { prompt_tokens: 120, completion_tokens: 30 }, + }); + expect(lines.at(-1)).toEqual({ + level: "info", + message: "run finished", + props: { + replyChars: 0, + promptTokens: 120, + completionTokens: 30, + costUsd: 0.0042, + }, + }); + }); + + it("sums per-call usage cost when no accounting event reaches the turn", () => { + const { log, lines } = recorder(); + const progress = new RunProgress(log, ROOT); + const usage = (cost: number) => + event({ + type: "usage", + usage: { + used: 1, + size: 1, + usage: { prompt_tokens: 1, completion_tokens: 1, cost }, + }, + }); + progress.observe(usage(0.01)); + progress.observe(usage(0.02)); + progress.finish(); + expect(lines.at(-1)?.props?.["costUsd"]).toBeCloseTo(0.03); + }); +}); diff --git a/packages/harness/tests/recorder.ts b/packages/harness/tests/recorder.ts new file mode 100644 index 0000000..d8b2914 --- /dev/null +++ b/packages/harness/tests/recorder.ts @@ -0,0 +1,20 @@ +import type { Logger } from "@loupe/logger"; + +export type Line = { + level: string; + message: string; + props?: Record; +}; + +/** A logger that records its lines for assertions. */ +export function recorder(): { log: Logger; lines: Line[] } { + const lines: Line[] = []; + const log: Logger = { + debug: (message, props) => lines.push({ level: "debug", message, props }), + info: (message, props) => lines.push({ level: "info", message, props }), + warn: (message, props) => lines.push({ level: "warn", message, props }), + error: (message, props) => lines.push({ level: "error", message, props }), + child: () => log, + }; + return { log, lines }; +} diff --git a/packages/harness/tests/trace.test.ts b/packages/harness/tests/trace.test.ts index 8ddc425..9497186 100644 --- a/packages/harness/tests/trace.test.ts +++ b/packages/harness/tests/trace.test.ts @@ -1,103 +1,91 @@ import { describe, expect, it } from "vitest"; +import type { TurnEvent } from "@whip/sdk"; + import { envSecretValues, redactSecrets, REDACTION_MARK, - whipEventToTrace, - type HarnessTraceEvent, + turnEventToTrace, } from "../src/index"; -describe("whipEventToTrace (nondestructive normalization)", () => { +const event = (fields: Record): TurnEvent => + ({ + seq: "1", + agentId: "root", + turnId: "t1", + ...fields, + }) as unknown as TurnEvent; + +describe("turnEventToTrace (nondestructive normalization)", () => { it("translates reasoning and text deltas", () => { - expect(whipEventToTrace({ type: "reasoning", delta: "think " })).toEqual([ - { type: "reasoning", delta: "think " }, - ]); - expect(whipEventToTrace({ type: "text", delta: "hi" })).toEqual([ + expect( + turnEventToTrace(event({ type: "reasoning", delta: "think " })), + ).toEqual([{ type: "reasoning", delta: "think " }]); + expect(turnEventToTrace(event({ type: "text", delta: "hi" }))).toEqual([ { type: "text", delta: "hi" }, ]); }); - it("normalizes tool_start/tool_end with truncated blobs", () => { + it("maps a host call to tool_start/tool_end with truncated blobs", () => { const long = "x".repeat(5000); - const start = whipEventToTrace({ - type: "tool_start", - name: "read", - args: long, - }); - expect(start).toEqual([ - { type: "tool_start", name: "read", args: long.slice(0, 2000) }, - ]); - const end = whipEventToTrace({ - type: "tool_end", - name: "read", - result: long, - }); - expect(end).toEqual([ - { type: "tool_end", name: "read", result: long.slice(0, 2000) }, - ]); - }); - - it("serializes structured tool results", () => { expect( - whipEventToTrace({ - type: "tool_end", - name: "read", - result: { lines: ["one", "two"], truncated: false }, - }), - ).toEqual([ - { - type: "tool_end", - name: "read", - result: '{"lines":["one","two"],"truncated":false}', - }, - ]); - }); - - it("handles circular tool payloads without throwing", () => { - const circular: Record = {}; - circular["self"] = circular; - expect( - whipEventToTrace({ type: "tool_start", name: "custom", args: circular }), + turnEventToTrace( + event({ + type: "host", + id: "h1", + invocationId: "i", + operation: "files.read", + summary: long, + status: "running", + }), + ), ).toEqual([ { type: "tool_start", - name: "custom", - args: "[unserializable tool payload]", + name: "files.read", + args: `${long.slice(0, 2000)}…`, }, ]); - }); - - it("defaults a missing tool name and serializes structured args", () => { - expect(whipEventToTrace({ type: "tool_start" })).toEqual([ - { type: "tool_start", name: "tool", args: undefined }, - ]); expect( - whipEventToTrace({ type: "tool_start", name: "grep", args: { x: 1 } }), - ).toEqual([{ type: "tool_start", name: "grep", args: '{"x":1}' }]); - }); - - it("ignores non-object raw events", () => { - expect(whipEventToTrace(null)).toEqual([]); - expect(whipEventToTrace("reasoning")).toEqual([]); + turnEventToTrace( + event({ + type: "host", + id: "h1", + invocationId: "i", + operation: "files.read", + summary: "", + status: "failed", + error: "denied", + }), + ), + ).toEqual([{ type: "tool_end", name: "files.read", result: "denied" }]); }); - it("normalizes done and error", () => { - expect(whipEventToTrace({ type: "done", text: "{}" })).toEqual([ - { type: "done", text: "{}" }, - ]); - const err = whipEventToTrace({ - type: "error", - error: { message: "boom" }, - })[0] as HarnessTraceEvent & { type: "error" }; - expect(err.type).toBe("error"); - expect(err.error).toContain("boom"); + it("maps a cell's code and result to a tool", () => { + expect( + turnEventToTrace( + event({ type: "cell", id: "c1", status: "running", code: "x = 1" }), + ), + ).toEqual([{ type: "tool_start", name: "cell", args: "x = 1" }]); + expect( + turnEventToTrace( + event({ type: "cell", id: "c1", status: "completed", result: "1" }), + ), + ).toEqual([{ type: "tool_end", name: "cell", result: "1" }]); + expect( + turnEventToTrace(event({ type: "cell", id: "c1", status: "called" })), + ).toEqual([]); }); - it("ignores unknown event types and malformed payloads", () => { - expect(whipEventToTrace({ type: "ping" })).toEqual([]); - expect(whipEventToTrace({ type: "reasoning" })).toEqual([]); - expect(whipEventToTrace({ type: "text", delta: 42 })).toEqual([]); + it("ignores events the trace has no shape for", () => { + expect(turnEventToTrace(event({ type: "notice", text: "n" }))).toEqual([]); + expect( + turnEventToTrace(event({ type: "end", status: "succeeded" })), + ).toEqual([]); + expect( + turnEventToTrace(event({ type: "raw", event: { kind: "x" } })), + ).toEqual([]); }); }); diff --git a/packages/harness/tests/whip-binary.test.ts b/packages/harness/tests/whip-binary.test.ts deleted file mode 100644 index 13b82b0..0000000 --- a/packages/harness/tests/whip-binary.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { - PINNED_WHIP_TAG, - pinnedWhipAsset, - pinnedWhipPath, -} from "../src/whip-binary"; - -describe("pinned whipcode release", () => { - it("is the release the vendored SDK came from", () => { - expect(PINNED_WHIP_TAG).toBe("v1.0.0"); - }); - - it("names the v1 release assets per platform", () => { - expect(pinnedWhipAsset("linux", "x64")).toBe("whipcode-linux-x64"); - expect(pinnedWhipAsset("linux", "arm64")).toBe("whipcode-linux-arm64"); - expect(pinnedWhipAsset("darwin", "arm64")).toBe("whipcode-darwin-arm64"); - expect(pinnedWhipAsset("darwin", "x64")).toBe("whipcode-darwin-x64"); - }); - - it("caches the binary under its v1 name", () => { - expect(pinnedWhipPath()).toMatch(/\/\.loupe\/bin\/whipcode$/); - }); -}); diff --git a/packages/harness/tests/whip-config.test.ts b/packages/harness/tests/whip-config.test.ts deleted file mode 100644 index 7af0989..0000000 --- a/packages/harness/tests/whip-config.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { readFileSync } from "node:fs"; -import { join } from "node:path"; - -import { describe, expect, it } from "vitest"; - -import { - buildClaudeArgs, - buildCodexArgs, - materializeWhipHome, - shouldRetryWithoutCacheKey, - type WhipConfig, -} from "../src/index"; - -const base: WhipConfig = { - provider: { - name: "inference-net", - baseUrl: "https://api.inference.net/v1", - apiKeyEnv: "INFERENCE_API_KEY", - label: "Inference.net", - }, - models: ["kimi-k3", "glm-5.3-flash", "gpt-5.6-luna"], - defaultModel: "kimi-k3", -}; - -function writtenConfig( - cfg: WhipConfig, - reasoning?: "low" | "medium" | "high", -): { - env: Record; - config: Record; -} { - const env = materializeWhipHome(cfg, reasoning); - const config = JSON.parse( - readFileSync(join(env["WHIP_HOME"]!, "config.json"), "utf8"), - ) as Record; - return { env, config }; -} - -describe("materializeWhipHome", () => { - it("writes a WHIP_HOME config with the provider and full model panel", () => { - const { env, config } = writtenConfig(base); - expect(env["WHIP_HOME"]).toBeTruthy(); - expect(config["defaultModel"]).toBe("kimi-k3"); - expect(config["defaultProvider"]).toBe("inference-net"); - expect(config["providers"]).toEqual({ - "inference-net": { - name: "Inference.net", - baseUrl: "https://api.inference.net/v1", - api: "openai-completions", - apiKeyEnv: "INFERENCE_API_KEY", - }, - }); - expect(Object.keys(config["models"] as object)).toEqual([ - "kimi-k3", - "glm-5.3-flash", - "gpt-5.6-luna", - ]); - expect( - (config["models"] as Record)["gpt-5.6-luna"], - ).toEqual({ providers: ["inference-net"] }); - }); - - it("defaults defaultModel to the first model and label to the provider key", () => { - const { config } = writtenConfig({ - provider: { - name: "inference-net", - baseUrl: "https://api.inference.net/v1", - apiKeyEnv: "INFERENCE_API_KEY", - }, - models: ["glm-5.3-flash", "kimi-k3"], - }); - expect(config["defaultModel"]).toBe("glm-5.3-flash"); - expect( - (config["providers"] as Record)["inference-net"] - ?.name, - ).toBe("inference-net"); - }); - - it("pins defaultEffort only when a reasoning effort is given", () => { - expect(writtenConfig(base).config).not.toHaveProperty("defaultEffort"); - expect(writtenConfig(base, "medium").config["defaultEffort"]).toBe( - "medium", - ); - }); -}); - -describe("native reasoning flags", () => { - it("claude gets --effort, codex gets model_reasoning_effort, neither when unset", () => { - expect(buildClaudeArgs({ systemPrompt: "s", reasoning: "high" })).toContain( - "--effort", - ); - expect(buildClaudeArgs({ systemPrompt: "s" })).not.toContain("--effort"); - expect(buildCodexArgs({ reasoning: "low" })).toEqual([ - "exec", - "-c", - "model_reasoning_effort=low", - "-", - ]); - expect(buildCodexArgs({})).toEqual(["exec", "-"]); - }); -}); - -describe("shouldRetryWithoutCacheKey", () => { - it("retries when the provider rejected prompt_cache_key as unrecognized", () => { - // The shape from issue #37: a 400 wrapped in whip's "whip error:" prefix. - const err = `whip error: "400 Bad Request: {"error":{"message":"Unrecognized request argument supplied: prompt_cache_key"}}"`; - expect(shouldRetryWithoutCacheKey(err, "loupe/acme/app/code")).toBe(true); - }); - - it("retries when an older whip predates the -cache-key flag", () => { - expect( - shouldRetryWithoutCacheKey( - "flag provided but not defined: -cache-key", - "loupe/acme/app/code", - ), - ).toBe(true); - }); - - it("matches prompt_cache_key case-insensitively", () => { - expect(shouldRetryWithoutCacheKey("PROMPT_CACHE_KEY bad", "k")).toBe(true); - }); - - it("does not retry an unrelated failure (a plain crash or a quota 400)", () => { - expect( - shouldRetryWithoutCacheKey("whip exited 1: boom", "loupe/acme/app/code"), - ).toBe(false); - }); - - it("never retries when no cache key was sent (promptCache:false opt-out)", () => { - // This is what guarantees the self-heal never fires for an opted-out - // reviewer and never double-retries. - expect( - shouldRetryWithoutCacheKey("prompt_cache_key rejected", undefined), - ).toBe(false); - }); -}); From abefcd7055d83ea5314da2cf7f0c6c7f96339e3a Mon Sep 17 00:00:00 2001 From: Sam Heutmaker Date: Sat, 26 Sep 2026 17:21:40 -0700 Subject: [PATCH 3/6] Document the whip-only loupe README, architecture, configuration, the Action and CLI guides, credentials, review traces, the how-it-works pages, and the example workflow describe one harness: the pinned whipcode install, the daemon loupe starts or attaches to, the SDK path, the output contract the daemon enforces, and reasoning set on the session. Claude and codex, the subprocess, `--harness`, and the panel paragraph are gone; main's diagrams and the sections on ensemble, dedup, the comment cap, the publish gate, and status routing stay as they were. Co-Authored-By: Claude Fable 5.1 --- README.md | 108 +++++++++++++++-------------- docs/README.md | 6 +- docs/architecture.md | 116 +++++++++++++++++++++++--------- docs/configuration.md | 23 ++++--- docs/credentials.md | 48 ++++++------- docs/github-action.md | 39 +++++++---- docs/guide.md | 19 +++--- docs/how-it-works/README.md | 2 +- docs/how-it-works/context.md | 10 +-- docs/how-it-works/review-run.md | 8 +-- docs/how-it-works/triggers.md | 2 +- docs/review-traces.md | 32 ++++----- examples/.loupe.json | 1 - examples/review.example.yml | 47 ++++++------- 14 files changed, 268 insertions(+), 193 deletions(-) diff --git a/README.md b/README.md index fb64488..e3d1d88 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # loupe -A harness- and model-agnostic AI pull-request reviewer. Define as many focused -reviewers as you want, run them from your terminal or as a GitHub Action, and -get **real GitHub reviews with inline, line-anchored comments** — not just a -wall-of-text PR comment. +An AI pull-request reviewer built on [whip](https://github.com/context-labs/whip). +Define as many focused reviewers as you want, run them from your terminal or as +a GitHub Action, and get **real GitHub reviews with inline, line-anchored +comments** — not just a wall-of-text PR comment. **Docs:** [`docs/`](docs/README.md) — user guide, configuration, credentials, GitHub Action, and the maintainer architecture reference. @@ -17,33 +17,35 @@ GitHub Action, and the maintainer architecture reference. source, a migration-risk reviewer reads the SQL, a security reviewer reads the auth code. Each posts its own labeled review, and a reviewer whose globs match nothing stays quiet. -- **Any harness, any model — your choice, not the vendor's.** loupe drives the - agent CLI you already use — [whip](https://github.com/context-labs/whip), - Claude, Codex — as a subprocess, and any model those can reach. Pick the - harness, model, and effort _per reviewer_. Hosted reviewers like Bugbot lock - you to their backend and their model; loupe doesn't — point it at a frontier - closed model, a fast open one, or a whole panel of them. +- **Any model, your choice.** Each reviewer is a whip agent, so it runs on any + model whip can reach — a frontier closed model, a fast open one, or a panel + of them for an ensemble. Pick the model and effort _per reviewer_. Hosted + reviewers like Bugbot lock you to their backend and their model; loupe + doesn't. - **Local and CI are the same engine.** The exact review that runs in the GitHub Action runs from your terminal with one command — `--dry-run` to preview without posting. No separate local path to drift, no "works in CI only". - **It reads the repo's own rules.** loupe enforces each repo's `CLAUDE.md` / `AGENTS.md` at review time — no vendored rulebook to copy around and keep in sync. -- **A panel of models for the hard calls.** Agentic reviewers can fan out - subagents across a diverse set of models to independently double- or - triple-confirm a suspected bug before flagging it — fewer false positives, and - the confident ones land as inline comments. +- **The runtime enforces the review contract.** Each reviewer is a whip agent + definition with the review schema as its output contract: the daemon + validates the reviewer's final message against it and corrects it once. + Reviewers hold read-only authority, and an agentic verify pass reads the + checkout to refute findings the surrounding code does not support. ## How it works ``` pull_request event (or `loupe review` locally, or an @loupe comment) - └─ @loupe/action reads config from env/flags, resolves harness credentials - ├─ @loupe/core fetch PR + conventions → build prompt → run harness → + └─ @loupe/action reads config from env/flags, brings up whip + ├─ @loupe/harness starts or attaches to the whip daemon (Unix socket), + │ builds each reviewer as an agent definition with an + │ output contract, runs one turn per review via the SDK + ├─ @loupe/core fetch PR + conventions → build prompt → run the turn → │ parse + validate findings against the diff → │ POST /pulls/{n}/reviews (inline comments, empty body) │ POST/PATCH /issues/{n}/comments (persistent summary) - ├─ @loupe/harness the agent CLI as a subprocess (whip, claude, codex, …) └─ @loupe/credentials provider chain: env → dotenv → infisical → your own ``` @@ -57,18 +59,19 @@ diagram — not a restatement of the diff. ```bash bun install -# whip self-authenticates from its own local login — no keys to wire: -bun run packages/action/src/cli.ts review owner/repo#123 --harness whip - -# or bring a key for a hosted harness: -ANTHROPIC_API_KEY=sk-... \ - bun run packages/action/src/cli.ts review owner/repo#123 --harness claude +# the whip daemon self-authenticates from its own local login — no keys to wire: +bun run packages/action/src/cli.ts review owner/repo#123 --dry-run ``` -Token comes from `--token`, else `GITHUB_TOKEN`, else `gh auth token`. +loupe attaches to the running whip daemon over its Unix socket, starting one if +needed. Token comes from `--token`, else `GITHUB_TOKEN`, else `gh auth token`. +The binary is `whipcode` on `PATH`; `LOUPE_WHIP_BIN` names another, and when +neither exists loupe downloads the pinned release it was built against. With a +`--config` whose `whip` block names a provider, models are routed through that +provider, so it must be configured in your daemon. -Key flags (defaults in parens): `--harness` (whip), `--model` (kimi-k3), -`--reasoning low|medium|high` (harness default; passed natively), `--profile quiet|chill|assertive` (chill), +Key flags (defaults in parens): `--model` (kimi-k3), +`--reasoning low|medium|high` (whip's default; set on the session), `--profile quiet|chill|assertive` (chill), `--config ` (focused reviewers), `--reviewer ` (run just one), `--prompt-file ` (custom guidance), `--dir` (subdir scope), `--ensemble` (multi-model majority), `--timezone`, `--max-turns` (agentic loop cap), @@ -76,8 +79,8 @@ Key flags (defaults in parens): `--harness` (whip), `--model` (kimi-k3), `--no-verify`, `--no-agentic`, `--providers env,dotenv,infisical`, `--dry-run`. -Use `--dry-run` (with `LOG_LEVEL=debug` to see raw harness output) to compute and -log a review without posting — the safe way to test. +Use `--dry-run` (with `LOG_LEVEL=debug` to see every cell and tool call) to +compute and log a review without posting — the safe way to test. ## Focused reviewers (`.loupe.json`) @@ -114,22 +117,24 @@ Per-reviewer keys: `prompt`/`promptFile`, `include`/`exclude`, `model`, ### Top-level config — keep review policy out of the workflow The config file also carries the **review defaults** that used to be repeated in -every workflow file: `harness`, `model`, `reasoning`, `profile`, `timezone`, +every workflow file: `model`, `reasoning`, `profile`, `timezone`, `dir`, and `maxTurns` (the agentic tool-loop cap; also per-reviewer), plus `maxComments` (the inline comment cap; also per-reviewer). Precedence is **Action input / CLI flag → `.loupe.json` → built-in default**, so a workflow can still override, but by default the policy lives with the repo. -It can also declare the **whip provider + model panel** under `whip`, so the -workflow no longer hand-writes `~/.whip/config.json` in a CI step. loupe -materializes it into a throwaway `WHIP_HOME` at review time (never touching a -developer's real `~/.whip`); only the API key _value_ stays in the workflow — -its env-var name is in the config. +It also declares the **whip provider + model panel** under `whip`. In CI, when +the provider's API key is in the environment, loupe writes that block into a +throwaway `WHIPCODE_HOME`, starts a dedicated daemon there, and stops it +afterwards — never touching a developer's real `~/.whipcode`. Locally, without +the key, loupe uses your own whip login instead. Only the API key _value_ stays +in the workflow; its env-var name is in the config. Every session names the +block's provider, so the models resolve even on a daemon whose default provider +is another. ```json { - "harness": "whip", "model": "kimi-k3", "timezone": "PST", "dir": "inference", @@ -145,9 +150,9 @@ its env-var name is in the config. } ``` -With that, the whole workflow is just: checkout → install the harness binary → +With that, the whole workflow is just: checkout → install whipcode → `context-labs/loupe@v0` with `config: .loupe.json` and the secret in `env`. See -Variant D in `examples/review.example.yml`. +Variant C in `examples/review.example.yml`. ```bash loupe review owner/repo#123 --config .loupe.json @@ -164,9 +169,10 @@ The system prompt is layered. `--prompt-file` (CLI) / `prompt-file` input (persona + priorities). loupe always appends the review procedure (check callers first; `procedure: false` removes it), the profile directive, tool-access directive, repo conventions, and the JSON output contract, plus a -reasoning note when `reasoning` is set — so a custom prompt can't break parsing -or trigger tool loops. Write -only persona/priorities; never the JSON schema. See `examples/loupe-prompt.md`. +reasoning note when `reasoning` is set — and the whip daemon validates the +final message against that contract — so a custom prompt can't break parsing +or widen what the agent may do. Write only persona/priorities; never the JSON +schema. See `examples/loupe-prompt.md`. ## GitHub integration @@ -179,7 +185,7 @@ into a consuming repo, or register it once at the org level. A second workflow o comment events gives you **`@loupe` chat**: `@loupe review` (re-review), `@loupe fix` (fix every open Loupe finding in one commit), `@loupe fix ` (make a specific edit and push it), `@loupe ` (Q&A grounded in the diff), and `@loupe help`. Inputs mirror -the CLI flags: `harness`, `model`, `reasoning`, `profile`, `config`, `reviewer`, +the CLI flags: `model`, `reasoning`, `profile`, `config`, `reviewer`, `prompt-file`, `dir`, `skills`, `ensemble`, `timezone`, `verify`, `full`, `convention-paths`, `credential-providers`, `github-token`. @@ -187,21 +193,21 @@ the CLI flags: `harness`, `model`, `reasoning`, `profile`, `config`, `reviewer`, For a monorepo, restrict the review to one folder — only changed files under it are reviewed, conventions are read from it (`inference/AGENTS.md`), and the -harness runs there: +agent works there: ```bash -loupe review context-labs/monorepo#6046 --harness whip --dir inference +loupe review context-labs/monorepo#6046 --dir inference ``` In the Action, set the `dir` input (or `LOUPE_DIR`). ## Credentials -Harnesses that self-authenticate need no provider at all: `whip` reviews using -its own local login (`~/.whip/`), so `loupe review --harness whip` just works -once `whip auth inference-net login` has been run. - -Otherwise `LOUPE_CREDENTIAL_PROVIDERS` is an ordered chain; first hit wins: +The whip daemon holds the model credentials. Locally that is your whip login +(`whipcode auth inference-net`), so `loupe review` just works. In CI the `whip` +block's `apiKeyEnv` names the variable the daemon reads; loupe resolves it +through `LOUPE_CREDENTIAL_PROVIDERS`, an ordered chain where the first hit +wins: - `env` — `process.env` (default; works with plain GitHub secrets) - `dotenv` — a `.env` file @@ -224,5 +230,7 @@ task check # format + lint + tsc + test ``` Bun workspace monorepo: `@loupe/credentials`, `@loupe/harness`, `@loupe/logger`, -`@loupe/core`, `@loupe/action`. Tooling mirrors the inference monorepo -(oxlint / oxfmt / typescript-7 / Taskfile). +`@loupe/core`, `@loupe/action`, plus the vendored `@whip/protocol` and +`@whip/sdk` under `vendor/` (refresh with `task vendor:whip WHIP=`; +the release and commit are in `vendor/@whip/VERSION`). Tooling mirrors the +inference monorepo (oxlint / oxfmt / typescript-7 / Taskfile). diff --git a/docs/README.md b/docs/README.md index 075c96f..92478e3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,7 @@ # loupe docs AI pull-request reviewer: inline, line-anchored comments with a -COMMENT / REQUEST_CHANGES verdict. Harness-agnostic, reviews against each repo's +COMMENT / REQUEST_CHANGES verdict. Built on whip, reviews against each repo's own conventions, runs its focused reviewers agentically by default. ## How it works @@ -13,7 +13,7 @@ Start at [how-it-works/README.md](how-it-works/README.md): triggers, the review - [Guide](guide.md) — install, run a review locally, CLI flags, dry-run. - [Configuration](configuration.md) — `.loupe.json` reviewer profiles, model, reasoning, agentic mode, custom prompts, conventions. -- [Credentials](credentials.md) — the provider chain and per-harness auth. +- [Credentials](credentials.md) — the provider chain, local vs CI auth. - [GitHub Action](github-action.md) — wire loupe into CI, inputs, secrets. - [Review traces](review-traces.md) — reasoning/tool transcripts in the Actions step summary, and how to verify them locally. @@ -23,4 +23,4 @@ Start at [how-it-works/README.md](how-it-works/README.md): triggers, the review ## Maintainer docs - [Architecture](architecture.md) — packages, the review pipeline, key files, - how to extend (new harness, new credential provider). + how to extend (new agent, new credential provider). diff --git a/docs/architecture.md b/docs/architecture.md index 2c94a16..74c320f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -8,15 +8,16 @@ Bun monorepo, `workspace:*` packages. Tooling: oxlint (type-aware), oxfmt, ``` packages/ ├─ credentials/ provider chain (env → dotenv → infisical → custom) -├─ harness/ the agent CLI abstraction + adapters (whip, claude, codex) +├─ harness/ the whip daemon, its SDK client, and the agent definitions ├─ logger/ winston structured logging + opt-in OTLP export ├─ core/ the review engine (no CLI/Action concerns) └─ action/ CLI + GitHub Action entry, config, reviewer-profile loading +vendor/@whip/ prebuilt @whip/protocol and @whip/sdk (see VERSION) ``` Dependency direction: `action` → `core` → (`harness`, `logger`); `harness` → -`logger`; `credentials` and `logger` are leaves. `core` knows nothing about the -CLI, env vars, or `.loupe.json` — those live in `action`. +`logger`, `@whip/sdk`; `credentials` and `logger` are leaves. `core` knows +nothing about the CLI, env vars, or `.loupe.json` — those live in `action`. ## The review pipeline @@ -32,9 +33,15 @@ CLI, env vars, or `.loupe.json` — those live in `action`. guidance + reasoning note + tool directive (headless vs agentic) + output contract; `buildUserPrompt` is PR metadata + conventions + rendered diff. 5. **Run** — pick `harnessCwd` (subdir if it exists on disk, else workdir, else - cwd; warn in agentic mode if absent). Call `harness.review(ctx)`. -6. **Parse** — `parseReviewOutput` extracts the last JSON object and validates - each finding independently (one malformed finding is dropped, not fatal). + cwd; warn in agentic mode if absent). Call `harness.review(ctx)` with + `agent: "reviewer"` and the review schema as `output`: the harness builds + the agent definition, serves it, and runs one turn on a session pinned to + it. The daemon validates the final message against the schema and corrects + it once. +6. **Parse** — `outcomeText` takes the turn's validated `output` (or its final + text when the turn cap answered past the check); `parseReviewOutput` + extracts the last JSON object and validates each finding independently + (one malformed finding is dropped, not fatal). 7. **Validate** — `validateFindings` splits findings into `inline` (their `path:line` is in the diff) and `dropped` (off-diff → summary notes). This is what prevents a hallucinated line from 422-ing the whole review. @@ -55,37 +62,77 @@ CLI, env vars, or `.loupe.json` — those live in `action`. the loose review-output schema. - `diff.ts` — `commentableLines` parses hunk headers to the RIGHT-side line set GitHub will accept comments on; `renderDiff` formats the diff for the prompt. -- `prompt.ts` — default guidance, the three system-prompt layers, user prompt. -- `parse.ts` — brace-depth JSON extraction + per-finding `safeParse`. +- `prompt.ts` — default guidance, the system-prompt layers, user prompt, the + verifier / chat / fix prompts. +- `output.ts` — the zod output contracts: the review and the verifier's + verdicts. The daemon derives JSON Schema from them and shows the model the + shape in its runtime guide. +- `parse.ts` — `outcomeText`, brace-depth JSON extraction + per-finding + `safeParse`. - `validate.ts` — inline vs dropped split. - `github.ts` — Octokit factory, PR/convention fetch, `postReview` + comment de-dup markers. -## Harness abstraction +## The harness seam `packages/harness/src/index.ts`: -- `Harness` = `{ name, credentialKeys, available(), review(ctx) }`. -- `HarnessContext` = `{ systemPrompt, userPrompt, model?, agentic?, workdir, env, - logger }`. -- `review` runs the CLI to completion and returns its raw stdout (the review - JSON); the core parses it. Adapters stay dumb. -- `runCli` (generic) pipes the prompt on stdin — used by `claude` - (`--append-system-prompt`, `--model`) and `codex` (system prepended to stdin). -- `runWhipStreaming` runs `whip run --format json` and consumes the NDJSON event - stream live: `reasoning`/`text` deltas become throttled `thinking` / - `streaming reply` logs, `tool_start`/`tool_end` become `tool call` logs; it - reassembles the assistant text from the stream. whip uses `-max-turns 40` - agentic / `10` one-shot as a safety cap, `-system`, and `-m `. -- `registry()` maps name → harness; `getHarness(name)` throws with the known set. +- `Harness` = `{ name, review(ctx) → RunOutcome }`. `review` runs one turn to + completion and returns `{ text, output, usage }`: the final message and, + under an output contract, the value the daemon validated. Parsing findings + out of it is the core's job. +- `HarnessContext` = `{ agent, output?, name?, systemPrompt, userPrompt, + model?, provider?, agentic?, workdir, maxTurns?, reasoning?, cacheKey?, + trace?, phase?, logger }`. `agent` picks the loupe agent (`reviewer`, + `verifier`, `chat`, `fixer`); `agentFor(ctx)` builds its definition. +- `whipHarness(client, secrets)` is the one implementation: `runAgent` serves + the definition from this process, opens a session (`cwd`, model, provider, + `permission_mode: automatic`; `headless`, `max_turns`, `cache_key`; + `session.effort` when `reasoning` is set), runs `session.run(prompt)`, and + deletes the session. Turn events feed `RunProgress` (the live log) and, + through `turnEventToTrace`, the trace sink. A failed turn or SDK error is + rethrown as a `HarnessError` classified by `classifyHarnessError`. + +The rest of the package: + +- `daemon.ts` — `resolveWhipBinary` (`LOUPE_WHIP_BIN`, then `whipcode` on + PATH, else the pinned `PINNED_WHIP_TAG` release downloaded once, single- + flighted); `ensureDaemon`: `whipcode daemon status --json`, start if needed, + return the Unix socket. With a `whip` config block whose `apiKeyEnv` is set, + `materializeWhipHome` writes a throwaway home and a dedicated daemon runs + there until `stop()`; otherwise the local daemon is used and left running. + The home variable follows the binary name (`WHIPCODE_HOME`). +- `client.ts` — `connect` (SDK client over `unixSocket`), `serveAgent` + (`client.agents.serve`, or only `register` for a definition with no hooks; + `runtime.sessions.create` pins each session to the served revision), and + `runAgent`, the one-turn convenience. +- `agents.ts` — `reviewerAgent` (`context` + `files` when agentic, read-only + capability, the review schema as `output`), `verifierAgent` (the same shape, + the verdicts schema), `chatAgent` (diff only, prose), `fixerAgent` (`files` + + `shell` with write and shell capabilities, hook-guarded). Ids carry a hash + of the canonical document (`loupe--<8 hex>`): a new session takes an + id's most recently created revision, so two variants sharing an id would + hand a session the wrong one; the hash keeps that from arising. +- `hooks.ts` — pure decision functions: `reviewerBeforeTool` (deny edits, so + an attempt shows up as a hook decision), `fixerBeforeTool` (no git history + commands, recursive deletes, sudo, or pipes to a shell; edits stay inside + the checkout). +- `progress.ts` — `RunProgress` maps turn events to the run log (throttled + `thinking` / `streaming reply`, one line per cell and host call, hook + decisions, a `run finished` line with tokens and cost); `turnEventToTrace` + maps them to `HarnessTraceEvent`s for the step summary. +- `errors.ts`, `trace.ts` — `HarnessError` and its kinds; the trace event + union and secret redaction. ## action files - `config.ts` — the only place that reads `process.env`; parses with Zod and resolves `LOUPE_CONFIG`/`LOUPE_PROMPT_FILE` against `GITHUB_WORKSPACE`. -- `run.ts` — `reviewPullRequest`: resolve harness, best-effort forward creds - (never hard-fail on a missing key), call `runReview`. Plus `formatResult` / - `renderReview`. +- `run.ts` — `resolveHarness`: one daemon and one client per process, shared + by every reviewer (resolve the provider key through the credential chain, + `ensureDaemon`, `connect`, `whipHarness`); `releaseWhip` closes the client + and stops a daemon loupe started. `reviewPullRequest` calls `runReview` on + it. Plus `formatResult` / `renderReview`. - `reviewers.ts` — loads/validates `.loupe.json` into resolved `Reviewer[]` (reads `promptFile` relative to the config). - `cli.ts` — commander CLI (single-reviewer or `--config` loop). @@ -93,15 +140,24 @@ CLI, env vars, or `.loupe.json` — those live in `action`. ## Extending -- **New harness** — implement `Harness`, add it to `registry()`. Map - `systemPrompt`/`userPrompt`/`model`/`agentic` onto its CLI; reuse `runCli` or - write a streamer like whip's. Set `credentialKeys` to what it needs forwarded. +- **New agent** — a definition builder in `harness/src/agents.ts`, a case in + `agentFor`, and a `HarnessContext.agent` kind. Hooks are pure functions in + `hooks.ts` with a test; returning nothing allows unchanged, hooks only + narrow. +- **Refresh the SDK** — `task vendor:whip WHIP=` rebuilds and + recopies `vendor/@whip` and records the release, commit, and wire protocol + in `VERSION`. Bump `PINNED_WHIP_TAG` and the workflows' install step with it. - **New credential provider** — implement `CredentialProvider`, add a case to `resolveProviders` in `action/src/config.ts`. - **New reviewer** — edit `.loupe.json`. No code change. ## Tests -Vitest, beside the code (`packages/core/tests`). The load-bearing logic covered: +Vitest, beside the code (`packages/*/tests`). The load-bearing logic covered: hunk→commentable-line parsing, off-diff finding rejection, JSON extraction, -empty-output error, severity normalization. Run `task test` (or `task check`). +empty-output error, severity normalization, the review pipeline against a fake +harness, hook decision tables, definition ids, daemon planning, the event → log +and event → trace mappings, and the runner itself against `@whip/sdk/testing`'s +scripted daemon (serve, pinned creation, configure, effort, run, multi-session, +failure). Run `task test` (or `task check`). Live behavior is checked with +`loupe review … --dry-run` against a real PR. diff --git a/docs/configuration.md b/docs/configuration.md index 8297cda..e6889fd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -35,7 +35,7 @@ whose globs match a changed file and posts each as its own labeled review | `dir` | no | One directory or a list. Overrides the top-level `dir` for this reviewer. | | `exclude` | no | Globs removed from scope (lockfiles, generated output, …). | | `model` | no | Overrides the run's model for this reviewer. | -| `reasoning` | no | `low` \| `medium` \| `high`. Passed to the harness natively (whip `defaultEffort` in the materialized `WHIP_HOME`, `claude --effort`, codex `model_reasoning_effort`) and noted in the prompt. Unset = the harness's own default. whip without a `whip` config block keeps its own default effort; only the prompt note applies. | +| `reasoning` | no | `low` \| `medium` \| `high`. Set on the reviewer's whip session natively (`session.effort`) and noted in the prompt. Unset = whip's own default. | | `agentic` | no | `false` to run one-shot; omitted = agentic (the default). | | `profile` | no | Noise profile: `quiet` (blockers) \| `chill` (default) \| `assertive` (all). | | `verify` | no | `false` to skip the verification pass (default on). | @@ -43,7 +43,7 @@ whose globs match a changed file and posts each as its own labeled review | `ensemble` | no | `["kimi-k3","glm-5.2-fast"]` — run several models, keep findings a majority agree on. | | `skills` | no | Paths to skill docs (a `SKILL.md` or a skill dir) folded into the reviewer, e.g. `[".agents/skills/i-have-adhd"]` to enforce a terse output style. | | `procedure` | no | `false` drops the always-on review procedure (caller check, wrapper rule) from this reviewer's prompt. Also a top-level default. | -| `promptCache` | no | `false` (default `true`) suppresses the stable prompt-cache key loupe sends so the provider reuses the cached system prefix. Set `false` for a reviewer whose model rejects `prompt_cache_key` as an unrecognized argument (e.g. some OpenAI-compatible endpoints strict-validate unknown fields); those models cache the prefix automatically by match, so the key adds nothing and its presence can 400. Also a top-level default and the `prompt-cache` Action input / `--no-prompt-cache` flag. The whip harness also self-heals a cache-key 400 by retrying without the key, so this flag only skips that wasted round-trip. | +| `promptCache` | no | `false` (default `true`) suppresses the stable prompt-cache key loupe sends so the provider reuses the cached system prefix. Set `false` for a reviewer whose model rejects `prompt_cache_key` as an unrecognized argument (e.g. some OpenAI-compatible endpoints strict-validate unknown fields); those models cache the prefix automatically by match, so the key adds nothing and its presence can 400. Also a top-level default and the `prompt-cache` Action input / `--no-prompt-cache` flag. loupe sends the key as the session's `cache_key`. | | `priorComments` | no | What happens to this reviewer's earlier inline comments on a re-review: `resolve` (default: resolve the thread, history kept) \| `delete` \| `keep` (leave them, new comments accumulate). Also a top-level default and the `prior-comments` Action input / `--prior-comments` flag. | | `maxComments` | no | Max inline comments posted (default 10). Extras are ranked out by severity and listed in a collapsed "Additional findings" section of the summary; a demoted blocker still requests changes. Also a top-level default and the `max-comments` Action input / `--max-comments` flag. | | `crossReviewerDedup` | no | `false` lets the same finding post from multiple reviewers (no dedup). Default `true`: before posting, the union of all reviewers' inline findings is deduplicated so the same reworded claim posts once. Also a top-level default and the `cross-reviewer-dedup` Action input. | @@ -60,7 +60,7 @@ Top level or per reviewer, a string or a list: ``` Only changed files under a listed directory are in scope, and convention docs -(`AGENTS.md`, …) are read from each. With one directory the harness runs inside +(`AGENTS.md`, …) are read from each. With one directory the agent runs inside it and the prompt explains the path mapping. With several it runs at the repo root so the agent reads both sides of a change in one review. The Action input and `--dir` flag take a comma-separated list. A single string keeps working as @@ -81,14 +81,19 @@ Every review's system prompt is assembled from: senior-reviewer prompt; a reviewer's `prompt`/`promptFile` (or `--prompt-file`) replaces this layer only. 2. **Reasoning note** — from `reasoning` / `--reasoning`. -3. **Tool directive + output contract** — always appended by loupe. This is why - a custom prompt can never break JSON parsing or change tool behavior. Write - only persona/priorities in a custom prompt, never the JSON schema. +3. **Tool directive + output contract** — always appended by loupe. The + contract is also the reviewer definition's output schema: the whip daemon + shows the model the shape, validates the final message against it, and + corrects it once. This is why a custom prompt can never break JSON parsing + or change tool behavior. Write only persona/priorities in a custom prompt, + never the JSON schema. ## Agentic vs one-shot -- **Agentic (default):** the harness gets the checkout and uses tools to inspect - the real schema/code, not just the diff (higher turn budget). Needs a real +- **Agentic (default):** the reviewer is a whip agent with read-only access to + the checkout (whip's `files` module; a hook makes any write attempt visible) + and reads the PR's hunks from a diff file loupe writes, so it can inspect the + real schema/code, not just the diff (higher turn budget). Needs a real checkout as workdir — CI checks the repo out; locally pass `--workdir`. - **One-shot:** `"agentic": false` (or `--no-agentic`) — reviews from the diff alone. Faster and cheaper; good for a general bug pass. @@ -108,7 +113,7 @@ or `LOUPE_CONVENTION_PATHS`). With `--dir`, paths resolve under the subdir Load your repo's skill docs into the reviewer. Each entry is a path (relative to the checkout) to a `SKILL.md` or a skill directory; loupe reads them and folds -them into the system prompt. This is how you enhance the underlying agent harness +them into the system prompt. This is how you enhance the reviewer agent with your repo's own skills — the skills live in the **consuming repo**, not in loupe. diff --git a/docs/credentials.md b/docs/credentials.md index f28384c..6491154 100644 --- a/docs/credentials.md +++ b/docs/credentials.md @@ -1,13 +1,27 @@ # Credentials -loupe resolves the secrets a harness needs through an ordered **provider chain**; -the first provider to return a value for a key wins. Resolved values are injected -into the harness subprocess env. +The whip daemon holds the model credentials; loupe never sends a model key +itself. loupe's job is to make sure the daemon it attaches to has one. -## Providers +## Locally + +Your own whip login (`whipcode auth inference-net`, stored under +`~/.whipcode`). loupe attaches to your running daemon over its Unix socket, or +starts one, and ignores the config's `whip` block when its `apiKeyEnv` is not +set — so `loupe review` just works and a developer's real `~/.whipcode` is never +modified. Every session still names the block's provider, so its models resolve +even when the daemon's default provider is another; the provider must be +configured in your daemon. + +## In CI + +The `.loupe.json` `whip` block names the provider and the env var it reads the +key from (`apiKeyEnv`). loupe resolves that variable through the provider +chain, writes the block into a throwaway `WHIPCODE_HOME`, starts a dedicated +daemon there with the key in its environment, and stops it when the run ends. Set the chain with `--providers` (CLI) or `LOUPE_CREDENTIAL_PROVIDERS` (Action), -comma-separated: +comma-separated; the first provider to return the value wins: | Provider | Reads from | |---|---| @@ -18,23 +32,9 @@ comma-separated: Add your own by implementing `CredentialProvider` (`{ name, get(key) }`) from `@loupe/credentials`. -## Per-harness auth - -Each harness declares `credentialKeys` — the env vars it wants forwarded: - -| Harness | Keys | Notes | -|---|---|---| -| `whip` | (none) | Self-authenticates. Reads `INFERENCE_API_KEY` if present, else its own local login (`~/.whip`). inference.net is its built-in default provider. | -| `claude` | `ANTHROPIC_API_KEY` | Falls back to the local `claude` login if the key is absent. | -| `codex` | `OPENAI_API_KEY` | — | - -Resolution is **best-effort**: loupe forwards whatever keys the chain can supply -and does not hard-fail on a missing one — a locally-logged-in harness self-auths, -and a genuinely missing key surfaces as the harness's own auth error (visible at -`LOG_LEVEL=debug`). - -## In CI +Resolution is **best-effort**: if no provider supplies the key, loupe logs a +warning and falls back to the local login, and a genuinely missing credential +surfaces as the daemon's own auth error (visible at `LOG_LEVEL=debug`). -The harness runs on the runner, so its key must reach the job env. With whip + -inference.net, expose `INFERENCE_API_KEY` to the job (e.g. via Infisical) and the -`env` provider forwards it. See [github-action.md](github-action.md). +The resolved values are also what the review trace scrubs from every payload +before it reaches the Actions step summary; see [Review traces](review-traces.md). diff --git a/docs/github-action.md b/docs/github-action.md index c82b6ea..9d883d2 100644 --- a/docs/github-action.md +++ b/docs/github-action.md @@ -1,8 +1,10 @@ # GitHub Action `action.yml` is a composite action: it sets up Bun, installs loupe's deps, and -runs the reviewer against the PR. The harness CLI must be installed by the -consuming workflow (it is not bundled). +runs the reviewer against the PR. The `whipcode` binary should be installed by +the consuming workflow (it is not bundled); when it is missing, loupe downloads +the pinned release it was built against. loupe starts a whip daemon from it for +the duration of the run. ## Minimal workflow @@ -22,20 +24,25 @@ jobs: continue-on-error: true # advisory: never block a PR steps: - uses: actions/checkout@v4 - - name: Install whip + - name: Install whipcode run: | - curl -fsSL -o whip https://github.com/context-labs/whip/releases/download/v0.4.1/whip-linux-x64 - chmod +x whip && sudo mv whip /usr/local/bin/whip + curl -fsSL -o whipcode https://github.com/context-labs/whip/releases/download/v1.0.0/whipcode-linux-x64 + chmod +x whipcode && sudo mv whipcode /usr/local/bin/whipcode - uses: context-labs/loupe@v0 with: - harness: whip model: kimi-k3 config: .loupe.json env: INFERENCE_API_KEY: ${{ secrets.INFERENCE_API_KEY }} ``` -See `examples/review.example.yml` for whip / claude / custom-prompt +Pin the whip release to the one loupe's vendored SDK came from +(`vendor/@whip/VERSION`); the SDK refuses a daemon from another protocol major. +With the `whip` block in `.loupe.json` and `INFERENCE_API_KEY` in the job env, +loupe writes the provider config into a throwaway `WHIPCODE_HOME`, starts a +dedicated daemon there, and stops it when the run ends. + +See `examples/review.example.yml` for the config-driven and custom-prompt variants. The live monorepo wiring is `inference/.loupe/` + the `inference--loupe-review.yml` workflow. @@ -56,7 +63,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: context-labs/loupe@v0 - with: { harness: whip, config: .loupe.json } + with: { config: .loupe.json } env: { INFERENCE_API_KEY: ${{ secrets.INFERENCE_API_KEY }} } ``` @@ -66,12 +73,15 @@ push it to the PR branch), `@loupe ` (answer grounded in the diff), `@loupe help`. loupe auto-detects the comment event and switches to chat mode; a comment without `@loupe` is ignored. -`@loupe fix` needs the chat job to have `permissions: contents: write` (to push) -and only works on same-repo branches, not forks. +`@loupe fix` runs a fixer agent that may edit files and run commands inside the +checkout but is blocked from git history commands, recursive deletes, and +escalation; loupe itself commits and pushes. It needs the chat job to have +`permissions: contents: write` (to push) and only works on same-repo branches, +not forks. ## Inputs -`harness`, `model`, `reasoning`, `profile`, `verify`, `full`, `prompt-file`, +`model`, `reasoning`, `profile`, `verify`, `full`, `prompt-file`, `config`, `reviewer`, `dir`, `convention-paths`, `credential-providers`, `ensemble`, `skills`, `timezone`, `max-turns`, `max-comments`, `cross-reviewer-dedup` (default `true`), `prompt-cache` (default `true`), `prior-comments` (default @@ -83,13 +93,16 @@ action's own directory. The entrypoint reads only these (parsed in `packages/action/src/config.ts`): `GITHUB_TOKEN`, `GITHUB_REPOSITORY`, `GITHUB_EVENT_PATH`, `GITHUB_WORKSPACE`, -`LOUPE_PR_NUMBER`, `LOUPE_HARNESS`, `LOUPE_MODEL`, `LOUPE_REASONING`, +`LOUPE_PR_NUMBER`, `LOUPE_MODEL`, `LOUPE_REASONING`, `LOUPE_PROMPT_FILE`, `LOUPE_CONFIG`, `LOUPE_REVIEWER`, `LOUPE_DIR`, `LOUPE_CONVENTION_PATHS`, `LOUPE_CREDENTIAL_PROVIDERS`, `LOUPE_INFISICAL_ENV`, `LOUPE_INFISICAL_PROJECT_ID`, `LOUPE_PROFILE`, `LOUPE_VERIFY`, `LOUPE_FULL`, `LOUPE_ENSEMBLE`, `LOUPE_SKILLS`, `LOUPE_TIMEZONE`, `LOUPE_MAX_TURNS`, `LOUPE_ENSEMBLE`, `LOUPE_SKILLS`, `LOUPE_TIMEZONE`, `LOUPE_MAX_TURNS`, `LOUPE_MAX_COMMENTS`, `LOUPE_PRIOR_COMMENTS`, `LOUPE_CROSS_REVIEWER_DEDUP`, `LOUPE_PROMPT_CACHE`. +`LOUPE_HARNESS` (the deprecated `harness` input) is still read and must be empty +or `whip`. The whip binary is `whipcode` on `PATH` unless `LOUPE_WHIP_BIN` says +otherwise. Comment/chat mode is auto-detected from `GITHUB_EVENT_NAME` (`issue_comment` / `pull_request_review_comment`), which the runner sets. @@ -149,5 +162,5 @@ read `steps..outputs.status`: retried for this kind, so it costs one call per reviewer, not two. - `rate-limit` — the provider throttled the call (HTTP 429, rate limit exceeded). Also not retried via the mode-switch fallback. -- `failed` — any other failure (harness crash, transient error, etc.). The +- `failed` — any other failure (a failed turn, a daemon that would not start, a transient error, etc.). The agentic→one-shot fallback still runs for unclassified errors. diff --git a/docs/guide.md b/docs/guide.md index b8902c9..424a04a 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -6,7 +6,9 @@ 2. Filters the files to each reviewer's scope (subdir + globs). 3. Builds a system prompt (reviewer guidance + reasoning + tool directive + JSON output contract) and a user prompt (PR metadata + conventions + diff). -4. Runs a harness (an agent CLI) to produce findings as JSON. +4. Runs the reviewer as a whip agent (one turn on the whip daemon, through + its SDK) whose final message is the review JSON, validated by the daemon + against the review schema. 5. Validates each finding's `path:line` against the diff — off-diff findings degrade to summary notes so a bad line never rejects the review. 6. Posts inline findings in an empty-body review and creates or updates one @@ -19,13 +21,15 @@ bun install ``` -Requires [Bun](https://bun.sh) 1.3.14 and a harness CLI on `PATH` (default: -[whip](https://github.com/context-labs/whip); also `claude`, `codex`). +Requires [Bun](https://bun.sh) 1.3.14 and +[whip](https://github.com/context-labs/whip)'s `whipcode` binary: on `PATH`, +named by `LOUPE_WHIP_BIN`, or downloaded by loupe itself (the pinned release in +`vendor/@whip/VERSION`) when neither exists. ## Run a review locally ```bash -# defaults: whip harness, kimi-k3, harness-default reasoning effort, agentic +# defaults: kimi-k3, whip's default reasoning effort, agentic bun run packages/action/src/cli.ts review owner/repo#123 # PR URL also works @@ -47,9 +51,8 @@ Resolved in order: `--token` → `GITHUB_TOKEN` → `gh auth token`. Needs | Flag | Default | Purpose | |---|---|---| -| `-H, --harness ` | `whip` | Agent CLI: `whip`, `claude`, `codex`. | -| `-m, --model ` | `kimi-k3` | Model id passed to the harness. | -| `-r, --reasoning ` | harness default | `low` \| `medium` \| `high`, passed to the harness natively. | +| `-m, --model ` | `kimi-k3` | Model id the reviewer runs on. | +| `-r, --reasoning ` | whip default | `low` \| `medium` \| `high`, set on the whip session. | | `--prior-comments ` | `resolve` | Prior inline comments on re-review: `resolve` \| `delete` \| `keep`. | | `--max-turns ` | `10` | Cap on the agentic tool loop. | | `--max-comments ` | `10` | Max inline comments per review; extras are ranked by severity into a collapsed summary section. | @@ -66,7 +69,7 @@ Resolved in order: `--token` → `GITHUB_TOKEN` → `gh auth token`. Needs | `--prompt-file ` | — | Custom reviewer guidance (single-reviewer mode). | | `-c, --conventions ` | `CLAUDE.md,AGENTS.md,.loupe.md,CONTRIBUTING.md` | Repo docs to enforce. | | `-p, --providers ` | `env,dotenv` | Credential provider chain. | -| `-w, --workdir ` | cwd | Repo checkout the harness may explore. | +| `-w, --workdir ` | cwd | Repo checkout the reviewer may explore. | | `--dry-run` | off | Compute + print, do not post. | | `-t, --token ` | — | GitHub token override. | | `--infisical-env`, `--infisical-project` | — | Infisical provider options. | diff --git a/docs/how-it-works/README.md b/docs/how-it-works/README.md index f50dbfe..f45a019 100644 --- a/docs/how-it-works/README.md +++ b/docs/how-it-works/README.md @@ -1,6 +1,6 @@ # How loupe works -loupe is a GitHub Action that reviews pull requests with an agentic coding CLI (whip, claude, or codex). A repo declares one or more focused **reviewers** in a `.loupe.json`, each with its own prompt and file globs. loupe runs every reviewer whose globs match a changed file, posts reviewer-specific inline comments, and updates one persistent combined summary. +loupe is a GitHub Action that reviews pull requests with [whip](https://github.com/context-labs/whip) agents. A repo declares one or more focused **reviewers** in a `.loupe.json`, each with its own prompt and file globs. loupe runs every reviewer whose globs match a changed file, posts reviewer-specific inline comments, and updates one persistent combined summary. Source: [context-labs/loupe](https://github.com/context-labs/loupe). loupe reviews its own PRs with [`.loupe/config.json`](../../.loupe/config.json). diff --git a/docs/how-it-works/context.md b/docs/how-it-works/context.md index c3603f2..9dd8a4c 100644 --- a/docs/how-it-works/context.md +++ b/docs/how-it-works/context.md @@ -15,21 +15,21 @@ This page expands the **Build context and run agent** box in [A review run](./re ## System prompt, in order -Order matters for caching. Everything here is identical across PRs for a given reviewer, so the provider reuses the cached prefix. `-cache-key loupe///` names that prefix. The key is on by default; set `promptCache: false` (the `prompt-cache` Action input / `--no-prompt-cache` flag) for a reviewer whose model rejects `prompt_cache_key` — those models cache the prefix automatically by match, so the key is redundant. If the provider 400s on the key, the whip harness also self-heals by retrying once without it. +Order matters for caching. Everything here is identical across PRs for a given reviewer, so the provider reuses the cached prefix. The session's `cache_key`, `loupe///`, names that prefix. The key is on by default; set `promptCache: false` (the `prompt-cache` Action input / `--no-prompt-cache` flag) for a reviewer whose model rejects `prompt_cache_key` — those models cache the prefix automatically by match, so the key is redundant. 1. **Guidance.** The reviewer's `prompt` or `promptFile`, verbatim. It replaces loupe's default guidance when set. 2. **Procedure.** Always appended, even under custom guidance: locate and read the callers of every changed export before judging, follow thin wrappers one more hop, treat a newly interactive or blocking call inside a spinner or other terminal-owning wrapper as a defect. `procedure: false` removes it. 3. **Skills.** Each path in `skills` is read from the checkout. A directory means its `SKILL.md`. A missing skill logs a warning and is skipped. 4. **Conventions.** Every convention doc that exists at the PR head, concatenated under `# ` headers. Fetched via the GitHub API, not the checkout. -5. **Reasoning note.** One sentence, only when `reasoning` is configured. The same value goes to the harness natively. +5. **Reasoning note.** One sentence, only when `reasoning` is configured. The same value is set on the whip session. 6. **Profile directive.** Which severities to report. -7. **Tool directive.** Agentic: you have the checkout, read hunks from the diff file on demand, spend the turn budget on callers and contracts first, use subagents only for genuinely parallel work, then stop and emit JSON. Headless (retry, chat): no tools, diff is inline. The verify pass uses the agentic directive when the review was agentic and a checkout exists, else the headless directive. -8. **Output contract.** One JSON object, not wrapped in a code fence: `summary`, `concerns[]`, `highlights[]`, optional `diagram`, `findings[]` with `path`, `line` (new-file line, must be in the diff), `severity`, `body`. `summary`, `detail`, and `body` are GitHub Markdown and may hold paragraphs and fenced code blocks. +7. **Tool directive.** Agentic: you have the checkout, read hunks from the diff file on demand, spend the turn budget on callers and contracts first, then stop and emit JSON. Headless (retry, chat): no tools, diff is inline. The verify pass uses the agentic directive when the review was agentic and a checkout exists, else the headless directive. +8. **Output contract.** One JSON object, not wrapped in a code fence (the same schema is the reviewer definition's output contract, which the daemon validates the final message against): `summary`, `concerns[]`, `highlights[]`, optional `diagram`, `findings[]` with `path`, `line` (new-file line, must be in the diff), `severity`, `body`. `summary`, `detail`, and `body` are GitHub Markdown and may hold paragraphs and fenced code blocks. ## User message - Environment line with the current date and time in the configured `timezone`. -- When `dir` names one directory and the harness runs inside it: "Your working directory is `/` inside the repository. Listed paths are repository-relative; drop the prefix when opening files, report `path` exactly as listed." With several directories the harness runs at the repo root and no note is needed. +- When `dir` names one directory and the agent runs inside it: "Your working directory is `/` inside the repository. Listed paths are repository-relative; drop the prefix when opening files, report `path` exactly as listed." With several directories the agent runs at the repo root and no note is needed. - PR title and body. - `pathInstructions` whose glob matches at least one reassessed file, one bullet each. - On an incremental run: "Files to reassess", then "the other listed files are context". diff --git a/docs/how-it-works/review-run.md b/docs/how-it-works/review-run.md index 7a9ae2f..fab2f5c 100644 --- a/docs/how-it-works/review-run.md +++ b/docs/how-it-works/review-run.md @@ -24,8 +24,8 @@ flowchart LR 2. **Scope.** Keep files under any listed `dir` that match the reviewer's `include` and miss its `exclude`. Zero files means the reviewer is skipped with no GitHub writes. 3. **Incremental or full.** See [First run vs later runs](./first-vs-incremental.md). Default is incremental. `@loupe review` and the `full` input force full. 4. **Prompts.** Every in-scope PR file's patch is rendered as a `### ` section and written to a temp file. loupe greps the checkout for callers of the exports the diff touches, three hops deep within the package. The user prompt carries the file tree, that path, the cwd-to-repo path mapping when `dir` is set, the call-site list, and on an incremental run the list of files to reassess. See [What the agent sees](./context.md). -5. **Run the agent.** For whip: `whip run --format json -quiet -no-session -max-turns -system -m -cache-key loupe///`, prompt on stdin, in a throwaway `WHIP_HOME` built from the config's `whip` block (with `defaultEffort` when `reasoning` is set). `claude` gets `--effort`, `codex` gets `-c model_reasoning_effort`. If the agentic run throws or returns something that is not a review, loupe retries once headless with the reassessed files' diff inlined and marks the run degraded — unless the harness classified the failure as `quota` or `rate-limit` (a `HarnessError` with that `kind`), which fails fast with no headless retry, since switching modes hits the same provider and the same billing/throttle. The Action surfaces the outcome as its `status` output; see [GitHub Action](../github-action.md#branching-on-failure). -6. **Parse.** The last `{...}` in stdout is the review. It must carry a string `summary`, so `{}` or `{"status":"done"}` is a failure, not a clean review; `findings` and `concerns` may be omitted for a clean review but must be arrays when present. Each finding is validated on its own; malformed entries are dropped and counted. Off-scale severities (`critical`, `minor`, ...) map onto blocker, warning, nit. +5. **Run the agent.** The reviewer is a whip agent definition: loupe's system prompt as its persona, the `context` and `files` modules with read-only capability (headless: `context` only), and the review schema as its output contract. loupe serves it from its own process and opens one session on the daemon (`cwd`, `model`, the `whip` block's `provider`, `permission_mode: automatic`; `headless`, `max_turns`, `cache_key: loupe///`; `session.effort` when `reasoning` is set), runs the prompt as one turn through the SDK, and deletes the session. In CI the daemon runs in a throwaway `WHIPCODE_HOME` built from the `whip` block. The daemon validates the final message against the contract and corrects it once; the result carries the validated value. If the turn fails or its output is not a review, loupe retries once headless with the reassessed files' diff inlined and marks the run degraded — unless the harness classified the failure as `quota` or `rate-limit` (a `HarnessError` with that `kind`), which fails fast with no headless retry, since switching modes hits the same provider and the same billing/throttle. The Action surfaces the outcome as its `status` output; see [GitHub Action](../github-action.md#branching-on-failure). +6. **Parse.** The turn's validated output (or, when the turn cap forced a final answer past the check, the last `{...}` in its final message) is the review. It must carry a string `summary`, so `{}` or `{"status":"done"}` is a failure, not a clean review; `findings` and `concerns` may be omitted for a clean review but must be arrays when present. Each finding is validated on its own; malformed entries are dropped and counted. Off-scale severities (`critical`, `minor`, ...) map onto blocker, warning, nit. 7. **Scope.** On an incremental run, findings anchored on a file outside the reassessed set are dropped and counted, so they cannot duplicate a prior comment that was deliberately kept. 8. **Anchor.** GitHub only accepts inline comments on lines present in the diff. A finding on an exact diff line stays inline. One within 10 lines snaps to the nearest diff line. Anything else, or on a file not in scope, becomes an off-diff note in the summary. 9. **Profile filter.** `quiet` keeps blockers, `chill` (default) keeps blockers and warnings, `assertive` keeps everything. @@ -40,9 +40,9 @@ flowchart LR | `maxTurns` | 10 | Agentic tool-loop cap, per reviewer or top level | | `maxComments` | 10 | Inline comment cap; ranked-out extras go to the summary | | headless cap | 10, fixed | Safety net for the one-shot retry and a headless verify pass | -| `reasoning` | harness default | Native effort setting on the harness plus one sentence in the prompt | +| `reasoning` | whip default | Set on the whip session (`session.effort`) plus one sentence in the prompt | -Hitting `maxTurns` mid-exploration is an error from the harness, which triggers the headless retry. +Hitting `maxTurns` mid-exploration makes the daemon force one tools-off final answer; if that answer is not a review, the headless retry runs. The pipeline hands its result to [GitHub objects](./github-objects.md), which defines the review, summary, and cleanup behavior. diff --git a/docs/how-it-works/triggers.md b/docs/how-it-works/triggers.md index 3d6a93b..72e1fa1 100644 --- a/docs/how-it-works/triggers.md +++ b/docs/how-it-works/triggers.md @@ -39,7 +39,7 @@ flowchart TD ## Job setup (both workflows) 1. `actions/checkout`. The review workflow lands on the PR merge ref. A comment-triggered chat workflow starts from the default branch; `@loupe fix` fetches and checks out the PR head branch before the agent edits it. -2. Install the harness CLI (`whip`, `claude`, or `codex`). +2. Install `whipcode` (the release pinned in `vendor/@whip/VERSION`; loupe downloads it itself when it is missing). 3. Provide the model provider's API key as an environment variable. Where it comes from (repo secret, Infisical, Vault) is the workflow's business, not loupe's. 4. `uses: context-labs/loupe@main` with `config: `. diff --git a/docs/review-traces.md b/docs/review-traces.md index 605d0da..5098aa0 100644 --- a/docs/review-traces.md +++ b/docs/review-traces.md @@ -18,7 +18,7 @@ all outcomes have completed. For every reviewer (or the single default review), the summary gets one section (`### \`\``) containing: -- **metadata** — the harness (`whip`, `claude`, …), model, and phase labels +- **metadata** — the harness (`whip`), model, and phase labels (`primary`, `fallback`, `ensemble:`, `verify`) carried on the events, when present; - **reasoning** — a collapsed `
` block with the total reasoning char @@ -33,12 +33,12 @@ For every reviewer (or the single default review), the summary gets one section Output is **bounded**: tool args/results and reply blobs are truncated, reasoning is head-only, each reviewer section and the whole file have a character cap. The step summary is free of unbounded dumps; "full detail is in the job logs" is -real, because the harness's raw stderr/stdout and structured events are still -logged as before. +real, because the run log (cells, host calls, hook decisions, cost) still +carries everything. -Known **secrets don't leak**: the harness hands the resolved credential values -(e.g. `ANTHROPIC_API_KEY`) to the subprocess via its env, and those exact values -are scrubbed (`[REDACTED]`) from any reasoning delta, tool arg/result or reply +Known **secrets don't leak**: the credential values loupe resolves for the +daemon (e.g. `INFERENCE_API_KEY`) and every secret-looking variable in the +daemon's environment are scrubbed (`[REDACTED]`) from any reasoning delta, tool arg/result or reply text before it reaches the summary. Values shorter than the noise floor are left alone, so nothing important is needlessly removed. @@ -46,14 +46,14 @@ Escaping keeps the summary valid Markdown even when a tool returns backticks, angles, or hashes: blob content is placed in fenced blocks and backticks are escaped so they cannot close an unrelated fence. -### Non-Whip harnesses +### Where the events come from -The reasoning/tool transcript comes from **whip's** structured NDJSON event log. -If a reviewer runs a different harness (`claude`, `codex`), that log doesn't -exist, so its section carries no per-step detail — instead it states **explicitly -that detailed reasoning and per-tool traces are Whip-only** and that the reviewer -ran the non-whip harness by name, so the note is never mistaken for a run that -silently produced nothing. The final `done`/`error` result still renders. +The transcript is whip's typed turn events (`session.run` in the SDK), mapped by +`turnEventToTrace`: reasoning and text deltas as they stream, each cell (the +model's code) and host call (a module operation) as a tool call with its result, +then `done` with the final message or `error` with the failure. Every reviewer +runs on whip, so every section carries the transcript; the renderer's note for +another harness name is only a guard. ## When it runs @@ -76,7 +76,7 @@ Pointing `GITHUB_STEP_SUMMARY` at a scratch file is enough to make the Action append its trace there on a normal run (pull_request or `@loupe review`). To eyeball the exact summary output offline — no checkout, no harness, no model — -a **fixture preview** renders realistic whip/ensemble/error/non-whip reviewer +a **fixture preview** renders realistic whip/ensemble/error reviewer traces and writes them to a path (plain file write, never `GITHUB_STEP_SUMMARY`): ```bash @@ -85,7 +85,7 @@ bun run trace:preview /tmp/trace.md # write to a file ``` To sanity-check the rendering, the unit tests exercise the renderer and writer -directly (`packages/action/tests/trace.test.ts`), and the harness's raw-event → +directly (`packages/action/tests/trace.test.ts`), and the harness's turn-event → normalized-event mapping plus secret scrubbing is covered by `packages/harness/tests/trace.test.ts`: @@ -95,7 +95,7 @@ bun run test ## Architecture boundaries -- `@loupe/harness` owns **capture/normalization** — the whip NDJSON event log is +- `@loupe/harness` owns **capture/normalization** — whip's typed turn events are mapped to the normalized `HarnessTraceEvent` union and emitted through the optional `HarnessContext.trace` callback. It also scrubs the run's known credential values from every emitted payload. Nothing here knows about GitHub. diff --git a/examples/.loupe.json b/examples/.loupe.json index 0104d2b..7c28d6b 100644 --- a/examples/.loupe.json +++ b/examples/.loupe.json @@ -1,5 +1,4 @@ { - "harness": "whip", "model": "kimi-k3", "timezone": "PST", "dir": "inference", diff --git a/examples/review.example.yml b/examples/review.example.yml index fc898d5..10b2f35 100644 --- a/examples/review.example.yml +++ b/examples/review.example.yml @@ -10,49 +10,36 @@ permissions: pull-requests: write # required to post the inline review jobs: - # --- Variant A: internal (whip + inference.net) --------------------------- - # whip self-authenticates from its own login; nothing to wire but the login. - review-whip: + # --- Variant A: flags only ------------------------------------------------ + # The whip daemon self-authenticates from its own login; nothing to wire but + # the login and the whipcode binary (loupe downloads the pinned release when + # it is missing from PATH). + review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: context-labs/loupe@v0 with: - harness: whip model: kimi-k3 reasoning: medium dir: inference # scope a monorepo to one folder (optional) - # --- Variant B: external (claude + API key secret) ------------------------ - review-claude: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: context-labs/loupe@v0 - with: - harness: claude - model: claude-opus-4-8 - reasoning: high - env: - # The harness CLI reads this; loupe's env provider forwards it in. - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - - # --- Variant C: custom reviewer prompt ------------------------------------ + # --- Variant B: custom reviewer prompt ------------------------------------ # Point prompt-file at a doc in your repo. Your guidance replaces the default; - # loupe still appends the JSON output contract, so parsing can't break. + # loupe still appends the JSON output contract, and the whip daemon enforces + # it on the reviewer's final message, so parsing can't break. review-custom: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: context-labs/loupe@v0 with: - harness: whip prompt-file: .github/loupe-prompt.md - # --- Variant D: config-driven (recommended for multiple repos) ------------ - # Put ALL review policy in .loupe.json — harness, model, timezone, dir, the - # whip provider + model panel, and the reviewers. The workflow then only - # provisions: checkout, the harness binary, and the secret. Nothing to repeat + # --- Variant C: config-driven (recommended for multiple repos) ------------ + # Put ALL review policy in .loupe.json — model, timezone, dir, the whip + # provider + model panel, and the reviewers. The workflow then only + # provisions: checkout, the whipcode binary, and the secret. Nothing to repeat # across repos or between the review and chat workflows. review-config: runs-on: ubuntu-latest @@ -62,10 +49,14 @@ jobs: pull-requests: write steps: - uses: actions/checkout@v4 - # install your harness binary here (e.g. curl the whip release), then: + - name: Install whipcode (the release in loupe's vendor/@whip/VERSION) + run: | + curl -fsSL -o whipcode https://github.com/context-labs/whip/releases/download/v1.0.0/whipcode-linux-x64 + chmod +x whipcode && sudo mv whipcode /usr/local/bin/whipcode - uses: context-labs/loupe@v0 with: - config: .loupe.json # harness/model/timezone/dir/whip all live here + config: .loupe.json # model/timezone/dir/whip all live here env: - # only the secret value stays in the workflow; its NAME is in .loupe.json + # only the secret value stays in the workflow; its NAME is in .loupe.json. + # loupe starts a dedicated whip daemon in a throwaway home with it. INFERENCE_API_KEY: ${{ secrets.INFERENCE_API_KEY }} From c188cedd475800d75593bc5232cffe1d0da22e21 Mon Sep 17 00:00:00 2001 From: Sam Heutmaker Date: Sat, 26 Sep 2026 17:26:04 -0700 Subject: [PATCH 4/6] Treat a reasoning effort the model lacks as advisory The first dry run on the desktop daemon failed both the agentic review and its headless fallback with `session.effort failed: glm-5.3 does not support effort "medium"`: the daemon checks the effort against the model's levels, where `whip run` had quietly applied its model-aware default. The session now keeps that default and logs what was asked for and why it was not applied; the reasoning note in the prompt still applies. Co-Authored-By: Claude Fable 5.1 --- packages/harness/src/client.ts | 13 +++++++-- packages/harness/tests/client.test.ts | 42 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/packages/harness/src/client.ts b/packages/harness/src/client.ts index 09a9d7e..590df1b 100644 --- a/packages/harness/src/client.ts +++ b/packages/harness/src/client.ts @@ -184,10 +184,19 @@ export async function serveAgent( if (configured.status !== "succeeded") { throw failed("run.configure", configured); } + // Reasoning effort is advisory: a model that has no such effort level + // (the daemon says so) keeps its own default, as it did under `whip run`. if (params.effort) { const effort = await session.setEffort(params.effort, false).result(); - if (effort.status !== "succeeded") - throw failed("session.effort", effort); + if (effort.status !== "succeeded") { + log.warn( + "reasoning effort not applied; the model's default applies", + { + effort: params.effort, + error: effort.failure?.message ?? effort.status, + }, + ); + } } return { rootId: session.rootId, diff --git a/packages/harness/tests/client.test.ts b/packages/harness/tests/client.test.ts index f7b52f2..8bf69f2 100644 --- a/packages/harness/tests/client.test.ts +++ b/packages/harness/tests/client.test.ts @@ -116,6 +116,48 @@ describe("runAgent", () => { expect(lines.at(-1)?.message).toBe("run finished"); }); + it("keeps going when the model has no such effort level, and says so", async () => { + const daemon = scriptedDaemon() + .serveSessions() + .reply("command.submit", (request) => + request.params["operation"] === "session.effort" + ? { + operation: "session.effort", + command_id: String(request.params["command_id"]), + ingress_seq: "901", + status: "failed", + failure: { + code: -32000, + message: 'glm-5.3 does not support effort "medium"', + }, + } + : undefined, + ); + const client = await connected(daemon); + const { log, lines } = recorder(); + const played = daemon.turn("root-1", { text: "ok", output: { ok: true } }); + const outcome = await runAgent(client, { + agent: reviewerAgent({ + name: "t", + systemPrompt: "p", + agentic: true, + output, + }), + cwd: "/repo", + prompt: "review", + effort: "medium", + logger: log, + }); + await played; + client.close(); + expect(outcome.output).toEqual({ ok: true }); + const warning = lines.find((l) => l.level === "warn"); + expect(warning?.message).toBe( + "reasoning effort not applied; the model's default applies", + ); + expect(warning?.props?.["error"]).toMatch(/does not support effort/); + }); + it("only registers an agent without tools or hooks and still runs it", async () => { const daemon = scriptedDaemon().serveSessions(); const client = await connected(daemon); From 43d67b40349d19532acf5efec25a46762a5e6a5e Mon Sep 17 00:00:00 2001 From: Sam Heutmaker Date: Sat, 26 Sep 2026 19:23:18 -0700 Subject: [PATCH 5/6] Review loupe PRs with the checked-out loupe The self-review ran `context-labs/loupe@main`, so a PR that changes loupe was reviewed by the code it replaces. Using the checkout makes each PR's self-review a run of its own code, including the whip install and daemon bring-up it ships with. On main the two are the same thing. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/loupe-review.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/loupe-review.yml b/.github/workflows/loupe-review.yml index 907e081..c58b67f 100644 --- a/.github/workflows/loupe-review.yml +++ b/.github/workflows/loupe-review.yml @@ -60,7 +60,9 @@ jobs: include-imports: true - name: Review continue-on-error: true - uses: context-labs/loupe@main + # The checked-out loupe reviews the PR, so a change to loupe is + # exercised by its own self-review. On main this is the same as @main. + uses: ./ with: config: .loupe/config.json env: From 87f9c3283c50a88f37c46486268d6819ece0d7ab Mon Sep 17 00:00:00 2001 From: Sam Heutmaker Date: Sat, 26 Sep 2026 19:32:58 -0700 Subject: [PATCH 6/6] Discover the provider's models after bringing whip up The CI path failed every turn within seconds: `output_invalid: the message is empty`, on the agentic run and its fallback alike. A daemon started from the materialized home has no model catalog, because the daemon only fetches one when a client asks (the desktop app does that when a provider is set up), and without it a model's limits collapse to a one-token output cap. glm-5.3 spends that token on reasoning and returns no content. After connecting, loupe now asks the daemon for the whip block's provider catalog (`provider.catalogs`), which fetches a missing or stale one and is a no-op on a daemon that already has it. Discovery failing is a warning that names the consequence; the turn's own failure says the rest. Co-Authored-By: Claude Fable 5.1 --- packages/action/src/run.ts | 6 +++ packages/harness/src/client.ts | 40 ++++++++++++++++++ packages/harness/src/index.ts | 2 +- packages/harness/tests/client.test.ts | 59 ++++++++++++++++++++++++++- 4 files changed, 105 insertions(+), 2 deletions(-) diff --git a/packages/action/src/run.ts b/packages/action/src/run.ts index e8794ff..b037d37 100644 --- a/packages/action/src/run.ts +++ b/packages/action/src/run.ts @@ -21,6 +21,7 @@ import { } from "@loupe/credentials"; import { connect, + discoverModels, ensureDaemon, envSecretValues, whipHarness, @@ -112,6 +113,11 @@ async function bringUp(opts: WhipOptions): Promise { logger: opts.logger, }); const client = await connect(daemon, opts.logger); + // A dedicated daemon in a throwaway home starts without a model catalog; + // the same call is a cheap no-op on a daemon that already has one. + if (opts.whipConfig) { + await discoverModels(client, opts.whipConfig.provider.name, opts.logger); + } // Known secrets (the resolved credential values handed to the daemon) are // scrubbed from every trace payload so an API key that surfaces in a tool // result or reasoning chunk never lands in the summary. diff --git a/packages/harness/src/client.ts b/packages/harness/src/client.ts index 590df1b..f6ad677 100644 --- a/packages/harness/src/client.ts +++ b/packages/harness/src/client.ts @@ -35,6 +35,46 @@ export async function connect( return client; } +/** + * Make sure the daemon knows the provider's models. A daemon started from a + * hand-written home has no model catalog until a client asks for one (the + * desktop app does that itself), and without it every model's limits collapse + * to a one-token output cap, so each turn ends with an empty final message. + * Cheap on a daemon that already has one: it refetches only a missing or + * stale catalog. Discovery failing is a warning; the turn's own failure says + * the rest. + */ +export async function discoverModels( + client: WhipClient, + provider: string, + logger: Logger, +): Promise { + const log = logger.child("whip"); + const outcome = await client.providers.catalogs({ provider }); + const result = outcome.result; + const catalog = result?.catalogs[provider]; + const errors = + (result as { errors?: Record } | undefined)?.errors ?? {}; + if (!result || errors[provider] || !catalog?.models?.length) { + log.warn( + "model discovery failed; the daemon does not know the provider's models, so requests will be capped at one output token", + { + provider, + error: + errors[provider] ?? + (outcome as { failure?: { message?: string } }).failure?.message ?? + "no catalog in the reply", + }, + ); + return; + } + log.debug("provider models discovered", { + provider, + models: catalog.models.map((m: { id: string }) => m.id), + fetchedAt: catalog.fetched_at, + }); +} + /** How one session on a served agent is set up. */ export type SessionParams = { /** Working directory the session's host modules operate in. */ diff --git a/packages/harness/src/index.ts b/packages/harness/src/index.ts index 134721e..1189dd9 100644 --- a/packages/harness/src/index.ts +++ b/packages/harness/src/index.ts @@ -32,7 +32,7 @@ export { resolveWhipBinary, } from "./daemon"; export type { DaemonHandle, WhipConfig } from "./daemon"; -export { connect, runAgent, serveAgent } from "./client"; +export { connect, discoverModels, runAgent, serveAgent } from "./client"; export type { AgentRun, RunOutcome, diff --git a/packages/harness/tests/client.test.ts b/packages/harness/tests/client.test.ts index 8bf69f2..80005e7 100644 --- a/packages/harness/tests/client.test.ts +++ b/packages/harness/tests/client.test.ts @@ -4,7 +4,7 @@ import { scriptedDaemon, type ScriptedDaemon } from "@whip/sdk/testing"; import { describe, expect, it } from "vitest"; import { reviewerAgent } from "../src/agents"; -import { runAgent, serveAgent } from "../src/client"; +import { discoverModels, runAgent, serveAgent } from "../src/client"; import { z } from "zod"; const output = z.object({ ok: z.boolean() }); @@ -258,3 +258,60 @@ describe("runAgent", () => { expect(commands(daemon).at(-1)?.operation).toBe("session.delete"); }); }); + +describe("discoverModels", () => { + const catalogs = (models: { id: string }[], error?: string) => ({ + models: {}, + providers: { + "inference-net": { base_url: "https://api.inference.net/v1" }, + }, + catalogs: { + "inference-net": { + fetched_at: "2026-09-26T00:00:00Z", + base_url: "https://api.inference.net/v1", + models, + }, + }, + errors: error ? { "inference-net": error } : {}, + }); + /** Queries ride the `query` method with the operation inside. */ + const answering = (models: { id: string }[], error?: string) => + scriptedDaemon().reply("query", (request) => + request.params["operation"] === "provider.catalogs" + ? { result: catalogs(models, error) } + : undefined, + ); + + it("asks the daemon for the provider's catalog and logs what it found", async () => { + const daemon = answering([{ id: "glm-5.3" }, { id: "kimi-k3" }]); + const client = await connected(daemon); + const { log, lines } = recorder(); + await discoverModels(client, "inference-net", log); + client.close(); + const request = daemon.current.requests.find( + (r) => r.params["operation"] === "provider.catalogs", + ); + expect(request?.params).toMatchObject({ + operation: "provider.catalogs", + payload: { provider: "inference-net" }, + }); + expect(lines.at(-1)).toMatchObject({ + level: "debug", + message: "provider models discovered", + props: { provider: "inference-net", models: ["glm-5.3", "kimi-k3"] }, + }); + }); + + it("warns, and goes on, when discovery fails or finds nothing", async () => { + const daemon = answering( + [], + "Model discovery failed; check the provider connection and retry.", + ); + const client = await connected(daemon); + const { log, lines } = recorder(); + await discoverModels(client, "inference-net", log); + client.close(); + expect(lines.at(-1)?.level).toBe("warn"); + expect(lines.at(-1)?.props?.["error"]).toMatch(/Model discovery failed/); + }); +});