From f6cdbc90a312b6d13a11e16f324e13ce62120d5a Mon Sep 17 00:00:00 2001 From: Manas Raghuwanshi Date: Mon, 7 Sep 2026 22:18:05 +0530 Subject: [PATCH] Add @executor-js/pi, a first-party Pi extension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pi ships no MCP client, so connecting it to Executor meant trusting a third-party bridge with access to every configured integration. This adds an Executor-maintained extension instead. It registers Executor's core surface — executor_execute, executor_skills, and executor_resume — as native Pi tools and forwards each call over MCP, so Pi gets the same lazy discovery every other agent does and its context stays clear of individual tool schemas. Configuration is environment-only, never package config: EXECUTOR_MCP_URL plus EXECUTOR_API_KEY (hosted) or EXECUTOR_AUTH_TOKEN (local/desktop). /executor reports the resolved endpoint and checks the connection. Closes #1957 --- .changeset/olive-hounds-repeat.md | 9 + .oxlintrc.jsonc | 19 ++ AGENTS.md | 2 + README.md | 35 +++ bun.lock | 250 ++++++++++++++++++- e2e/local/pi-extension.test.ts | 51 ++++ e2e/package.json | 2 + e2e/scenarios/pi-extension.test.ts | 83 +++++++ e2e/src/clients/pi.ts | 244 ++++++++++++++++++ package.json | 2 +- packages/hosts/pi/CHANGELOG.md | 1 + packages/hosts/pi/package.json | 81 ++++++ packages/hosts/pi/src/config.test.ts | 81 ++++++ packages/hosts/pi/src/config.ts | 93 +++++++ packages/hosts/pi/src/connection.ts | 132 ++++++++++ packages/hosts/pi/src/extension.test.ts | 316 ++++++++++++++++++++++++ packages/hosts/pi/src/extension.ts | 69 ++++++ packages/hosts/pi/src/result.test.ts | 78 ++++++ packages/hosts/pi/src/result.ts | 51 ++++ packages/hosts/pi/src/schema.test.ts | 103 ++++++++ packages/hosts/pi/src/tools.ts | 144 +++++++++++ packages/hosts/pi/tsconfig.build.json | 12 + packages/hosts/pi/tsconfig.json | 15 ++ packages/hosts/pi/tsup.config.ts | 18 ++ packages/hosts/pi/vitest.config.ts | 7 + scripts/publish-packages.ts | 1 + scripts/smoke-test-packed.ts | 1 + 27 files changed, 1887 insertions(+), 13 deletions(-) create mode 100644 .changeset/olive-hounds-repeat.md create mode 100644 e2e/local/pi-extension.test.ts create mode 100644 e2e/scenarios/pi-extension.test.ts create mode 100644 e2e/src/clients/pi.ts create mode 100644 packages/hosts/pi/CHANGELOG.md create mode 100644 packages/hosts/pi/package.json create mode 100644 packages/hosts/pi/src/config.test.ts create mode 100644 packages/hosts/pi/src/config.ts create mode 100644 packages/hosts/pi/src/connection.ts create mode 100644 packages/hosts/pi/src/extension.test.ts create mode 100644 packages/hosts/pi/src/extension.ts create mode 100644 packages/hosts/pi/src/result.test.ts create mode 100644 packages/hosts/pi/src/result.ts create mode 100644 packages/hosts/pi/src/schema.test.ts create mode 100644 packages/hosts/pi/src/tools.ts create mode 100644 packages/hosts/pi/tsconfig.build.json create mode 100644 packages/hosts/pi/tsconfig.json create mode 100644 packages/hosts/pi/tsup.config.ts create mode 100644 packages/hosts/pi/vitest.config.ts diff --git a/.changeset/olive-hounds-repeat.md b/.changeset/olive-hounds-repeat.md new file mode 100644 index 0000000000..43d361ec54 --- /dev/null +++ b/.changeset/olive-hounds-repeat.md @@ -0,0 +1,9 @@ +--- +"@executor-js/pi": minor +--- + +Add `@executor-js/pi`, a first-party extension that connects the Pi coding agent to Executor. + +Pi ships no MCP client, so Pi users previously needed a third-party bridge to reach Executor. This package registers Executor's core tools — `executor_execute`, `executor_skills`, and `executor_resume` — as native Pi tools and forwards each call over MCP. + +Install it with `pi install npm:@executor-js/pi`, then point it at Executor with `EXECUTOR_MCP_URL` and a bearer: `EXECUTOR_API_KEY` for a hosted Executor, or `EXECUTOR_AUTH_TOKEN` for a local or desktop one. `/executor` reports the resolved endpoint and checks the connection. diff --git a/.oxlintrc.jsonc b/.oxlintrc.jsonc index c86ee9dc58..0cc3bc0c9d 100644 --- a/.oxlintrc.jsonc +++ b/.oxlintrc.jsonc @@ -145,6 +145,25 @@ "executor/no-unknown-error-message": "off", }, }, + { + // boundary: the Pi extension runs inside Pi's runtime, not ours. There + // is no Effect runtime in that process, and throwing is Pi's error + // PROTOCOL, not a shortcut: `AgentToolResult` has no error field, and the + // harness marks a tool call failed exactly when `execute` throws + // (pi-agent-core, harness/execution/tools.js). The MCP client it drives + // is likewise promise-native. Scoped to this package because every file + // in it sits on that boundary; the rules that keep our own failures + // typed (no-double-cast, no-json-parse, no-explicit-any, …) stay on. + "files": ["packages/hosts/pi/**/*.ts"], + "rules": { + "executor/no-error-constructor": "off", + "executor/no-instanceof-error": "off", + "executor/no-promise-catch": "off", + "executor/no-promise-reject": "off", + "executor/no-try-catch-or-throw": "off", + "executor/no-unknown-error-message": "off", + }, + }, { "files": ["packages/kernel/core/src/code-recovery.ts"], "rules": { diff --git a/AGENTS.md b/AGENTS.md index 244b88678a..7e7e1fc71c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,6 +51,8 @@ executor. React, API, and testing helpers. - `packages/react`: shared React UI and client/atom integration. - `packages/hosts/mcp`: MCP host surface. +- `packages/hosts/pi`: the Pi extension (`@executor-js/pi`). Pi ships no MCP + client, so this bridges Executor's core tools into it. - `packages/kernel/*`: execution runtimes and code-execution substrate. - `apps/{local,cloud,cli,desktop}`: product composition roots. diff --git a/README.md b/README.md index cbf860f1bd..0bf0f5bfe0 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,41 @@ differs) already filled in. Most MCP clients only load servers at startup, so you may need to restart the client or open a new chat before the Executor tools appear. +### Use with Pi + +[Pi](https://pi.dev) ships no MCP client, so `add-mcp` does not apply to it. +Install the first-party extension instead — it needs an Executor to talk to, so +set one up first (any of the forms above): + +```bash +pi install npm:@executor-js/pi +``` + +Point it at your Executor with two environment variables — the endpoint, and a +bearer for it. Which bearer depends on where that Executor runs: + +```bash +# Hosted (executor.sh, or a deployment of your own) +export EXECUTOR_MCP_URL=https://executor.example/acme/mcp # the URL from the Connect card +export EXECUTOR_API_KEY=… # from Executor's API Keys page + +# Local CLI service or desktop app — loopback is not a free pass, auth is on +export EXECUTOR_MCP_URL=http://127.0.0.1:4788/mcp # the URL from the Connect card +export EXECUTOR_AUTH_TOKEN=… # the server's own bearer token +``` + +A local server mints that token on first run and keeps it, so it stays valid +across restarts. The Connect card's `add-mcp` command carries it in the +`Authorization: Bearer …` header it prints; on disk it is the `token` in +`~/.executor/server-control/auth.json` (or `$EXECUTOR_DATA_DIR/server-control/auth.json`). +`EXECUTOR_API_KEY` wins when both are set. + +Then run `/executor` in Pi: it prints the endpoint it resolved and the tools it +can see. Executor arrives as `executor_execute`, `executor_skills`, and +`executor_resume` — the same small surface every other agent gets, so Pi's +context stays clear of your individual tool schemas. Start with +`executor_skills` for the guide to writing `executor_execute` code. + ## Add an integration From the web UI, click **Add Integration**, paste an OpenAPI, GraphQL, or MCP URL, diff --git a/bun.lock b/bun.lock index 473028d7be..c32de7eb9c 100644 --- a/bun.lock +++ b/bun.lock @@ -355,9 +355,11 @@ "name": "@executor-js/e2e", "version": "0.0.48", "dependencies": { + "@earendil-works/pi-coding-agent": "^0.85.1", "@executor-js/api": "workspace:*", "@executor-js/emulate": "^0.14.1", "@executor-js/mcporter": "^0.11.4", + "@executor-js/pi": "workspace:*", "@executor-js/plugin-graphql": "workspace:*", "@executor-js/plugin-mcp": "workspace:*", "@executor-js/plugin-openapi": "workspace:*", @@ -755,6 +757,34 @@ "zod": "4.3.6", }, }, + "packages/hosts/pi": { + "name": "@executor-js/pi", + "version": "0.0.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + }, + "devDependencies": { + "@earendil-works/pi-ai": "^0.85.1", + "@earendil-works/pi-coding-agent": "^0.85.1", + "@effect/vitest": "catalog:", + "@executor-js/execution": "workspace:*", + "@executor-js/host-mcp": "workspace:*", + "@executor-js/plugin-mcp": "workspace:*", + "@types/node": "catalog:", + "bun-types": "catalog:", + "effect": "catalog:", + "tsup": "catalog:", + "typebox": "^1.3.7", + "typescript": "catalog:", + "vitest": "catalog:", + "zod": "4.3.6", + }, + "peerDependencies": { + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": "*", + "typebox": "*", + }, + }, "packages/kernel/core": { "name": "@executor-js/codemode-core", "version": "1.6.8", @@ -1339,6 +1369,8 @@ "@antfu/ni": ["@antfu/ni@0.23.2", "", { "bin": { "na": "bin/na.mjs", "ni": "bin/ni.mjs", "nr": "bin/nr.mjs", "nu": "bin/nu.mjs", "nci": "bin/nci.mjs", "nlx": "bin/nlx.mjs", "nun": "bin/nun.mjs" } }, "sha512-FSEVWXvwroExDXUu8qV6Wqp2X3D1nJ0Li4LFymCyvCVrm7I3lNfG0zZWSWvGU1RE7891eTnFTyh31L3igOwNKQ=="], + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.123.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1", "standardwebhooks": "^1.0.0" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-Y9oX9mPNGZClHQOFqrWRk43Srcu/UHuPq3rfxxOq7JgW0gi+lJA2MAOK4Ul3k/+AUrwRWFJvd0tK3oC0Pw25dw=="], + "@astrojs/cloudflare": ["@astrojs/cloudflare@13.1.10", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@astrojs/underscore-redirects": "1.0.3", "@cloudflare/vite-plugin": "^1.25.6", "piccolore": "^0.1.3", "tinyglobby": "^0.2.15", "vite": "^7.3.1" }, "peerDependencies": { "astro": "^6.0.0", "wrangler": "^4.61.1" } }, "sha512-Ogl8p4MifPMHbpHKJL78eqEL+I3wbHrYmo83P/FkKZQ9VzzKi2A1RjnbaiiPAwrA8xQOqIUo4zlN7+Mse0aJAQ=="], "@astrojs/compiler": ["@astrojs/compiler@3.0.1", "", {}, "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA=="], @@ -1371,6 +1403,8 @@ "@aws-sdk/checksums": ["@aws-sdk/checksums@3.1000.8", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.974.23", "@aws-sdk/types": "^3.973.13", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-v0U9S7gBIme3OTgt1LdbAF4RpvavCc+4GK1+1xqAcqtbrHsEhjQo6R45LKcjhs/+WrRJij1Y0Gztw7QPAIeUfA=="], + "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/eventstream-handler-node": "^3.972.16", "@aws-sdk/middleware-eventstream": "^3.972.12", "@aws-sdk/middleware-websocket": "^3.972.19", "@aws-sdk/token-providers": "3.1048.0", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ=="], + "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1075.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.23", "@aws-sdk/credential-provider-node": "^3.972.58", "@aws-sdk/middleware-flexible-checksums": "^3.974.33", "@aws-sdk/middleware-sdk-s3": "^3.972.54", "@aws-sdk/signature-v4-multi-region": "^3.996.35", "@aws-sdk/types": "^3.973.13", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-h1A6nIl1YX6Y45enGsTK7ef3ZrOnBiQJ1qF5R2K/nMWfsu6A9mc2Y5T66nxerABzyjjyyvign3MrzafnFoQKmA=="], "@aws-sdk/client-sqs": ["@aws-sdk/client-sqs@3.1075.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.23", "@aws-sdk/credential-provider-node": "^3.972.58", "@aws-sdk/middleware-sdk-sqs": "^3.972.31", "@aws-sdk/types": "^3.973.13", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZHowzLHFTIz482Z98Y8GtszLN7Q1YvC74xxExwr2rbKJeNWTXHVoMVwlq/jv5Kyf0elVAGmoID12Gf6aSdWy0Q=="], @@ -1393,17 +1427,23 @@ "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.55", "", { "dependencies": { "@aws-sdk/core": "^3.974.23", "@aws-sdk/nested-clients": "^3.997.23", "@aws-sdk/types": "^3.973.13", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-g2BoECD1q01kTPByi56+VLVvdWDzMkKIcr77qixpqH0okw2t0U5CoPv+6S8v/D1Y2Wa6QKKtn6XAtDzP+Kfpvg=="], + "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.34", "", { "dependencies": { "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-cTeVzpu1xEAkryTZBYhGwnQ6gOGyp8ZYZvmn0Sg/nI/ABmy/CRHHxPDJDUi9PxwxUtGGaatvfRUB3FCgT/rSWw=="], + + "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.29", "", { "dependencies": { "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-dlRzHCgyB8W6hLuDC5pcT5q+ziPt00n4QGgGBE17ucLVU4zMa6lsbuUdQ2Pm75Z5VA8GF+R/+SgrRcaTdIzSIQ=="], + "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.33", "", { "dependencies": { "@aws-sdk/checksums": "^3.1000.8", "tslib": "^2.6.2" } }, "sha512-qMgQSPemQq2/eW/e/0+SpY4kYR5L7dUgBiVdEc5bd+ztHNv07ZMYiI+sTiir3TgKndFfglSw/VFi7oZJ6bZ63g=="], "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.54", "", { "dependencies": { "@aws-sdk/core": "^3.974.23", "@aws-sdk/signature-v4-multi-region": "^3.996.35", "@aws-sdk/types": "^3.973.13", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-GDfDQ0gwLFRKN9gWIKcmVrHJ3e7XagnY7N1LLzMVNgnOnuY7f/ALgmy3CuBjosWD95T/Z6e+gs1IeWmLPkyLKQ=="], "@aws-sdk/middleware-sdk-sqs": ["@aws-sdk/middleware-sdk-sqs@3.972.31", "", { "dependencies": { "@aws-sdk/types": "^3.973.13", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-56ifsBmK9bLn5EE/t6c0nmjOB1BO8cJDLkA1VOlsN1GR85ROqnaCwVDspqcwsLaBDgPlwyYNedoDIoT3t6Ho1A=="], + "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.52", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-vsPPM+nMbKJlUCFU+eoGZbdxdxDIAX9LbpjSXaR5Ufpmqgp8TdYQnoExhLu4T3umW/JIIPny1ydbhWidZZYokQ=="], + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.23", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.23", "@aws-sdk/signature-v4-multi-region": "^3.996.35", "@aws-sdk/types": "^3.973.13", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-gO93ZPsI2bxeFZD42f1/qjDw6FAZkNZcKRO94LIiT03fzOmcJ9e/tunxjVjA1Rl69ClmVJzz8H3G9CdKef10PA=="], "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.35", "", { "dependencies": { "@aws-sdk/types": "^3.973.13", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-6L/VWs+Wch2stHemCGTmUNqKLMzURxQDK5boNG3Jn3kAOp71meDUuS5sbObpEvFxHDq0uWeSLFDNSYsjNt+Dlg=="], - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1074.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.23", "@aws-sdk/nested-clients": "^3.997.23", "@aws-sdk/types": "^3.973.13", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-pv80IzgGW4RnXWtft692chZOM9i6PhebVsLCcnaM4dBEPZva2fE6FXAHs76G7Rc7s3yGyX/68G0nZMrUy+Vmpg=="], + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1048.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA=="], "@aws-sdk/types": ["@aws-sdk/types@3.973.13", "", { "dependencies": { "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-pEHZqRkAlHfnfAU9tK+WpKv/gBNjGJrHMgA3A0iYRGyswBS2t0pfez+lWlwktb3Bqa0ovh7w/QJTFwp3fDxLNg=="], @@ -1611,6 +1651,18 @@ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + "@earendil-works/chord": ["@earendil-works/chord@0.85.1", "", { "dependencies": { "esbuild": "0.28.1" } }, "sha512-VDlkEC3dhCzQ5fcyH1OhG19dq+6jCn+rqc/iXFivwDYGR5anwo2RCiXij9PpHhqNR5GuhhE+Er69Zi1Sn4eY6w=="], + + "@earendil-works/pi-agent-core": ["@earendil-works/pi-agent-core@0.85.1", "", { "dependencies": { "@earendil-works/chord": "^0.85.1", "@earendil-works/pi-ai": "^0.85.1", "@earendil-works/pi-telemetry": "^0.85.1", "diff": "8.0.4", "ignore": "7.0.5", "typebox": "1.3.7", "yaml": "2.9.0" } }, "sha512-hIXIP3eAWueAYiAl8aMvWCvvZ8Q5gT3Dip5bE5uJyIGh4+YlWRjtMLI4BaeoXoSs93zndjue61u1B/vhefLnuA=="], + + "@earendil-works/pi-ai": ["@earendil-works/pi-ai@0.85.1", "", { "dependencies": { "@anthropic-ai/sdk": "0.123.0", "@aws-sdk/client-bedrock-runtime": "3.1048.0", "@earendil-works/pi-telemetry": "^0.85.1", "@google/genai": "1.52.0", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", "openai": "6.40.0", "partial-json": "0.1.7", "typebox": "1.3.7" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-+VgVIJDkDO2efYJKEEqvPTH4zmnIaXdAppGbO+vKFA9qy5PdhFiAenuFAkU+oiCSfOC4dMHDyrjdQeL4ZoC5CQ=="], + + "@earendil-works/pi-coding-agent": ["@earendil-works/pi-coding-agent@0.85.1", "", { "dependencies": { "@earendil-works/chord": "^0.85.1", "@earendil-works/pi-agent-core": "^0.85.1", "@earendil-works/pi-ai": "^0.85.1", "@earendil-works/pi-tui": "^0.85.1", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", "diff": "8.0.4", "grok-mermaid": "0.2.2", "highlight.js": "10.7.3", "hosted-git-info": "9.0.3", "ignore": "7.0.5", "jiti": "2.7.0", "minimatch": "10.2.5", "proper-lockfile": "4.1.2", "semver": "7.8.0", "typebox": "1.3.7", "undici": "8.9.0", "yaml": "2.9.0" }, "optionalDependencies": { "@mariozechner/clipboard": "0.3.9" }, "bin": { "pi": "dist/bundle/cli.js" } }, "sha512-FGRN+OHbWaefBPGaTggAdLjrIHW+s2PzLyglz/5dfLzb9of7uuXMXYC0fJIeZTw+shS32o2cuQ9jF7YSDuL/oQ=="], + + "@earendil-works/pi-telemetry": ["@earendil-works/pi-telemetry@0.85.1", "", {}, "sha512-Bg/YN6kA7Swja/NQxka8xFdecb4E/auIEGF2G5A25EaQXhRnPj300/7/KpgsDDMYUzHTDAv4RyUxaQPJKW81Rw=="], + + "@earendil-works/pi-tui": ["@earendil-works/pi-tui@0.85.1", "", { "dependencies": { "get-east-asian-width": "1.6.0", "marked": "18.0.5" } }, "sha512-OIzw9efInmO4WOBnD4TxcTdBjmzvYJpzslkgoUro946nEGoYWg5rwv1p4fDt3/JvMx9QybryUCUwlm7j8Dreig=="], + "@effect/atom-react": ["@effect/atom-react@4.0.0-beta.59", "", { "peerDependencies": { "effect": "^4.0.0-beta.59", "react": "^19.2.4", "scheduler": "*" } }, "sha512-VkznQz5c+Z/BLxX+hQNPzPOyUnLQjnbppFSNP7tbPru7HKR4ihzCDC6Xjbx87156MOrZ+JOa6shTMbmvGT5W0w=="], "@effect/language-service": ["@effect/language-service@0.85.1", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-EXnJjIy6zQ3nUO/MZ+ynWUb8B895KZPotd1++oTs9JjDkplwM7cb6zo8Zq2zU6piwq+KflO7amXbEfj1UMpHkw=="], @@ -1813,6 +1865,8 @@ "@executor-js/onboarding-demo": ["@executor-js/onboarding-demo@workspace:packages/onboarding-demo"], + "@executor-js/pi": ["@executor-js/pi@workspace:packages/hosts/pi"], + "@executor-js/plugin-desktop-settings": ["@executor-js/plugin-desktop-settings@workspace:packages/plugins/desktop-settings"], "@executor-js/plugin-encrypted-secrets": ["@executor-js/plugin-encrypted-secrets@workspace:packages/plugins/encrypted-secrets"], @@ -1869,6 +1923,8 @@ "@giscus/react": ["@giscus/react@3.1.0", "", { "dependencies": { "giscus": "^1.6.0" }, "peerDependencies": { "react": "^16 || ^17 || ^18 || ^19", "react-dom": "^16 || ^17 || ^18 || ^19" } }, "sha512-0TCO2TvL43+oOdyVVGHDItwxD1UMKP2ZYpT6gXmhFOqfAJtZxTzJ9hkn34iAF/b6YzyJ4Um89QIt9z/ajmAEeg=="], + "@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="], + "@graphql-tools/executor": ["@graphql-tools/executor@1.5.3", "", { "dependencies": { "@graphql-tools/utils": "^11.1.0", "@graphql-typed-document-node/core": "^3.2.0", "@repeaterjs/repeater": "^3.0.4", "@whatwg-node/disposablestack": "^0.0.6", "@whatwg-node/promise-helpers": "^1.0.0", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mgBFC0bsrZPZLu9EnydpMnAuQ8Iiq0CEbUcsmvXsm2/iYektGHDN/+bmb7hicA6dWZtdPfklYJmr21WD0GnOfA=="], "@graphql-tools/merge": ["@graphql-tools/merge@9.1.9", "", { "dependencies": { "@graphql-tools/utils": "^11.1.0", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-iHUWNjRHeQRYdgIMIuChThOwoKzA9vrzYeslgfBo5eUYEyHGZCoDPjAavssoYXLwstYt1dZj2J22jSzc2DrN0Q=="], @@ -2125,6 +2181,28 @@ "@manypkg/get-packages": ["@manypkg/get-packages@1.1.3", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@changesets/types": "^4.0.1", "@manypkg/find-root": "^1.1.0", "fs-extra": "^8.1.0", "globby": "^11.0.0", "read-yaml-file": "^1.1.0" } }, "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A=="], + "@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.9", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.9", "@mariozechner/clipboard-darwin-universal": "0.3.9", "@mariozechner/clipboard-darwin-x64": "0.3.9", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-musl": "0.3.9", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" } }, "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA=="], + + "@mariozechner/clipboard-darwin-arm64": ["@mariozechner/clipboard-darwin-arm64@0.3.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ=="], + + "@mariozechner/clipboard-darwin-universal": ["@mariozechner/clipboard-darwin-universal@0.3.9", "", { "os": "darwin" }, "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ=="], + + "@mariozechner/clipboard-darwin-x64": ["@mariozechner/clipboard-darwin-x64@0.3.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg=="], + + "@mariozechner/clipboard-linux-arm64-gnu": ["@mariozechner/clipboard-linux-arm64-gnu@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw=="], + + "@mariozechner/clipboard-linux-arm64-musl": ["@mariozechner/clipboard-linux-arm64-musl@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ=="], + + "@mariozechner/clipboard-linux-riscv64-gnu": ["@mariozechner/clipboard-linux-riscv64-gnu@0.3.9", "", { "os": "linux", "cpu": "none" }, "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw=="], + + "@mariozechner/clipboard-linux-x64-gnu": ["@mariozechner/clipboard-linux-x64-gnu@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw=="], + + "@mariozechner/clipboard-linux-x64-musl": ["@mariozechner/clipboard-linux-x64-musl@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ=="], + + "@mariozechner/clipboard-win32-arm64-msvc": ["@mariozechner/clipboard-win32-arm64-msvc@0.3.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ=="], + + "@mariozechner/clipboard-win32-x64-msvc": ["@mariozechner/clipboard-win32-x64-msvc@0.3.9", "", { "os": "win32", "cpu": "x64" }, "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA=="], + "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="], @@ -2907,6 +2985,8 @@ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + "@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="], + "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], @@ -2925,7 +3005,7 @@ "@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.8.2", "", { "dependencies": { "@smithy/core": "^3.26.0", "@smithy/types": "^4.15.0", "tslib": "^2.6.2" } }, "sha512-wfl1uwrAqMH9/pi4kqBo5LBcFwrJLxuDLqL7p7qNcJIFcyZDUc6pzhYk4CYv+DP7fIUpQCZumwNnkhPKS52osQ=="], + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA=="], "@smithy/signature-v4": ["@smithy/signature-v4@5.5.2", "", { "dependencies": { "@smithy/core": "^3.26.0", "@smithy/types": "^4.15.0", "tslib": "^2.6.2" } }, "sha512-7xHpmPY4rt0IOmeAA8EfjgEH8isT+587TCdy9H6a7d4OMi5CQ0oEHhWllunvPu4j4Cq0vTFwdxXN/kABWPjdyA=="], @@ -4121,7 +4201,7 @@ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], @@ -4177,6 +4257,8 @@ "graphql-yoga": ["graphql-yoga@5.21.0", "", { "dependencies": { "@envelop/core": "^5.5.1", "@envelop/instrumentation": "^1.0.0", "@graphql-tools/executor": "^1.5.0", "@graphql-tools/schema": "^10.0.11", "@graphql-tools/utils": "^10.11.0", "@graphql-yoga/logger": "^2.0.1", "@graphql-yoga/subscription": "^5.0.5", "@whatwg-node/fetch": "^0.10.6", "@whatwg-node/promise-helpers": "^1.3.2", "@whatwg-node/server": "^0.10.14", "lru-cache": "^10.0.0", "tslib": "^2.8.1" }, "peerDependencies": { "graphql": "^15.2.0 || ^16.0.0" } }, "sha512-PS37UoDihx8209RRl1ogttzWevNYDnGvP7beHkwHzUpUdfZTHsVRTVe1ysGXre1EjwUAePbpez302YSrq70Ngw=="], + "grok-mermaid": ["grok-mermaid@0.2.2", "", {}, "sha512-XcJEP5dDC8liHBh52mlLjU18fNvu1ckFsu0QpIG3+APZ270fsj9wxpiA6cOURmbUEuoMVgjbC2+UYgTdCqqgzA=="], + "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], "h3": ["h3@1.15.11", "", { "dependencies": { "cookie-es": "^1.2.3", "crossws": "^0.3.5", "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="], @@ -4229,11 +4311,13 @@ "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], + "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + "hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="], "hono": ["hono@4.12.14", "", {}, "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w=="], - "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + "hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="], "html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="], @@ -4439,6 +4523,8 @@ "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], + "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], @@ -4871,6 +4957,8 @@ "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "openai": ["openai@6.40.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"] }, "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA=="], + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], "openid-client": ["openid-client@6.8.4", "", { "dependencies": { "jose": "^6.2.2", "oauth4webapi": "^3.8.5" } }, "sha512-QSw0BA08piujetEwfZsHoTrDpMEha7GDZDicQqVwX4u0ChCjefvjDB++TZ8BTg76UpwhzIQgdvvfgfl3HpCSAw=="], @@ -4939,6 +5027,8 @@ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + "partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="], + "partyserver": ["partyserver@0.5.10", "", { "dependencies": { "nanoid": "^5.1.9" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260424.1 || ^5.20260703.1" } }, "sha512-t2B3mhTL1IOxCsj8rZgn4TxTuub7oLhypjc1/fGPNsbgnn9OsHms6uR3QEd5bFJ/7xrFo771j3DdUlll8cxgBg=="], "partysocket": ["partysocket@1.3.0", "", { "dependencies": { "event-target-polyfill": "^0.0.4" }, "peerDependencies": { "react": ">=17" }, "optionalPeers": ["react"] }, "sha512-1zToNyolZFK/7nuAw/K2bZrNzFqaZyRoCEkS+9vG6WSC5ikrN6qWRe96q6ImU51uptz2r+dAwSkwhJVdQi4LiA=="], @@ -5291,7 +5381,7 @@ "retext-stringify": ["retext-stringify@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unified": "^11.0.0" } }, "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA=="], - "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], @@ -5609,6 +5699,8 @@ "truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "^1.0.1" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="], + "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], + "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], @@ -5635,6 +5727,8 @@ "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], + "typebox": ["typebox@1.3.28", "", {}, "sha512-5hTNlwzKj7BPPqhT91TPa4fK9bh/ZOei7mzweMpNidwLTxtduXHgr/OAbwx4mRW8aKFYu1I8fT1+0+PQtst1/g=="], + "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], "typeorm": ["typeorm@0.3.29", "", { "dependencies": { "@sqltools/formatter": "^1.2.5", "ansis": "^4.2.0", "app-root-path": "^3.1.0", "buffer": "^6.0.3", "dayjs": "^1.11.20", "debug": "^4.4.3", "dedent": "^1.7.2", "dotenv": "^16.6.1", "glob": "^10.5.0", "reflect-metadata": "^0.2.2", "sha.js": "^2.4.12", "sql-highlight": "^6.1.0", "tslib": "^2.8.1", "uuid": "^11.1.1", "yargs": "^17.7.2" }, "peerDependencies": { "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@sap/hana-client": "^2.14.22", "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0", "ioredis": "^5.0.4", "mongodb": "^5.8.0 || ^6.0.0", "mssql": "^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0", "mysql2": "^2.2.5 || ^3.0.1", "oracledb": "^6.3.0", "pg": "^8.5.1", "pg-native": "^3.0.0", "pg-query-stream": "^4.0.0", "redis": "^3.1.1 || ^4.0.0 || ^5.0.14", "sql.js": "^1.4.0", "sqlite3": "^5.0.3", "ts-node": "^10.7.0", "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" }, "optionalPeers": ["@google-cloud/spanner", "@sap/hana-client", "better-sqlite3", "ioredis", "mongodb", "mssql", "mysql2", "oracledb", "pg", "pg-native", "pg-query-stream", "redis", "sql.js", "sqlite3", "ts-node", "typeorm-aurora-data-api-driver"], "bin": { "typeorm": "cli.js", "typeorm-ts-node-esm": "cli-ts-node-esm.js", "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js" } }, "sha512-wwPEX/df4l72gCmOsrs0otJZYLGA9lLQkUZCkukbsymEycV4zXv2KM7wU7v2r8L01TaCgY9ApSSqHQWBOUhEoQ=="], @@ -5869,6 +5963,42 @@ "@astrojs/react/vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="], + "@aws-sdk/client-bedrock-runtime/@smithy/node-http-handler": ["@smithy/node-http-handler@4.8.2", "", { "dependencies": { "@smithy/core": "^3.26.0", "@smithy/types": "^4.15.0", "tslib": "^2.6.2" } }, "sha512-wfl1uwrAqMH9/pi4kqBo5LBcFwrJLxuDLqL7p7qNcJIFcyZDUc6pzhYk4CYv+DP7fIUpQCZumwNnkhPKS52osQ=="], + + "@aws-sdk/client-s3/@smithy/node-http-handler": ["@smithy/node-http-handler@4.8.2", "", { "dependencies": { "@smithy/core": "^3.26.0", "@smithy/types": "^4.15.0", "tslib": "^2.6.2" } }, "sha512-wfl1uwrAqMH9/pi4kqBo5LBcFwrJLxuDLqL7p7qNcJIFcyZDUc6pzhYk4CYv+DP7fIUpQCZumwNnkhPKS52osQ=="], + + "@aws-sdk/client-sqs/@smithy/node-http-handler": ["@smithy/node-http-handler@4.8.2", "", { "dependencies": { "@smithy/core": "^3.26.0", "@smithy/types": "^4.15.0", "tslib": "^2.6.2" } }, "sha512-wfl1uwrAqMH9/pi4kqBo5LBcFwrJLxuDLqL7p7qNcJIFcyZDUc6pzhYk4CYv+DP7fIUpQCZumwNnkhPKS52osQ=="], + + "@aws-sdk/credential-provider-http/@smithy/node-http-handler": ["@smithy/node-http-handler@4.8.2", "", { "dependencies": { "@smithy/core": "^3.26.0", "@smithy/types": "^4.15.0", "tslib": "^2.6.2" } }, "sha512-wfl1uwrAqMH9/pi4kqBo5LBcFwrJLxuDLqL7p7qNcJIFcyZDUc6pzhYk4CYv+DP7fIUpQCZumwNnkhPKS52osQ=="], + + "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1074.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.23", "@aws-sdk/nested-clients": "^3.997.23", "@aws-sdk/types": "^3.973.13", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-pv80IzgGW4RnXWtft692chZOM9i6PhebVsLCcnaM4dBEPZva2fE6FXAHs76G7Rc7s3yGyX/68G0nZMrUy+Vmpg=="], + + "@aws-sdk/eventstream-handler-node/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/eventstream-handler-node/@smithy/core": ["@smithy/core@3.33.3", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg=="], + + "@aws-sdk/eventstream-handler-node/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/middleware-eventstream/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/middleware-eventstream/@smithy/core": ["@smithy/core@3.33.3", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg=="], + + "@aws-sdk/middleware-eventstream/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/middleware-websocket/@aws-sdk/core": ["@aws-sdk/core@3.977.9", "", { "dependencies": { "@aws-sdk/types": "^3.974.5", "@aws-sdk/xml-builder": "^3.972.40", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.33.3", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.17.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-reqPFEQrZxDZpeGj4PFMepBeR5LGYHRqq/L0motTzgFkCRBA4rFdaVXDSLYyGHhxVz7sT2PDnPN9CluGSfgyJA=="], + + "@aws-sdk/middleware-websocket/@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="], + + "@aws-sdk/middleware-websocket/@smithy/core": ["@smithy/core@3.33.3", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg=="], + + "@aws-sdk/middleware-websocket/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.8.0", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA=="], + + "@aws-sdk/middleware-websocket/@smithy/signature-v4": ["@smithy/signature-v4@5.7.3", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow=="], + + "@aws-sdk/middleware-websocket/@smithy/types": ["@smithy/types@4.18.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A=="], + + "@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.8.2", "", { "dependencies": { "@smithy/core": "^3.26.0", "@smithy/types": "^4.15.0", "tslib": "^2.6.2" } }, "sha512-wfl1uwrAqMH9/pi4kqBo5LBcFwrJLxuDLqL7p7qNcJIFcyZDUc6pzhYk4CYv+DP7fIUpQCZumwNnkhPKS52osQ=="], + "@babel/core/@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -5945,6 +6075,24 @@ "@develar/schema-utils/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + "@earendil-works/chord/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + + "@earendil-works/pi-agent-core/typebox": ["typebox@1.3.7", "", {}, "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg=="], + + "@earendil-works/pi-agent-core/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "@earendil-works/pi-ai/typebox": ["typebox@1.3.7", "", {}, "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg=="], + + "@earendil-works/pi-coding-agent/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="], + + "@earendil-works/pi-coding-agent/typebox": ["typebox@1.3.7", "", {}, "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg=="], + + "@earendil-works/pi-coding-agent/undici": ["undici@8.9.0", "", {}, "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA=="], + + "@earendil-works/pi-coding-agent/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "@earendil-works/pi-tui/marked": ["marked@18.0.5", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w=="], + "@effect/platform-node-shared/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], "@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], @@ -6019,6 +6167,8 @@ "@fastify/otel/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.212.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.212.0", "import-in-the-middle": "^2.0.6", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg=="], + "@google/genai/ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], + "@graphql-tools/executor/@graphql-tools/utils": ["@graphql-tools/utils@11.1.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag=="], "@graphql-tools/merge/@graphql-tools/utils": ["@graphql-tools/utils@11.1.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag=="], @@ -6329,6 +6479,8 @@ "@slack/web-api/p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="], + "@slack/web-api/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], @@ -6417,6 +6569,8 @@ "app-builder-lib/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], + "app-builder-lib/hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + "app-builder-lib/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "app-builder-lib/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], @@ -6559,7 +6713,7 @@ "hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "hosted-git-info/lru-cache": ["lru-cache@11.3.5", "", {}, "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw=="], "htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], @@ -6583,6 +6737,8 @@ "ink-confirm-input/ink-text-input": ["ink-text-input@3.3.0", "", { "dependencies": { "chalk": "^3.0.0", "prop-types": "^15.5.10" }, "peerDependencies": { "ink": "^2.0.0", "react": "^16.5.2" } }, "sha512-gO4wrOf2ie3YuEARTIwGlw37lMjFn3Gk6CKIDrMlHb46WFMagZU7DplohjM24zynlqfnXA5UDEIfC2NBcvD8kg=="], + "is-fullwidth-code-point/get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "jake/async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], "jsonwebtoken/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], @@ -6611,6 +6767,8 @@ "mermaid/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + "micromark-extension-cjk-friendly-util/get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "mimetext/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], @@ -6645,6 +6803,8 @@ "p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], @@ -6665,12 +6825,8 @@ "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], - "promise-retry/retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], - "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - "proper-lockfile/retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], - "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], "protobufjs/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], @@ -6729,6 +6885,8 @@ "streamdown/marked": ["marked@17.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA=="], + "string-width/get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "string-width-cjs/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], @@ -6789,6 +6947,10 @@ "@astrojs/react/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], + "@aws-sdk/middleware-websocket/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.40", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA=="], + + "@aws-sdk/middleware-websocket/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="], + "@babel/helper-annotate-as-pure/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], "@babel/helper-annotate-as-pure/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="], @@ -6923,6 +7085,58 @@ "@develar/schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + "@earendil-works/chord/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + + "@earendil-works/chord/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + + "@earendil-works/chord/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + + "@earendil-works/chord/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + + "@earendil-works/chord/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + + "@earendil-works/chord/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + + "@earendil-works/chord/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + + "@earendil-works/chord/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + + "@earendil-works/chord/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + + "@earendil-works/chord/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + + "@earendil-works/chord/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + + "@earendil-works/chord/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + + "@earendil-works/chord/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + + "@earendil-works/chord/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + + "@earendil-works/chord/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + + "@earendil-works/chord/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + + "@earendil-works/chord/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + + "@earendil-works/chord/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + + "@earendil-works/chord/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + + "@earendil-works/chord/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + + "@earendil-works/chord/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + + "@earendil-works/chord/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + + "@earendil-works/chord/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + + "@earendil-works/chord/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + + "@earendil-works/chord/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + + "@earendil-works/chord/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + "@electron/asar/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], "@electron/fuses/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -7075,6 +7289,8 @@ "@opentui/core/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "@opentui/core/string-width/get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "@opentui/core/string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "@opentui/core/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], @@ -7249,6 +7465,8 @@ "app-builder-lib/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + "app-builder-lib/hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "app-builder-lib/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], "astro/@clack/prompts/@clack/core": ["@clack/core@1.2.0", "", { "dependencies": { "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg=="], @@ -7437,8 +7655,6 @@ "har-validator/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - "hosted-git-info/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - "iconv-corefoundation/cli-truncate/slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="], "iconv-corefoundation/cli-truncate/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -7579,6 +7795,8 @@ "wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "wrap-ansi/string-width/get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], @@ -7739,6 +7957,8 @@ "@react-grab/cli/ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "@react-grab/cli/ora/string-width/get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "@react-grab/cli/ora/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "@slack/web-api/axios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], @@ -7749,8 +7969,12 @@ "agents/yargs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "agents/yargs/string-width/get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "agents/yargs/string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "app-builder-lib/hosted-git-info/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "astro/@clack/prompts/fast-string-width/fast-string-truncated-width": ["fast-string-truncated-width@1.2.1", "", {}, "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow=="], "dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -7793,6 +8017,8 @@ "agents/yargs/cliui/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "agents/yargs/cliui/string-width/get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "agents/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "agents/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], diff --git a/e2e/local/pi-extension.test.ts b/e2e/local/pi-extension.test.ts new file mode 100644 index 0000000000..29f084ad29 --- /dev/null +++ b/e2e/local/pi-extension.test.ts @@ -0,0 +1,51 @@ +// The local half of the Pi setup the README documents. The hosted half is +// covered cross-target (scenarios/pi-extension.test.ts) with an API key in +// `EXECUTOR_API_KEY`; a local or desktop Executor has no API keys page, and +// hands out its own bearer token instead — `EXECUTOR_AUTH_TOKEN`. +// +// Nothing about that token is special to the extension, which is the point: the +// only way to know a local user can follow the README is to boot a real +// `executor web`, take the token it prints, and drive the installed extension +// against it exactly as the docs say. +import { expect } from "@effect/vitest"; +import { Effect } from "effect"; + +import { installPi, piToolText } from "../src/clients/pi"; +import { scenario } from "../src/scenario"; +import { Cli, RunDir } from "../src/services"; +import { withLocalServer } from "./local-server"; + +scenario( + "Local · the Pi extension reaches a local Executor with EXECUTOR_AUTH_TOKEN", + // Strictly greater than withLocalServer's 240s boot wait, so a stuck boot + // surfaces its terminal tail instead of vitest's generic timeout. + { timeout: 300_000 }, + Effect.gen(function* () { + const cli = yield* Cli; + const runDir = yield* RunDir; + + yield* withLocalServer(cli, runDir, (server) => + Effect.gen(function* () { + const pi = yield* Effect.promise(() => + installPi({ + EXECUTOR_MCP_URL: new URL("/mcp", server.origin).toString(), + EXECUTOR_AUTH_TOKEN: server.token, + }), + ); + yield* Effect.addFinalizer(() => Effect.promise(() => pi.close())); + + // `/executor` is the check a user runs after following the README. + const [status] = yield* Effect.promise(() => pi.runCommand("/executor")); + expect(status?.type, `the local server accepted the token: ${status?.message}`).toBe( + "info", + ); + expect(status?.message, "it reports the local endpoint").toContain(server.origin); + + const executed = yield* Effect.promise(() => + pi.callTool("executor_execute", { code: "return 21 * 2;" }), + ); + expect(piToolText(executed), "the local sandbox ran the code").toContain("42"); + }).pipe(Effect.scoped), + ); + }), +); diff --git a/e2e/package.json b/e2e/package.json index 6ddd66d3f3..cd6394d6e2 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -22,9 +22,11 @@ "motel": "MOTEL_OTEL_BASE_URL=http://127.0.0.1:4796 MOTEL_OTEL_DB_PATH=runs/.motel/telemetry.sqlite motel server" }, "dependencies": { + "@earendil-works/pi-coding-agent": "^0.85.1", "@executor-js/api": "workspace:*", "@executor-js/emulate": "^0.14.1", "@executor-js/mcporter": "^0.11.4", + "@executor-js/pi": "workspace:*", "@executor-js/plugin-graphql": "workspace:*", "@executor-js/plugin-mcp": "workspace:*", "@executor-js/plugin-openapi": "workspace:*", diff --git a/e2e/scenarios/pi-extension.test.ts b/e2e/scenarios/pi-extension.test.ts new file mode 100644 index 0000000000..6949871c9e --- /dev/null +++ b/e2e/scenarios/pi-extension.test.ts @@ -0,0 +1,83 @@ +// Cross-target: the published Pi extension against a real Executor. Nothing +// here is target-specific — the extension only needs an MCP endpoint and a +// bearer, which is exactly the claim worth testing on every target that serves +// them (cloud's org-scoped /{org}/mcp path included). +// +// The package is packed and installed through Pi's own package manager, then +// discovered, loaded, and wrapped by Pi's own loader (src/clients/pi.ts). So +// what runs is the whole user path — `pi install npm:@executor-js/pi`, the +// `pi` manifest, the default export, `/executor`, Pi's tool wrapper — with only +// the LLM left out: a model picks these tools, it does not make them work. +import { expect } from "@effect/vitest"; +import { Effect } from "effect"; + +import { installPi, piToolText } from "../src/clients/pi"; +import { scenario } from "../src/scenario"; +import { Mcp, Target } from "../src/services"; +import type { Identity } from "../src/target"; + +const emailOf = (identity: Identity): string => identity.credentials?.email ?? identity.label; + +scenario( + "Pi · the installed Executor extension drives execute, skills, and resume over MCP", + { timeout: 300_000 }, + Effect.gen(function* () { + const target = yield* Target; + const mcp = yield* Mcp; + const identity = yield* target.newIdentity(); + const bearer = yield* mcp.mintBearer(emailOf(identity)); + + // The environment a user copies out of Executor's Connect card and API + // Keys page — the extension's only configuration. + const pi = yield* Effect.promise(() => + installPi({ EXECUTOR_MCP_URL: mcp.url, EXECUTOR_API_KEY: bearer }), + ); + yield* Effect.addFinalizer(() => Effect.promise(() => pi.close())); + + expect( + pi.loadedExtensions.some((path) => path.includes("@executor-js/pi")), + "Pi installed the package and loaded its extension from node_modules", + ).toBe(true); + expect(pi.toolNames, "Executor's three tools joined Pi's registry").toEqual( + expect.arrayContaining(["executor_execute", "executor_skills", "executor_resume"]), + ); + + // `/executor` is the one thing a Pi user runs before trusting the install. + const [status] = yield* Effect.promise(() => pi.runCommand("/executor")); + expect(status?.type, "the connection check succeeded").toBe("info"); + expect(status?.message, "it reports the endpoint it resolved").toContain(mcp.url); + expect(status?.message, "pinned to the elicitation mode the resume schema assumes").toContain( + "elicitation_mode=model", + ); + expect(status?.message, "and the tools Executor actually serves").toContain("execute"); + + const executed = yield* Effect.promise(() => + pi.callTool("executor_execute", { code: "return 21 * 2;" }), + ); + expect(piToolText(executed), "the sandbox ran the code and returned it").toContain("42"); + + const documented = yield* Effect.promise(() => + pi.callTool("executor_skills", { name: "execute" }), + ); + expect( + piToolText(documented).length, + "the execute guide comes back for the model to read", + ).toBeGreaterThan(0); + + // Resume needs a paused execution to actually resume, which needs a policy + // that gates a tool — far more setup than this scenario is for. What is + // worth proving here is the part that silently breaks: that the server + // ACCEPTS the arguments this package advertises. A schema mismatch fails + // as an MCP argument-validation error; an accepted call fails on the + // unknown id instead, which is what we assert. + const refused = yield* Effect.promise(() => + pi.callTool("executor_resume", { executionId: "exec_does_not_exist", action: "accept" }).then( + () => "resolved", + (error: unknown) => (error instanceof Error ? error.message : String(error)), + ), + ); + expect(refused, "the server took the arguments and answered about the id").toContain( + "exec_does_not_exist", + ); + }).pipe(Effect.scoped), +); diff --git a/e2e/src/clients/pi.ts b/e2e/src/clients/pi.ts new file mode 100644 index 0000000000..8e25b76bb7 --- /dev/null +++ b/e2e/src/clients/pi.ts @@ -0,0 +1,244 @@ +// Drive the REAL Pi installation path for @executor-js/pi: pack the package the +// way the release does, hand the tarball to Pi's own package manager (what +// `pi install npm:…` runs), and let Pi discover, load, and wrap the extension +// itself. Everything past the `installAndPersist` call is Pi's code — npm +// install, the `pi.extensions` manifest, the jiti loader, the default-export +// call, the tool wrapper, the slash-command dispatcher. +// +// Pi needs an LLM provider only to run a turn. Installing, loading, registering +// tools, and dispatching `/executor` need none, so this stays headless: no +// model is configured, and the tools are invoked the way Pi's agent loop +// invokes them (AgentTool.execute) instead of through a model. +import { execFileSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { + createAgentSession, + DefaultPackageManager, + DefaultResourceLoader, + SessionManager, + SettingsManager, + type AgentSession, + type ExtensionUIContext, +} from "@earendil-works/pi-coding-agent"; + +/** Pi's wrapped tool and its result, named off the session so this file needs + * no direct dependency on pi-agent-core. */ +type PiTool = NonNullable[number]; +type PiToolResult = Awaited>; + +const EXTENSION_PACKAGE_DIR = fileURLToPath( + new URL("../../../packages/hosts/pi/", import.meta.url), +); + +/** A message the extension pushed to Pi's UI (what `/executor` prints). */ +export interface PiNotice { + readonly message: string; + readonly type: "info" | "warning" | "error"; +} + +export interface PiHome { + /** Absolute paths of the extension files Pi loaded, from Pi's own loader. */ + readonly loadedExtensions: readonly string[]; + /** Every tool in Pi's registry after the extension registered its own. */ + readonly toolNames: readonly string[]; + /** Invoke a tool through Pi's wrapper, as Pi's agent loop does. */ + readonly callTool: ( + name: string, + params: Record, + signal?: AbortSignal, + ) => Promise; + /** Type a slash command into Pi and collect what the extension notified. */ + readonly runCommand: (text: string) => Promise; + /** Quit the session the way Pi does, so `session_shutdown` handlers run. */ + readonly close: () => Promise; +} + +/** The text a model would read off a tool result, images and the rest dropped. */ +export const piToolText = (result: PiToolResult): string => + result.content + .filter((part): part is Extract => part.type === "text") + .map((part) => part.text) + .join("\n"); + +/** + * The package's manifest, minus `devDependencies`. + * + * Packing outside the workspace means no lockfile, and the dev block's + * `catalog:` / `workspace:*` specifiers can only be resolved from one. Dropping + * it costs nothing that this test could observe — npm never installs a + * dependency's dev deps — while `dependencies` and `peerDependencies`, the two + * blocks npm acts on, are plain ranges and pass through untouched. Should a + * `workspace:*` ever land in one of those, `bun pm pack` fails here loudly + * rather than shipping something the tarball can't install. + */ +const stagedManifest = (): string => { + const manifest = JSON.parse( + readFileSync(join(EXTENSION_PACKAGE_DIR, "package.json"), "utf8"), + ) as Record; + delete manifest.devDependencies; + return `${JSON.stringify(manifest, null, 2)}\n`; +}; + +/** + * Build and pack the extension into a tarball, without touching the workspace. + * + * `bun run build` is deliberately NOT used: tsup runs with `clean: true`, so a + * second target packaging the same package concurrently (vitest runs projects + * in parallel) would have its `dist/` deleted mid-pack. Both compilers run with + * the package as cwd — that is where their configs and dependencies are — but + * emit into this run's own staging dir, which nothing else can reach. + * + * What lands in the archive is `files` applied to that build, i.e. the same + * artifact npm would serve. The release's `publishConfig.exports` rewrite is + * absent and irrelevant: Pi resolves the extension by path (`pi.extensions`), + * and the published `exports` map is what `scripts/smoke-test-packed.ts` covers. + */ +const packExtension = (destination: string): string => { + const staging = join(destination, "package"); + const dist = join(staging, "dist"); + mkdirSync(staging, { recursive: true }); + writeFileSync(join(staging, "package.json"), stagedManifest()); + + const run = (command: string, args: readonly string[]): void => { + execFileSync(join(EXTENSION_PACKAGE_DIR, "node_modules", ".bin", command), args, { + cwd: EXTENSION_PACKAGE_DIR, + stdio: "pipe", + }); + }; + run("tsup", ["--out-dir", dist]); + run("tsc", ["-p", "tsconfig.build.json", "--outDir", join(dist, "types")]); + + execFileSync("bun", ["pm", "pack", "--destination", destination], { + cwd: staging, + stdio: "pipe", + }); + const tarball = readdirSync(destination).find((entry) => entry.endsWith(".tgz")); + if (!tarball) throw new Error(`bun pm pack produced no tarball in ${destination}`); + return join(destination, tarball); +}; + +/** + * The extension reads its endpoint and token from the environment, and it runs + * in this process — so the environment IS this process's. Set before load, + * restored on close. + */ +const withEnv = (env: Record): (() => void) => { + const previous = new Map(Object.keys(env).map((key) => [key, process.env[key]])); + for (const [key, value] of Object.entries(env)) process.env[key] = value; + return () => { + for (const [key, value] of previous) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + }; +}; + +/** + * Install the packed extension into a throwaway Pi and start a session with it. + * + * `installAndPersist` writes `npm:@executor-js/pi@…` into the agent dir's + * settings.json and npm-installs the tarball under it — the same two steps + * `pi install npm:@executor-js/pi` performs, against the same code. + */ +export const installPi = async (env: Record): Promise => { + const root = mkdtempSync(join(tmpdir(), "e2e-pi-")); + // Until the handle exists there is nothing for the scenario to hang a + // finalizer on, so everything past the temp dir's creation — the build, the + // pack, the install — undoes its own state on the way out. `restoreEnv` is + // only defined once the environment has actually been touched. + let restoreEnv: (() => void) | undefined; + try { + const tarball = packExtension(root); + restoreEnv = withEnv(env); + return await startPi(root, tarball, restoreEnv); + } catch (error) { + restoreEnv?.(); + rmSync(root, { recursive: true, force: true }); + throw error; + } +}; + +const startPi = async (root: string, tarball: string, restoreEnv: () => void): Promise => { + const agentDir = join(root, "agent"); + const projectDir = join(root, "project"); + for (const dir of [agentDir, projectDir]) mkdirSync(dir, { recursive: true }); + + const settingsManager = SettingsManager.create(projectDir, agentDir); + const packageManager = new DefaultPackageManager({ cwd: projectDir, agentDir, settingsManager }); + await packageManager.installAndPersist(`npm:@executor-js/pi@file:${tarball}`); + + // Pi's own resource discovery: read the installed packages out of settings, + // follow each package's `pi` manifest, load what it names. + const resourceLoader = new DefaultResourceLoader({ cwd: projectDir, agentDir, settingsManager }); + await resourceLoader.reload(); + const extensions = resourceLoader.getExtensions(); + if (extensions.errors.length > 0) { + throw new Error( + `Pi failed to load the extension: ${extensions.errors + .map((failure) => `${failure.path}: ${failure.error}`) + .join("; ")}`, + ); + } + + const { session } = await createAgentSession({ + cwd: projectDir, + agentDir, + settingsManager, + resourceLoader, + sessionManager: SessionManager.inMemory(projectDir), + }); + + const notices: PiNotice[] = []; + // Headless: there is no terminal to draw dialogs into, and the extension only + // ever notifies. Anything interactive would be a bug in the extension, so it + // is left off this context rather than stubbed into silence. + const ui: Pick = { + notify: (message, type) => notices.push({ message, type: type ?? "info" }), + }; + await session.bindExtensions({ uiContext: ui as ExtensionUIContext, mode: "print" }); + + const toolByName = (name: string): PiTool => { + const tool = session.state.tools?.find((candidate) => candidate.name === name); + if (!tool) throw new Error(`Pi has no tool named ${name}`); + return tool; + }; + + let calls = 0; + let closed = false; + + return { + loadedExtensions: extensions.extensions.map((extension) => extension.path), + toolNames: session.getAllTools().map((tool) => tool.name), + callTool: (name, params, signal) => { + calls += 1; + return toolByName(name).execute(`e2e-call-${calls}`, params, signal); + }, + runCommand: async (text) => { + const before = notices.length; + await session.prompt(text); + return notices.slice(before); + }, + // Idempotent, and each step cleans up after the one before it fails: a + // shutdown handler that throws must not leave this process holding the + // scenario's credentials or a stray Pi installation on disk. + close: async () => { + if (closed) return; + closed = true; + try { + try { + // What Pi emits on quit — the extension closes its MCP session here. + await session.extensionRunner.emit({ type: "session_shutdown", reason: "quit" }); + } finally { + session.dispose(); + } + } finally { + restoreEnv(); + rmSync(root, { recursive: true, force: true }); + } + }, + }; +}; diff --git a/package.json b/package.json index c284f45623..8c71a82fe7 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "test": "turbo run test --filter=!@executor-js/e2e ${TURBO_TEST_CONCURRENCY:+--concurrency=$TURBO_TEST_CONCURRENCY}", "test:e2e": "bun run --cwd e2e test", "test:release:bootstrap": "vitest run tests/release-bootstrap-smoke.test.ts", - "build:packages": "bun run --filter='@executor-js/fumadb' build && bun run --filter='@executor-js/codemode-core' build && bun run --filter='@executor-js/runtime-quickjs' build && bun run --filter='@executor-js/sdk' build && bun run --filter='@executor-js/config' build && bun run --filter='@executor-js/execution' build && bun run --filter='@executor-js/cli' build && bun run --filter='@executor-js/plugin-*' build", + "build:packages": "bun run --filter='@executor-js/fumadb' build && bun run --filter='@executor-js/codemode-core' build && bun run --filter='@executor-js/runtime-quickjs' build && bun run --filter='@executor-js/sdk' build && bun run --filter='@executor-js/config' build && bun run --filter='@executor-js/execution' build && bun run --filter='@executor-js/cli' build && bun run --filter='@executor-js/plugin-*' build && bun run --filter='@executor-js/pi' build", "typecheck": "turbo run typecheck", "typecheck:slow": "turbo run typecheck:slow", "ci": "bun run lint && bun run typecheck && bun run test", diff --git a/packages/hosts/pi/CHANGELOG.md b/packages/hosts/pi/CHANGELOG.md new file mode 100644 index 0000000000..bcc814438a --- /dev/null +++ b/packages/hosts/pi/CHANGELOG.md @@ -0,0 +1 @@ +# @executor-js/pi diff --git a/packages/hosts/pi/package.json b/packages/hosts/pi/package.json new file mode 100644 index 0000000000..59c8e449ea --- /dev/null +++ b/packages/hosts/pi/package.json @@ -0,0 +1,81 @@ +{ + "name": "@executor-js/pi", + "version": "0.0.0", + "description": "Executor extension for the Pi coding agent: Pi's tools, backed by your Executor catalog.", + "keywords": [ + "agent", + "executor", + "extension", + "mcp", + "pi", + "pi-coding-agent", + "pi-package" + ], + "homepage": "https://github.com/UsefulSoftwareCo/executor/tree/main/packages/hosts/pi", + "bugs": { + "url": "https://github.com/UsefulSoftwareCo/executor/issues" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/UsefulSoftwareCo/executor.git", + "directory": "packages/hosts/pi" + }, + "files": [ + "dist" + ], + "type": "module", + "exports": { + ".": "./src/extension.ts" + }, + "publishConfig": { + "access": "public", + "exports": { + ".": { + "import": { + "types": "./dist/types/extension.d.ts", + "default": "./dist/extensions/executor.js" + } + } + } + }, + "scripts": { + "build": "tsup && tsc -p tsconfig.build.json", + "typecheck": "tsgo --noEmit", + "test": "vitest run", + "test:watch": "vitest", + "typecheck:slow": "tsc --noEmit" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0" + }, + "devDependencies": { + "@earendil-works/pi-ai": "^0.85.1", + "@earendil-works/pi-coding-agent": "^0.85.1", + "@effect/vitest": "catalog:", + "@executor-js/execution": "workspace:*", + "@executor-js/host-mcp": "workspace:*", + "@executor-js/plugin-mcp": "workspace:*", + "@types/node": "catalog:", + "bun-types": "catalog:", + "effect": "catalog:", + "tsup": "catalog:", + "typebox": "^1.3.7", + "typescript": "catalog:", + "vitest": "catalog:", + "zod": "4.3.6" + }, + "peerDependencies": { + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": "*", + "typebox": "*" + }, + "engines": { + "node": ">=22.19.0" + }, + "pi": { + "extensions": [ + "./dist/extensions" + ] + } +} diff --git a/packages/hosts/pi/src/config.test.ts b/packages/hosts/pi/src/config.test.ts new file mode 100644 index 0000000000..58631f82c5 --- /dev/null +++ b/packages/hosts/pi/src/config.test.ts @@ -0,0 +1,81 @@ +import { describe, expect, it } from "@effect/vitest"; + +import { ENDPOINT_ENV_VAR, resolvePiExecutorConfig } from "./config"; + +const resolve = (env: Record) => resolvePiExecutorConfig(env); + +describe("resolvePiExecutorConfig", () => { + it("reports a missing endpoint instead of throwing, so startup survives it", () => { + const resolved = resolve({}); + expect(resolved.ok).toBe(false); + if (resolved.ok) return; + expect(resolved.reason, "the reason names the variable to set").toContain(ENDPOINT_ENV_VAR); + }); + + it("treats a blank endpoint as unset", () => { + expect(resolve({ [ENDPOINT_ENV_VAR]: " " }).ok).toBe(false); + }); + + it("rejects an endpoint that is not a URL", () => { + const resolved = resolve({ [ENDPOINT_ENV_VAR]: "127.0.0.1:4788/mcp" }); + expect(resolved.ok).toBe(false); + if (resolved.ok) return; + expect(resolved.reason).toContain("not a valid URL"); + }); + + it("pins elicitation_mode=model, because the resume schema depends on it", () => { + const resolved = resolve({ [ENDPOINT_ENV_VAR]: "https://executor.example/acme/mcp" }); + expect(resolved.ok).toBe(true); + if (!resolved.ok) return; + expect(resolved.config.endpoint).toBe( + "https://executor.example/acme/mcp?elicitation_mode=model", + ); + }); + + it("replaces a browser elicitation mode rather than trusting the pasted URL", () => { + const resolved = resolve({ + [ENDPOINT_ENV_VAR]: "https://executor.example/mcp?elicitation_mode=browser&artifacts=false", + }); + expect(resolved.ok).toBe(true); + if (!resolved.ok) return; + const url = new URL(resolved.config.endpoint); + expect(url.searchParams.get("elicitation_mode"), "browser mode is overridden").toBe("model"); + expect(url.searchParams.get("artifacts"), "unrelated options survive").toBe("false"); + }); + + it("prefers a hosted API key over a local server token, like the CLI does", () => { + const resolved = resolve({ + [ENDPOINT_ENV_VAR]: "https://executor.example/mcp", + EXECUTOR_API_KEY: "key_123", + EXECUTOR_AUTH_TOKEN: "token_456", + }); + expect(resolved.ok).toBe(true); + if (!resolved.ok) return; + expect(resolved.config.authorization).toBe("Bearer key_123"); + expect(resolved.config.tokenSource, "and the source is kept, for the rejection hint").toBe( + "EXECUTOR_API_KEY", + ); + }); + + it("falls back to the local server token", () => { + const resolved = resolve({ + [ENDPOINT_ENV_VAR]: "https://executor.example/mcp", + EXECUTOR_AUTH_TOKEN: "token_456", + }); + expect(resolved.ok).toBe(true); + if (!resolved.ok) return; + expect(resolved.config.authorization).toBe("Bearer token_456"); + expect(resolved.config.tokenSource).toBe("EXECUTOR_AUTH_TOKEN"); + }); + + it("carries no header when no token is configured", () => { + const resolved = resolve({ + [ENDPOINT_ENV_VAR]: "http://127.0.0.1:4788/mcp", + EXECUTOR_API_KEY: "", + }); + expect(resolved.ok).toBe(true); + if (!resolved.ok) return; + expect(resolved.config.authorization).toBeNull(); + expect(resolved.config.tokenSource).toBeNull(); + }); +}); diff --git a/packages/hosts/pi/src/config.ts b/packages/hosts/pi/src/config.ts new file mode 100644 index 0000000000..63271aadc1 --- /dev/null +++ b/packages/hosts/pi/src/config.ts @@ -0,0 +1,93 @@ +// --------------------------------------------------------------------------- +// Where this extension points, and how it authenticates. +// +// The environment is the only source. Pi installs this package from npm, so a +// credential in package config would be a credential in every user's +// node_modules; the issue asking for this integration called that out by name. +// --------------------------------------------------------------------------- + +/** Names the Executor MCP endpoint. Copy it from the Connect an agent card. */ +export const ENDPOINT_ENV_VAR = "EXECUTOR_MCP_URL"; + +/** + * Bearer sources, in the CLI's own precedence order (see + * `readCliServerAuth` in apps/cli/src/server-connection.ts): a hosted API key + * beats a local/desktop server token. + */ +export const TOKEN_ENV_VARS = ["EXECUTOR_API_KEY", "EXECUTOR_AUTH_TOKEN"] as const; + +/** Which variable a bearer came from. Hosted keys and local server tokens are + * minted in different places, so a rejected one has a different remedy. */ +export type PiTokenSource = (typeof TOKEN_ENV_VARS)[number]; + +export interface PiExecutorConfig { + /** Endpoint to POST MCP at, already pinned to model elicitation. */ + readonly endpoint: string; + /** Full `Authorization` header value, or null when no token is configured. */ + readonly authorization: string | null; + /** The variable that supplied `authorization`, or null when none did. */ + readonly tokenSource: PiTokenSource | null; +} + +export type ResolvedPiExecutorConfig = + | { readonly ok: true; readonly config: PiExecutorConfig } + | { readonly ok: false; readonly reason: string }; + +/** + * Pin `elicitation_mode=model`, replacing whatever the URL carried. + * + * The `resume` tool this extension registers has the shape Executor serves in + * MODEL mode (`executionId` + `action` + `content`). In browser mode the server + * registers a `resume` taking `executionId` alone, so a pasted + * `?elicitation_mode=browser` URL would leave us advertising two parameters the + * server rejects. Pinning the mode is what makes the fixed schema honest. + * Unrelated query parameters (`artifacts`, `search_tools`, …) are preserved. + */ +const pinModelElicitation = (url: URL): string => { + url.searchParams.set("elicitation_mode", "model"); + return url.toString(); +}; + +const firstToken = ( + env: Record, +): { readonly name: PiTokenSource; readonly value: string } | undefined => { + for (const name of TOKEN_ENV_VARS) { + const value = env[name]?.trim(); + if (value !== undefined && value.length > 0) return { name, value }; + } + return undefined; +}; + +/** + * Resolve configuration without throwing: extension startup must never fail on + * a missing or malformed endpoint. The reason is surfaced later, when a tool + * call or `/executor` actually needs a connection. + */ +export const resolvePiExecutorConfig = ( + env: Record, +): ResolvedPiExecutorConfig => { + const rawEndpoint = env[ENDPOINT_ENV_VAR]?.trim(); + if (rawEndpoint === undefined || rawEndpoint.length === 0) { + return { + ok: false, + reason: `${ENDPOINT_ENV_VAR} is not set. Copy the MCP URL from Executor's "Connect an agent" card, and set ${TOKEN_ENV_VARS[0]} to an API key from Executor's API Keys page — or, for a local or desktop Executor, ${TOKEN_ENV_VARS[1]} to that server's own bearer token.`, + }; + } + + // `URL.parse` returns null instead of throwing (Node >= 22.1; Pi requires + // >= 22.19), which keeps this module free of exception control flow. + const url = URL.parse(rawEndpoint); + if (url === null) { + return { ok: false, reason: `${ENDPOINT_ENV_VAR} is not a valid URL: ${rawEndpoint}` }; + } + + const token = firstToken(env); + return { + ok: true, + config: { + endpoint: pinModelElicitation(url), + authorization: token === undefined ? null : `Bearer ${token.value}`, + tokenSource: token?.name ?? null, + }, + }; +}; diff --git a/packages/hosts/pi/src/connection.ts b/packages/hosts/pi/src/connection.ts new file mode 100644 index 0000000000..e1faa1917d --- /dev/null +++ b/packages/hosts/pi/src/connection.ts @@ -0,0 +1,132 @@ +// --------------------------------------------------------------------------- +// The MCP connection to Executor. +// +// Opened on the first tool call, never at startup: Pi must start cleanly even +// when Executor is unreachable, misconfigured, or simply not running yet. +// --------------------------------------------------------------------------- + +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import { CallToolResultSchema, type CallToolResult } from "@modelcontextprotocol/sdk/types.js"; + +import { + TOKEN_ENV_VARS, + type PiExecutorConfig, + type PiTokenSource, + type ResolvedPiExecutorConfig, +} from "./config"; + +const CLIENT_INFO = { name: "executor-pi", version: "0.1.0" } as const; + +export interface ExecutorConnection { + readonly resolved: ResolvedPiExecutorConfig; + readonly callTool: ( + name: string, + args: Record, + signal: AbortSignal | undefined, + ) => Promise; + /** Tool names Executor currently serves. Used by `/executor` to prove reachability. */ + readonly listToolNames: () => Promise; + readonly close: () => Promise; +} + +/** + * What to do about a credential the server refused. The two variables are + * refilled from different places — a hosted Executor has an API Keys page, a + * local or desktop one hands out its own server token and has no such page — so + * the remedy follows the variable the token actually came from. + */ +const rejectedTokenHint = (source: PiTokenSource): string => + source === TOKEN_ENV_VARS[0] + ? `The API key in ${TOKEN_ENV_VARS[0]} was rejected — mint a fresh one on Executor's API Keys page.` + : `The token in ${TOKEN_ENV_VARS[1]} was rejected — copy your local or desktop server's current token again (the "Connect an agent" card's command carries it).`; + +const describeFailure = (error: unknown, config: PiExecutorConfig): Error => { + const detail = error instanceof Error ? error.message : String(error); + // Executor answers an unauthenticated MCP POST with an OAuth challenge, and + // the SDK surfaces it as `invalid_token` — often with no status code in the + // message at all. Match the vocabulary, not just the number. + const unauthorized = /\b(401|403)\b|unauthorized|forbidden|invalid_token/i.test(detail); + const hint = + config.tokenSource === null + ? `No token is set — put a hosted API key in ${TOKEN_ENV_VARS[0]}, or a local or desktop server's bearer token in ${TOKEN_ENV_VARS[1]}.` + : unauthorized + ? rejectedTokenHint(config.tokenSource) + : "A token was sent."; + return new Error(`Could not reach Executor at ${config.endpoint}. ${hint} (${detail})`); +}; + +export const createExecutorConnection = ( + resolved: ResolvedPiExecutorConfig, +): ExecutorConnection => { + // A single in-flight connect, so parallel tool calls share one MCP session + // rather than racing two. + let pending: Promise | null = null; + + const connect = async (config: PiExecutorConfig): Promise => { + const client = new Client(CLIENT_INFO); + const transport = new StreamableHTTPClientTransport(new URL(config.endpoint), { + requestInit: + config.authorization === null + ? undefined + : { headers: { Authorization: config.authorization } }, + }); + await client.connect(transport); + return client; + }; + + const connected = (): Promise => { + if (!resolved.ok) return Promise.reject(new Error(resolved.reason)); + const config = resolved.config; + if (pending === null) { + pending = connect(config).catch((error: unknown) => { + // A failed connect must not poison every later call: clear the memo so + // the next one retries (Executor may just have been starting up). + pending = null; + throw describeFailure(error, config); + }); + } + return pending; + }; + + /** + * Rejections here are transport or protocol failures, not tool failures — + * a failing Executor tool comes back as a result with `isError`. So a + * rejection means this session is suspect: drop it and let the next call + * reconnect, which is what makes an Executor restart survivable without + * restarting Pi. + */ + const withSession = async (use: (client: Client) => Promise): Promise => { + const client = await connected(); + try { + return await use(client); + } catch (error) { + pending = null; + void client.close().catch(() => undefined); + throw error; + } + }; + + return { + resolved, + callTool: (name, args, signal) => + // `callTool` types its result as the union of the modern shape and the + // 2024-10-07 `{ toolResult }` one, because the overload accepts either + // schema. Passing `CallToolResultSchema` is what actually decides it, so + // the modern branch is the only one that can arrive here. + withSession((client) => + client.callTool({ name, arguments: args }, CallToolResultSchema, { signal }), + ) as Promise, + listToolNames: () => + withSession(async (client) => { + const listed = await client.listTools(); + return listed.tools.map((tool) => tool.name); + }), + close: async () => { + const current = pending; + pending = null; + if (current === null) return; + await current.then((client) => client.close()).catch(() => undefined); + }, + }; +}; diff --git a/packages/hosts/pi/src/extension.test.ts b/packages/hosts/pi/src/extension.test.ts new file mode 100644 index 0000000000..ce9cdc546b --- /dev/null +++ b/packages/hosts/pi/src/extension.test.ts @@ -0,0 +1,316 @@ +// End-to-end for the bridge: a real MCP server over HTTP, the real MCP client, +// and the real tool definitions — only Pi itself is faked. + +import { describe, expect, it } from "@effect/vitest"; +import { Effect } from "effect"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; +import { serveMcpServer, TEST_IMAGE_PNG_BASE64 } from "@executor-js/plugin-mcp/testing"; +import z from "zod"; + +import { resolvePiExecutorConfig } from "./config"; +import { createExecutorConnection, type ExecutorConnection } from "./connection"; +import { describeConnection } from "./extension"; +import { registerExecutorTools } from "./tools"; + +const API_KEY = "key_123"; + +/** A stand-in for Executor: the same three tool names, scripted results. */ +const executorLikeServer = (): McpServer => { + const server = new McpServer({ name: "executor-fake", version: "1.0.0" }); + server.registerTool( + "execute", + { description: "run code", inputSchema: { code: z.string() } }, + ({ code }) => { + if (code === "boom") { + return { + content: [{ type: "text" as const, text: "Error: policy blocked" }], + isError: true, + }; + } + if (code === "screenshot") { + return { + content: [{ type: "image" as const, data: TEST_IMAGE_PNG_BASE64, mimeType: "image/png" }], + }; + } + return { content: [{ type: "text" as const, text: `ran: ${code}` }] }; + }, + ); + server.registerTool( + "skills", + { description: "docs", inputSchema: { name: z.string().optional() } }, + ({ name }) => ({ content: [{ type: "text" as const, text: `doc: ${name ?? "index"}` }] }), + ); + server.registerTool( + "resume", + { + description: "resume", + inputSchema: { + executionId: z.string(), + action: z.enum(["accept", "decline", "cancel"]), + content: z.string().default("{}"), + }, + }, + ({ executionId, action }) => ({ + content: [{ type: "text" as const, text: `${action}ed ${executionId}` }], + }), + ); + return server; +}; + +interface CapturedTool { + readonly name: string; + readonly call: ( + params: Record, + signal?: AbortSignal, + ) => Promise<{ readonly content: unknown }>; +} + +interface FakePi { + readonly api: ExtensionAPI; + readonly tools: Map; + readonly commands: string[]; + readonly events: string[]; +} + +const makeFakePi = (): FakePi => { + const tools = new Map(); + const commands: string[] = []; + const events: string[] = []; + const api: Pick = { + registerTool: (tool) => { + tools.set(tool.name, { + name: tool.name, + call: (params, signal) => + tool.execute("call-1", params as never, signal, undefined, {} as ExtensionContext), + }); + }, + registerCommand: (name) => { + commands.push(name); + }, + on: (event: string) => { + events.push(event); + }, + }; + return { api: api as ExtensionAPI, tools, commands, events }; +}; + +const callTool = ( + pi: FakePi, + name: string, + params: Record, + signal?: AbortSignal, +): Promise<{ readonly content: unknown }> => { + const tool = pi.tools.get(name); + if (tool === undefined) return Promise.reject(new Error(`${name} was never registered`)); + return tool.call(params, signal); +}; + +/** Resolve to the thrown message instead of the value, for failure assertions. */ +const failureOf = (work: Promise): Promise => + work.then( + () => "resolved", + (error: unknown) => (error instanceof Error ? error.message : String(error)), + ); + +/** Everything wired the way `executorExtension` wires it, minus Pi. `source` is + * the variable the bearer arrives in — hosted API key by default, the local + * server's own token when a scenario asks for it. */ +const connect = ( + endpoint: string | undefined, + token = API_KEY, + source: "EXECUTOR_API_KEY" | "EXECUTOR_AUTH_TOKEN" = "EXECUTOR_API_KEY", +): { pi: FakePi; connection: ExecutorConnection } => { + const pi = makeFakePi(); + const connection = createExecutorConnection( + resolvePiExecutorConfig( + endpoint === undefined ? {} : { EXECUTOR_MCP_URL: endpoint, [source]: token }, + ), + ); + registerExecutorTools(pi.api, connection); + return { pi, connection }; +}; + +const authorizedServer = () => + serveMcpServer(executorLikeServer, { + path: "/mcp", + auth: { + validateAuthorization: (authorization) => + Effect.succeed(authorization === `Bearer ${API_KEY}`), + }, + }); + +describe("the Pi extension", () => { + it.effect("registers its tools with no Executor reachable at all", () => + Effect.sync(() => { + const { pi } = connect(undefined); + // The whole point of registering from a literal: Pi still starts. + expect([...pi.tools.keys()]).toEqual([ + "executor_execute", + "executor_skills", + "executor_resume", + ]); + }), + ); + + it.effect("explains what to set when a call happens without configuration", () => + Effect.gen(function* () { + const { pi } = connect(undefined); + const failure = yield* Effect.promise(() => + failureOf(callTool(pi, "executor_execute", { code: "1 + 1" })), + ); + expect(failure).toContain("EXECUTOR_MCP_URL"); + }), + ); + + it.effect("round-trips a tool call over HTTP with the bearer attached", () => + Effect.scoped( + Effect.gen(function* () { + const server = yield* authorizedServer(); + const { pi } = connect(server.endpoint); + + const result = yield* Effect.promise(() => + callTool(pi, "executor_execute", { code: "1 + 1" }), + ); + expect(result.content).toEqual([{ type: "text", text: "ran: 1 + 1" }]); + + const requests = yield* server.requests; + expect( + requests.some((request) => request.authorization === `Bearer ${API_KEY}`), + "the API key reached the server", + ).toBe(true); + expect( + requests.some((request) => request.url.includes("elicitation_mode=model")), + "the endpoint is pinned to model elicitation", + ).toBe(true); + }), + ), + ); + + it.effect("sends a rejected hosted key to the API Keys page", () => + Effect.scoped( + Effect.gen(function* () { + const server = yield* authorizedServer(); + const { pi } = connect(server.endpoint, "wrong-key", "EXECUTOR_API_KEY"); + + const failure = yield* Effect.promise(() => + failureOf(callTool(pi, "executor_execute", { code: "1 + 1" })), + ); + expect(failure, "the failure is the rejection, not a missing token").toContain( + "was rejected", + ); + expect(failure, "names the variable that holds the bad key").toContain("EXECUTOR_API_KEY"); + expect(failure, "and where hosted keys come from").toContain("API Keys page"); + }), + ), + ); + + // A local or desktop Executor has no API Keys page: it hands out its own + // server token, so telling that user to mint an API key is a dead end. + it.effect("sends a rejected local server token back to the server's own token", () => + Effect.scoped( + Effect.gen(function* () { + const server = yield* authorizedServer(); + const { pi } = connect(server.endpoint, "wrong-token", "EXECUTOR_AUTH_TOKEN"); + + const failure = yield* Effect.promise(() => + failureOf(callTool(pi, "executor_execute", { code: "1 + 1" })), + ); + expect(failure, "the failure is the rejection, not a missing token").toContain( + "was rejected", + ); + expect(failure, "names the variable that holds the bad token").toContain( + "EXECUTOR_AUTH_TOKEN", + ); + expect(failure, "and does not send a local user to a page they do not have").not.toContain( + "API Keys page", + ); + }), + ), + ); + + it.effect("turns an isError result into a thrown error carrying the server's text", () => + Effect.scoped( + Effect.gen(function* () { + const server = yield* authorizedServer(); + const { pi } = connect(server.endpoint); + + // Pi has no error flag on results: only a throw marks the call failed. + const failure = yield* Effect.promise(() => + failureOf(callTool(pi, "executor_execute", { code: "boom" })), + ); + expect(failure).toBe("Error: policy blocked"); + }), + ), + ); + + it.effect("hands images to Pi rather than a placeholder", () => + Effect.scoped( + Effect.gen(function* () { + const server = yield* authorizedServer(); + const { pi } = connect(server.endpoint); + + const result = yield* Effect.promise(() => + callTool(pi, "executor_execute", { code: "screenshot" }), + ); + expect(result.content).toEqual([ + { type: "image", data: TEST_IMAGE_PNG_BASE64, mimeType: "image/png" }, + ]); + }), + ), + ); + + it.effect("passes the resume arguments the model-mode schema promises", () => + Effect.scoped( + Effect.gen(function* () { + const server = yield* authorizedServer(); + const { pi } = connect(server.endpoint); + + const result = yield* Effect.promise(() => + callTool(pi, "executor_resume", { executionId: "exec_1", action: "accept" }), + ); + expect(result.content).toEqual([{ type: "text", text: "accepted exec_1" }]); + }), + ), + ); + + it.effect("cancels an in-flight call when Pi aborts the turn", () => + Effect.scoped( + Effect.gen(function* () { + const server = yield* authorizedServer(); + const { pi } = connect(server.endpoint); + + const outcome = yield* Effect.promise(() => + callTool(pi, "executor_execute", { code: "1 + 1" }, AbortSignal.abort()).then( + () => "resolved", + () => "rejected", + ), + ); + expect(outcome).toBe("rejected"); + }), + ), + ); + + it.effect("`/executor` reports the endpoint and the tools it can see", () => + Effect.scoped( + Effect.gen(function* () { + const server = yield* authorizedServer(); + const { connection } = connect(server.endpoint); + + const report = yield* Effect.promise(() => describeConnection(connection)); + expect(report.type).toBe("info"); + expect(report.message).toContain("execute"); + yield* Effect.promise(() => connection.close()); + }), + ), + ); + + it.effect("`/executor` says what is missing when nothing is configured", () => + Effect.gen(function* () { + const { connection } = connect(undefined); + const report = yield* Effect.promise(() => describeConnection(connection)); + expect(report.type).toBe("error"); + expect(report.message).toContain("EXECUTOR_MCP_URL"); + }), + ); +}); diff --git a/packages/hosts/pi/src/extension.ts b/packages/hosts/pi/src/extension.ts new file mode 100644 index 0000000000..da8d069d66 --- /dev/null +++ b/packages/hosts/pi/src/extension.ts @@ -0,0 +1,69 @@ +// --------------------------------------------------------------------------- +// @executor-js/pi — Executor's tools, inside Pi. +// +// Pi ships no MCP client, so this extension is the bridge: it registers +// Executor's small `execute` / `skills` / `resume` surface as Pi tools and +// forwards each call over MCP. Configuration is environment-only; see config.ts. +// --------------------------------------------------------------------------- + +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + +import { ENDPOINT_ENV_VAR, resolvePiExecutorConfig } from "./config"; +import { createExecutorConnection, type ExecutorConnection } from "./connection"; +import { registerExecutorTools } from "./tools"; + +// Pi only needs the default export. The pieces are re-exported so each can be +// tested on its own — resolution, the connection, and the registered schemas — +// without standing up a Pi session for it. +export { + ENDPOINT_ENV_VAR, + TOKEN_ENV_VARS, + resolvePiExecutorConfig, + type PiExecutorConfig, + type PiTokenSource, + type ResolvedPiExecutorConfig, +} from "./config"; +export { createExecutorConnection, type ExecutorConnection } from "./connection"; +export { registerExecutorTools, TOOL_NAME_PREFIX } from "./tools"; + +export interface StatusReport { + readonly message: string; + readonly type: "info" | "error"; +} + +/** What `/executor` prints: where we point, and whether it actually answers. */ +export const describeConnection = async (connection: ExecutorConnection): Promise => { + const resolved = connection.resolved; + if (!resolved.ok) + return { message: `Executor is not configured. ${resolved.reason}`, type: "error" }; + + const auth = resolved.config.authorization === null ? "no token" : "token set"; + const target = `${resolved.config.endpoint} (${auth})`; + try { + const names = await connection.listToolNames(); + return { message: `Executor connected: ${target}\nTools: ${names.join(", ")}`, type: "info" }; + } catch (error) { + const detail = error instanceof Error ? error.message : String(error); + return { message: `Executor is unreachable: ${target}\n${detail}`, type: "error" }; + } +}; + +export default function executorExtension(pi: ExtensionAPI): void { + const connection = createExecutorConnection(resolvePiExecutorConfig(process.env)); + + registerExecutorTools(pi, connection); + + pi.registerCommand("executor", { + description: `Check this session's Executor connection (${ENDPOINT_ENV_VAR}).`, + handler: async (_args, ctx) => { + const report = await describeConnection(connection); + ctx.ui.notify(report.message, report.type); + }, + }); + + // Pi owns the process lifetime; close the session deterministically rather + // than leaving Executor to time it out. + pi.on("session_shutdown", async () => { + await connection.close(); + }); +} diff --git a/packages/hosts/pi/src/result.test.ts b/packages/hosts/pi/src/result.test.ts new file mode 100644 index 0000000000..c90f194287 --- /dev/null +++ b/packages/hosts/pi/src/result.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from "@effect/vitest"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; + +import { resultText, toPiContent } from "./result"; + +const result = (content: CallToolResult["content"]): CallToolResult => ({ content }); + +describe("toPiContent", () => { + it("passes text through", () => { + expect(toPiContent(result([{ type: "text", text: "ran: 1 + 1" }]))).toEqual([ + { type: "text", text: "ran: 1 + 1" }, + ]); + }); + + it("passes images through — Pi renders them", () => { + expect(toPiContent(result([{ type: "image", data: "aGk=", mimeType: "image/png" }]))).toEqual([ + { type: "image", data: "aGk=", mimeType: "image/png" }, + ]); + }); + + it("keeps the text of an inline text resource", () => { + expect( + toPiContent( + result([ + { type: "resource", resource: { uri: "file:///notes.txt", text: "hello from a file" } }, + ]), + ), + ).toEqual([{ type: "text", text: "hello from a file" }]); + }); + + it("names what it dropped for content Pi cannot carry", () => { + const content = toPiContent( + result([ + { type: "audio", data: "aGk=", mimeType: "audio/mpeg" }, + { + type: "resource", + resource: { uri: "file:///clip.bin", mimeType: "application/octet-stream", blob: "aGk=" }, + }, + ]), + ); + // Executor precedes each binary block with its own "File output: …" line, + // so the model still knows what exists; only the bytes are gone. + expect(content).toEqual([ + { + type: "text", + text: "[audio/mpeg audio omitted — Pi tool results carry text and images only]", + }, + { + type: "text", + text: "[resource file:///clip.bin omitted — Pi tool results carry text and images only]", + }, + ]); + }); + + it("never returns empty content", () => { + expect(toPiContent(result([]))).toEqual([ + { type: "text", text: "Executor returned no content." }, + ]); + }); +}); + +describe("resultText", () => { + it("joins the text blocks, which is all a thrown error can carry", () => { + expect( + resultText( + result([ + { type: "text", text: "Error: policy blocked github.createIssue" }, + { type: "image", data: "aGk=", mimeType: "image/png" }, + { type: "text", text: "Ask the user to approve it." }, + ]), + ), + ).toBe("Error: policy blocked github.createIssue\nAsk the user to approve it."); + }); + + it("is empty when there is no text to report", () => { + expect(resultText(result([{ type: "image", data: "aGk=", mimeType: "image/png" }]))).toBe(""); + }); +}); diff --git a/packages/hosts/pi/src/result.ts b/packages/hosts/pi/src/result.ts new file mode 100644 index 0000000000..05962168d0 --- /dev/null +++ b/packages/hosts/pi/src/result.ts @@ -0,0 +1,51 @@ +// --------------------------------------------------------------------------- +// MCP tool results -> what Pi can put in front of a model. +// +// Pi's `AgentToolResult.content` is `(TextContent | ImageContent)[]`. There is +// no audio and no resource block, and — importantly — no error flag: the +// harness marks a call failed exactly when `execute` throws +// (pi-agent-core, harness/execution/tools.js). So an MCP `isError` result has +// to leave here as a thrown error carrying its text in the message, because +// the harness discards the content and keeps only the message. +// --------------------------------------------------------------------------- + +import type { ImageContent, TextContent } from "@earendil-works/pi-ai"; +import type { CallToolResult, ContentBlock } from "@modelcontextprotocol/sdk/types.js"; + +export type PiContent = TextContent | ImageContent; + +const text = (value: string): TextContent => ({ type: "text", text: value }); + +/** + * Executor already precedes every binary block with a `File output: name + * (mime, bytes)` line (see `outputFileContent` in + * packages/hosts/mcp/src/tool-server.ts), so dropping the payload costs the + * bytes and nothing else: the model still knows the file exists and what it is. + */ +const omitted = (what: string): TextContent => + text(`[${what} omitted — Pi tool results carry text and images only]`); + +const blockToPiContent = (block: ContentBlock): PiContent => { + if (block.type === "text") return text(block.text); + if (block.type === "image") return { type: "image", data: block.data, mimeType: block.mimeType }; + if (block.type === "audio") return omitted(`${block.mimeType} audio`); + if (block.type === "resource_link") return omitted(`resource ${block.uri}`); + // Text resources carry their content inline; binary ones carry a blob. + if ("text" in block.resource && typeof block.resource.text === "string") { + return text(block.resource.text); + } + return omitted(`resource ${block.resource.uri}`); +}; + +/** Flatten a result's text for a message the harness will keep. */ +export const resultText = (result: CallToolResult): string => + result.content + .filter((block): block is Extract => block.type === "text") + .map((block) => block.text) + .join("\n") + .trim(); + +export const toPiContent = (result: CallToolResult): PiContent[] => { + const content = result.content.map(blockToPiContent); + return content.length > 0 ? content : [text("Executor returned no content.")]; +}; diff --git a/packages/hosts/pi/src/schema.test.ts b/packages/hosts/pi/src/schema.test.ts new file mode 100644 index 0000000000..6c92eac1cf --- /dev/null +++ b/packages/hosts/pi/src/schema.test.ts @@ -0,0 +1,103 @@ +// --------------------------------------------------------------------------- +// The contract test. +// +// This package hand-writes TypeBox schemas for tools a DIFFERENT package +// serves. Nothing else in CI would notice if Executor renamed a parameter or +// made an optional one required — Pi would just start sending calls the server +// rejects. So: stand up the real Executor MCP server, ask it what it serves, +// and compare against what we register. +// +// Descriptions are deliberately NOT compared. Ours are thin on purpose (the +// long-form guidance lives behind `skills`), so only the call-breaking shape is +// asserted: parameter names, JSON types, enum values, and which are required. +// --------------------------------------------------------------------------- + +import { describe, expect, it } from "@effect/vitest"; +import { Effect } from "effect"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import type { ExecutionEngine } from "@executor-js/execution"; +import { createExecutorMcpServer } from "@executor-js/host-mcp/tool-server"; + +import { EXECUTE_PARAMETERS, RESUME_PARAMETERS, SKILLS_PARAMETERS } from "./tools"; + +/** The narrowest engine `createExecutorMcpServer` will accept. */ +const stubEngine = (): ExecutionEngine => ({ + execute: () => Effect.succeed({ result: "stub" }), + executeWithPause: () => Effect.succeed({ status: "completed", result: { result: "stub" } }), + resume: () => Effect.succeed(null), + isExecutionSettled: undefined, + getPausedExecution: () => Effect.succeed(null), + pausedExecutionCount: () => Effect.succeed(0), + hasPausedExecutions: () => Effect.succeed(false), + getDescription: Effect.succeed("contract-test executor"), + shutdown: Effect.void, +}); + +/** + * Model elicitation is what config.ts pins the endpoint to, and it is what + * makes `resume` take `action`/`content` rather than `executionId` alone. + */ +const withExecutorTools = async (assert: (tools: Map) => void): Promise => { + const server = await Effect.runPromise( + createExecutorMcpServer({ engine: stubEngine(), elicitationMode: { mode: "model" } }), + ); + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + const client = new Client({ name: "pi-contract-test", version: "1.0.0" }); + await server.connect(serverTransport); + await client.connect(clientTransport); + try { + const listed = await client.listTools(); + assert(new Map(listed.tools.map((tool) => [tool.name, tool.inputSchema]))); + } finally { + await clientTransport.close(); + await serverTransport.close(); + } +}; + +type SchemaShape = { + readonly properties: Record; + readonly required: readonly string[]; +}; + +const isRecord = (value: unknown): value is Record => + typeof value === "object" && value !== null; + +/** + * Reduce a JSON Schema to the part a caller can get wrong: each property's + * type (plus enum members, which are equally call-breaking), and the required + * set. TypeBox schemas are JSON Schema at runtime, so both sides go through + * this unchanged. + */ +const shapeOf = (schema: unknown): SchemaShape => { + expect(isRecord(schema), "schema should be an object").toBe(true); + const record = schema as Record; + const properties = isRecord(record.properties) ? record.properties : {}; + const described: Record = {}; + for (const [name, property] of Object.entries(properties)) { + if (!isRecord(property)) continue; + const type = typeof property.type === "string" ? property.type : "unknown"; + described[name] = Array.isArray(property.enum) + ? `${type}(${[...property.enum].sort().join("|")})` + : type; + } + const required = Array.isArray(record.required) + ? [...record.required].filter((name): name is string => typeof name === "string").sort() + : []; + return { properties: described, required }; +}; + +describe("the registered schemas match the tools Executor serves", () => { + it("execute, skills, and resume line up parameter for parameter", async () => { + await withExecutorTools((served) => { + expect( + [...served.keys()], + "Executor should still serve the three tools this package registers", + ).toEqual(expect.arrayContaining(["execute", "skills", "resume"])); + + expect(shapeOf(served.get("execute")), "execute").toEqual(shapeOf(EXECUTE_PARAMETERS)); + expect(shapeOf(served.get("skills")), "skills").toEqual(shapeOf(SKILLS_PARAMETERS)); + expect(shapeOf(served.get("resume")), "resume").toEqual(shapeOf(RESUME_PARAMETERS)); + }); + }); +}); diff --git a/packages/hosts/pi/src/tools.ts b/packages/hosts/pi/src/tools.ts new file mode 100644 index 0000000000..9bd3f33bb1 --- /dev/null +++ b/packages/hosts/pi/src/tools.ts @@ -0,0 +1,144 @@ +// --------------------------------------------------------------------------- +// Executor's core tool surface, as Pi tools. +// +// The set is fixed: `execute`, `skills`, and model-mode `resume` are what +// Executor serves by default, and registering them from a literal (rather than +// mirroring `tools/list` at startup) is what lets Pi start while Executor is +// down. `parameters` mirror the server's own input schemas — schema.test.ts +// asserts that against a real Executor MCP server, so drift fails CI. +// +// Descriptions stay deliberately thin. Executor's long-form guidance lives +// server-side behind `executor_skills`, so a server-side wording fix reaches +// Pi users without republishing this package. +// --------------------------------------------------------------------------- + +import { StringEnum } from "@earendil-works/pi-ai"; +import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { + Type, + type Static, + type TObject, + type TOptional, + type TSchema, + type TString, + type TUnsafe, +} from "typebox"; + +import type { ExecutorConnection } from "./connection"; +import { resultText, toPiContent } from "./result"; + +/** Pi has no tool namespacing, so every tool carries the origin in its name. */ +export const TOOL_NAME_PREFIX = "executor_"; + +// The annotations keep the emitted .d.ts portable: without them TypeScript +// names typebox's inferred types through its install path. +export const EXECUTE_PARAMETERS: TObject<{ code: TString }> = Type.Object({ + code: Type.String({ + description: "JavaScript to run inside Executor's sandbox.", + }), +}); + +export const SKILLS_PARAMETERS: TObject<{ name: TOptional }> = Type.Object({ + name: Type.Optional( + Type.String({ + description: 'A doc from Executor\'s own catalog, e.g. "execute". Omit to list them.', + }), + ), +}); + +export const RESUME_PARAMETERS: TObject<{ + executionId: TString; + action: TUnsafe<"accept" | "decline" | "cancel">; + content: TOptional; +}> = Type.Object({ + executionId: Type.String({ + description: "The execution ID from the paused executor_execute result.", + }), + action: StringEnum(["accept", "decline", "cancel"] as const, { + description: "How to respond to the interaction.", + }), + content: Type.Optional( + Type.String({ + description: "JSON-encoded response content for form elicitations.", + }), + ), +}); + +/** The raw MCP result, kept on `details` for logs and custom renderers. */ +type ExecutorToolDetails = CallToolResult; + +const executorTool = (input: { + readonly tool: string; + readonly label: string; + readonly description: string; + readonly parameters: T; + readonly connection: ExecutorConnection; +}): ToolDefinition => ({ + name: `${TOOL_NAME_PREFIX}${input.tool}`, + label: input.label, + description: input.description, + parameters: input.parameters, + execute: async (_toolCallId, params, signal) => { + const result = await input.connection.callTool( + input.tool, + params as Record, + signal, + ); + // Pi marks a call failed only when `execute` throws, and keeps just the + // message — so the server's error text has to ride in it. See result.ts. + if (result.isError === true) { + const message = resultText(result); + throw new Error( + message.length > 0 ? message : `Executor's ${input.tool} tool reported an error.`, + ); + } + return { content: toPiContent(result), details: result }; + }, +}); + +export const registerExecutorTools = (pi: ExtensionAPI, connection: ExecutorConnection): void => { + pi.registerTool( + executorTool({ + tool: "execute", + label: "Executor", + description: [ + "Run JavaScript inside Executor to search your integration catalog and call the tools in it.", + 'Call `executor_skills({ name: "execute" })` first — it returns the guide for writing this code.', + ].join(" "), + parameters: EXECUTE_PARAMETERS, + connection, + }), + ); + + pi.registerTool( + executorTool({ + tool: "skills", + label: "Executor skills", + // Spelled out because Pi models have read this as a general skill reader + // before: github.com/UsefulSoftwareCo/executor/issues/1731. + description: [ + "Documentation for Executor's own tools — how to write code for `executor_execute`.", + "Not a general skill reader: it cannot reach Pi's skills, a SKILL.md on disk, or anything you authored.", + "Call with no name to list the few docs available.", + ].join(" "), + parameters: SKILLS_PARAMETERS, + connection, + }), + ); + + pi.registerTool( + executorTool({ + tool: "resume", + label: "Executor resume", + description: + "Resume an Executor execution that paused for approval, using the executionId from the paused executor_execute result.", + parameters: RESUME_PARAMETERS, + connection, + }), + ); +}; + +export type ExecuteParams = Static; +export type SkillsParams = Static; +export type ResumeParams = Static; diff --git a/packages/hosts/pi/tsconfig.build.json b/packages/hosts/pi/tsconfig.build.json new file mode 100644 index 0000000000..0216b09fc1 --- /dev/null +++ b/packages/hosts/pi/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "dist/types", + "rootDir": "src" + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/hosts/pi/tsconfig.json b/packages/hosts/pi/tsconfig.json new file mode 100644 index 0000000000..6b710f8bec --- /dev/null +++ b/packages/hosts/pi/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "lib": ["ES2022"], + "types": ["bun-types", "node"], + "noUnusedLocals": true, + "noImplicitOverride": true + }, + "include": ["src/**/*.ts", "tsup.config.ts", "vitest.config.ts"] +} diff --git a/packages/hosts/pi/tsup.config.ts b/packages/hosts/pi/tsup.config.ts new file mode 100644 index 0000000000..fa9bda4c4e --- /dev/null +++ b/packages/hosts/pi/tsup.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + // Pi discovers extensions from the directories named in package.json's `pi` + // key, so the built entry has to land inside `dist/extensions/`. + entry: { "extensions/executor": "src/extension.ts" }, + format: ["esm"], + // Declarations are emitted to `dist/types` by a separate tsc pass, NOT here. + // Pi loads every file in an extensions directory whose name ends in `.ts` or + // `.js` (pi-coding-agent, core/package-manager.js: `entry.name.endsWith(".ts") + // || entry.name.endsWith(".js")`), and `executor.d.ts` ends in `.ts` — it + // would be picked up and loaded as a second, broken extension. + dts: false, + clean: true, + sourcemap: true, + // Pi bundles these itself and requires them as `"*"` peers. + external: ["@earendil-works/pi-coding-agent", "@earendil-works/pi-ai", "typebox"], +}); diff --git a/packages/hosts/pi/vitest.config.ts b/packages/hosts/pi/vitest.config.ts new file mode 100644 index 0000000000..ae847ff6d9 --- /dev/null +++ b/packages/hosts/pi/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["src/**/*.test.ts"], + }, +}); diff --git a/scripts/publish-packages.ts b/scripts/publish-packages.ts index 62034b988c..61f56a62a4 100644 --- a/scripts/publish-packages.ts +++ b/scripts/publish-packages.ts @@ -41,6 +41,7 @@ const PUBLIC_PACKAGE_DIRS = [ "packages/plugins/mcp", "packages/plugins/onepassword", "packages/plugins/openapi", + "packages/hosts/pi", ] as const; const parseArgs = (argv: ReadonlyArray): { dryRun: boolean; prepareOnly: boolean } => { diff --git a/scripts/smoke-test-packed.ts b/scripts/smoke-test-packed.ts index 4273e2c4e3..bafcc080a3 100644 --- a/scripts/smoke-test-packed.ts +++ b/scripts/smoke-test-packed.ts @@ -47,6 +47,7 @@ const PUBLIC_PACKAGE_DIRS = [ "packages/plugins/mcp", "packages/plugins/onepassword", "packages/plugins/openapi", + "packages/hosts/pi", ] as const; type PackageJson = {