@xingular/harness 0.1.6 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -0
- package/LICENSE +202 -0
- package/NOTICE.md +60 -0
- package/README.md +265 -71
- package/THIRD_PARTY_LICENSES.md +942 -0
- package/dist/cli.js +14842 -9445
- package/dist/sdk/accounts/accounts.d.ts +165 -0
- package/dist/sdk/accounts/accounts.js +730 -0
- package/dist/sdk/accounts/choices.d.ts +23 -0
- package/dist/sdk/accounts/choices.js +62 -0
- package/dist/sdk/accounts/env.d.ts +78 -0
- package/dist/sdk/accounts/env.js +294 -0
- package/dist/sdk/accounts/secrets.d.ts +16 -0
- package/dist/sdk/accounts/secrets.js +40 -0
- package/dist/sdk/accounts/store.d.ts +114 -0
- package/dist/sdk/accounts/store.js +255 -0
- package/dist/sdk/browser/config.d.ts +16 -0
- package/dist/sdk/browser/config.js +22 -0
- package/dist/sdk/browser/diagnostics.d.ts +4 -0
- package/dist/sdk/browser/diagnostics.js +38 -0
- package/dist/sdk/browser/index.d.ts +17 -0
- package/dist/sdk/browser/index.js +65 -0
- package/dist/sdk/browser/network.d.ts +10 -0
- package/dist/sdk/browser/network.js +59 -0
- package/dist/sdk/browser/session.d.ts +84 -0
- package/dist/sdk/browser/session.js +192 -0
- package/dist/sdk/browser/tools.d.ts +8 -0
- package/dist/sdk/browser/tools.js +329 -0
- package/dist/sdk/capabilities/api.d.ts +22 -0
- package/dist/sdk/capabilities/api.js +54 -0
- package/dist/sdk/capabilities/runSnapshot.d.ts +18 -0
- package/dist/sdk/capabilities/runSnapshot.js +47 -0
- package/dist/sdk/capabilities/types.d.ts +29 -0
- package/dist/sdk/capabilities/types.js +1 -0
- package/dist/sdk/cli.d.ts +2 -0
- package/dist/sdk/cli.js +142 -0
- package/dist/sdk/core/bus.d.ts +23 -0
- package/dist/sdk/core/bus.js +76 -0
- package/dist/sdk/core/caller.d.ts +22 -0
- package/dist/sdk/core/caller.js +16 -0
- package/dist/sdk/core/changes.d.ts +15 -0
- package/dist/sdk/core/changes.js +15 -0
- package/dist/sdk/core/effort.d.ts +2 -0
- package/dist/sdk/core/effort.js +1 -0
- package/dist/sdk/core/env.d.ts +18 -0
- package/dist/sdk/core/env.js +8 -0
- package/dist/sdk/core/errors.d.ts +15 -0
- package/dist/sdk/core/errors.js +24 -0
- package/dist/sdk/core/frontmatter.d.ts +12 -0
- package/dist/sdk/core/frontmatter.js +50 -0
- package/dist/sdk/core/fs.d.ts +18 -0
- package/dist/sdk/core/fs.js +106 -0
- package/dist/sdk/core/index.d.ts +24 -0
- package/dist/sdk/core/index.js +187 -0
- package/dist/sdk/core/path.d.ts +7 -0
- package/dist/sdk/core/path.js +79 -0
- package/dist/sdk/core/projects.d.ts +18 -0
- package/dist/sdk/core/projects.js +67 -0
- package/dist/sdk/core/secrets.d.ts +2 -0
- package/dist/sdk/core/secrets.js +5 -0
- package/dist/sdk/core/text.d.ts +7 -0
- package/dist/sdk/core/text.js +40 -0
- package/dist/sdk/doctor/checks.d.ts +5 -0
- package/dist/sdk/doctor/checks.js +208 -0
- package/dist/sdk/doctor/probe.d.ts +4 -0
- package/dist/sdk/doctor/probe.js +92 -0
- package/dist/sdk/doctor/service.d.ts +23 -0
- package/dist/sdk/doctor/service.js +144 -0
- package/dist/sdk/doctor/types.d.ts +141 -0
- package/dist/sdk/doctor/types.js +29 -0
- package/dist/sdk/harness.d.ts +133 -0
- package/dist/sdk/harness.js +299 -0
- package/dist/sdk/history/lessonPrompt.d.ts +2 -0
- package/dist/sdk/history/lessonPrompt.js +15 -0
- package/dist/sdk/history/lessons.d.ts +52 -0
- package/dist/sdk/history/lessons.js +173 -0
- package/dist/sdk/history/recall.d.ts +68 -0
- package/dist/sdk/history/recall.js +199 -0
- package/dist/sdk/history/trajectory.d.ts +7 -0
- package/dist/sdk/history/trajectory.js +216 -0
- package/dist/sdk/hub/catalog.json +123 -0
- package/dist/sdk/hub/cli.d.ts +7 -0
- package/dist/sdk/hub/cli.js +81 -0
- package/dist/sdk/hub/hub.d.ts +74 -0
- package/dist/sdk/hub/hub.js +474 -0
- package/dist/sdk/hub/schema.d.ts +280 -0
- package/dist/sdk/hub/schema.js +78 -0
- package/dist/sdk/index.d.ts +63 -0
- package/dist/sdk/index.js +48 -0
- package/dist/sdk/mcp/connections.d.ts +83 -0
- package/dist/sdk/mcp/connections.js +669 -0
- package/dist/sdk/mcp/manager.d.ts +26 -0
- package/dist/sdk/mcp/manager.js +124 -0
- package/dist/sdk/mcp/oauth.d.ts +35 -0
- package/dist/sdk/mcp/oauth.js +171 -0
- package/dist/sdk/mcp/scopeMigration.d.ts +19 -0
- package/dist/sdk/mcp/scopeMigration.js +198 -0
- package/dist/sdk/mcp/service.d.ts +160 -0
- package/dist/sdk/mcp/service.js +510 -0
- package/dist/sdk/mcp/types.d.ts +131 -0
- package/dist/sdk/mcp/types.js +49 -0
- package/dist/sdk/pi/runtime.d.ts +13 -0
- package/dist/sdk/pi/runtime.js +31 -0
- package/dist/sdk/plugins/api.d.ts +66 -0
- package/dist/sdk/plugins/api.js +155 -0
- package/dist/sdk/plugins/attachments.d.ts +46 -0
- package/dist/sdk/plugins/attachments.js +111 -0
- package/dist/sdk/plugins/builtin.d.ts +6 -0
- package/dist/sdk/plugins/builtin.js +68 -0
- package/dist/sdk/plugins/catalogue.d.ts +29 -0
- package/dist/sdk/plugins/catalogue.js +196 -0
- package/dist/sdk/plugins/cli.d.ts +13 -0
- package/dist/sdk/plugins/cli.js +187 -0
- package/dist/sdk/plugins/discover.d.ts +42 -0
- package/dist/sdk/plugins/discover.js +115 -0
- package/dist/sdk/plugins/firstParty.d.ts +50 -0
- package/dist/sdk/plugins/firstParty.js +113 -0
- package/dist/sdk/plugins/hooks.d.ts +94 -0
- package/dist/sdk/plugins/hooks.js +162 -0
- package/dist/sdk/plugins/identity.d.ts +19 -0
- package/dist/sdk/plugins/identity.js +48 -0
- package/dist/sdk/plugins/install.d.ts +20 -0
- package/dist/sdk/plugins/install.js +110 -0
- package/dist/sdk/plugins/inventory.d.ts +22 -0
- package/dist/sdk/plugins/inventory.js +110 -0
- package/dist/sdk/plugins/manifest.d.ts +13 -0
- package/dist/sdk/plugins/manifest.js +56 -0
- package/dist/sdk/plugins/plugins.d.ts +104 -0
- package/dist/sdk/plugins/plugins.js +164 -0
- package/dist/sdk/plugins/runtime.d.ts +9 -0
- package/dist/sdk/plugins/runtime.js +95 -0
- package/dist/sdk/plugins/trust.d.ts +44 -0
- package/dist/sdk/plugins/trust.js +120 -0
- package/dist/sdk/plugins/types.d.ts +159 -0
- package/dist/sdk/plugins/types.js +7 -0
- package/dist/sdk/prompt/compile.d.ts +174 -0
- package/dist/sdk/prompt/compile.js +396 -0
- package/dist/sdk/refine/cli.d.ts +4 -0
- package/dist/sdk/refine/cli.js +186 -0
- package/dist/sdk/refine/demo.d.ts +21 -0
- package/dist/sdk/refine/demo.js +114 -0
- package/dist/sdk/refine/evidence.d.ts +55 -0
- package/dist/sdk/refine/evidence.js +330 -0
- package/dist/sdk/refine/host.d.ts +51 -0
- package/dist/sdk/refine/host.js +76 -0
- package/dist/sdk/refine/ledger.d.ts +48 -0
- package/dist/sdk/refine/ledger.js +76 -0
- package/dist/sdk/refine/library.d.ts +55 -0
- package/dist/sdk/refine/library.js +177 -0
- package/dist/sdk/refine/pi.d.ts +72 -0
- package/dist/sdk/refine/pi.js +248 -0
- package/dist/sdk/refine/prompts.d.ts +45 -0
- package/dist/sdk/refine/prompts.js +390 -0
- package/dist/sdk/refine/recursion.d.ts +113 -0
- package/dist/sdk/refine/recursion.js +258 -0
- package/dist/sdk/refine/runner.d.ts +81 -0
- package/dist/sdk/refine/runner.js +150 -0
- package/dist/sdk/refine/selection.d.ts +57 -0
- package/dist/sdk/refine/selection.js +251 -0
- package/dist/sdk/refine/service.d.ts +84 -0
- package/dist/sdk/refine/service.js +165 -0
- package/dist/sdk/refine/suite.d.ts +67 -0
- package/dist/sdk/refine/suite.js +174 -0
- package/dist/sdk/refine/trajectory.d.ts +9 -0
- package/dist/sdk/refine/trajectory.js +68 -0
- package/dist/sdk/refine/types.d.ts +77 -0
- package/dist/sdk/refine/types.js +10 -0
- package/dist/sdk/server/methods.d.ts +3 -0
- package/dist/sdk/server/methods.js +370 -0
- package/dist/sdk/server/remote.d.ts +19 -0
- package/dist/sdk/server/remote.js +84 -0
- package/dist/sdk/server/rpc.d.ts +20 -0
- package/dist/sdk/server/rpc.js +102 -0
- package/dist/sdk/server/serve.d.ts +10 -0
- package/dist/sdk/server/serve.js +18 -0
- package/dist/sdk/sessions/approvals.d.ts +47 -0
- package/dist/sdk/sessions/approvals.js +119 -0
- package/dist/sdk/sessions/continuation.d.ts +48 -0
- package/dist/sdk/sessions/continuation.js +85 -0
- package/dist/sdk/sessions/contracts.d.ts +534 -0
- package/dist/sdk/sessions/contracts.js +299 -0
- package/dist/sdk/sessions/extensionUi.d.ts +19 -0
- package/dist/sdk/sessions/extensionUi.js +64 -0
- package/dist/sdk/sessions/history.d.ts +16 -0
- package/dist/sdk/sessions/history.js +68 -0
- package/dist/sdk/sessions/lineageJoin.d.ts +2 -0
- package/dist/sdk/sessions/lineageJoin.js +61 -0
- package/dist/sdk/sessions/planMode.d.ts +36 -0
- package/dist/sdk/sessions/planMode.js +84 -0
- package/dist/sdk/sessions/policy.d.ts +43 -0
- package/dist/sdk/sessions/policy.js +1 -0
- package/dist/sdk/sessions/pricing.d.ts +34 -0
- package/dist/sdk/sessions/pricing.js +40 -0
- package/dist/sdk/sessions/reasoningSummaries.d.ts +11 -0
- package/dist/sdk/sessions/reasoningSummaries.js +61 -0
- package/dist/sdk/sessions/registry.d.ts +102 -0
- package/dist/sdk/sessions/registry.js +383 -0
- package/dist/sdk/sessions/runIndex.d.ts +21 -0
- package/dist/sdk/sessions/runIndex.js +65 -0
- package/dist/sdk/sessions/runStore.d.ts +66 -0
- package/dist/sdk/sessions/runStore.js +335 -0
- package/dist/sdk/sessions/runner.d.ts +213 -0
- package/dist/sdk/sessions/runner.js +589 -0
- package/dist/sdk/sessions/sessions.d.ts +140 -0
- package/dist/sdk/sessions/sessions.js +394 -0
- package/dist/sdk/sessions/subagentService.d.ts +41 -0
- package/dist/sdk/sessions/subagentService.js +247 -0
- package/dist/sdk/sessions/subagents.d.ts +50 -0
- package/dist/sdk/sessions/subagents.js +68 -0
- package/dist/sdk/terminal/accountsMenu.d.ts +46 -0
- package/dist/sdk/terminal/accountsMenu.js +392 -0
- package/dist/sdk/terminal/cli.d.ts +20 -0
- package/dist/sdk/terminal/cli.js +121 -0
- package/dist/sdk/terminal/client.d.ts +15 -0
- package/dist/sdk/terminal/client.js +126 -0
- package/dist/sdk/terminal/control.d.ts +4 -0
- package/dist/sdk/terminal/control.js +25 -0
- package/dist/sdk/terminal/controller.d.ts +72 -0
- package/dist/sdk/terminal/controller.js +351 -0
- package/dist/sdk/terminal/hubMenu.d.ts +10 -0
- package/dist/sdk/terminal/hubMenu.js +34 -0
- package/dist/sdk/terminal/launch.d.ts +5 -0
- package/dist/sdk/terminal/launch.js +27 -0
- package/dist/sdk/terminal/logs.d.ts +14 -0
- package/dist/sdk/terminal/logs.js +59 -0
- package/dist/sdk/terminal/piExtension.d.ts +6 -0
- package/dist/sdk/terminal/piExtension.js +392 -0
- package/dist/sdk/terminal/pluginMenu.d.ts +34 -0
- package/dist/sdk/terminal/pluginMenu.js +101 -0
- package/dist/sdk/terminal/socket.d.ts +70 -0
- package/dist/sdk/terminal/socket.js +256 -0
- package/dist/sdk/terminal/standalone.d.ts +18 -0
- package/dist/sdk/terminal/standalone.js +146 -0
- package/dist/sdk/terminal/terminals.d.ts +176 -0
- package/dist/sdk/terminal/terminals.js +767 -0
- package/dist/sdk/terminal/trajectory.d.ts +10 -0
- package/dist/sdk/terminal/trajectory.js +30 -0
- package/dist/sdk/terminal/tui/chrome.d.ts +64 -0
- package/dist/sdk/terminal/tui/chrome.js +169 -0
- package/dist/sdk/terminal/tui/clipboardFiles.d.ts +11 -0
- package/dist/sdk/terminal/tui/clipboardFiles.js +74 -0
- package/dist/sdk/terminal/tui/commandPages.d.ts +7 -0
- package/dist/sdk/terminal/tui/commandPages.js +63 -0
- package/dist/sdk/terminal/tui/commands.d.ts +42 -0
- package/dist/sdk/terminal/tui/commands.js +276 -0
- package/dist/sdk/terminal/tui/dialogGeometry.d.ts +27 -0
- package/dist/sdk/terminal/tui/dialogGeometry.js +50 -0
- package/dist/sdk/terminal/tui/dialogs.d.ts +75 -0
- package/dist/sdk/terminal/tui/dialogs.js +363 -0
- package/dist/sdk/terminal/tui/keybindings.d.ts +11 -0
- package/dist/sdk/terminal/tui/keybindings.js +49 -0
- package/dist/sdk/terminal/tui/list.d.ts +73 -0
- package/dist/sdk/terminal/tui/list.js +196 -0
- package/dist/sdk/terminal/tui/modeChoices.d.ts +42 -0
- package/dist/sdk/terminal/tui/modeChoices.js +70 -0
- package/dist/sdk/terminal/tui/modelPicker.d.ts +55 -0
- package/dist/sdk/terminal/tui/modelPicker.js +155 -0
- package/dist/sdk/terminal/tui/mouse.d.ts +3 -0
- package/dist/sdk/terminal/tui/mouse.js +3 -0
- package/dist/sdk/terminal/tui/page.d.ts +14 -0
- package/dist/sdk/terminal/tui/page.js +1 -0
- package/dist/sdk/terminal/tui/pageOverlay.d.ts +27 -0
- package/dist/sdk/terminal/tui/pageOverlay.js +145 -0
- package/dist/sdk/terminal/tui/pages/accountsPage.d.ts +54 -0
- package/dist/sdk/terminal/tui/pages/accountsPage.js +181 -0
- package/dist/sdk/terminal/tui/pages/componentsPage.d.ts +47 -0
- package/dist/sdk/terminal/tui/pages/componentsPage.js +119 -0
- package/dist/sdk/terminal/tui/pages/doctorPage.d.ts +37 -0
- package/dist/sdk/terminal/tui/pages/doctorPage.js +145 -0
- package/dist/sdk/terminal/tui/pages/hubPage.d.ts +39 -0
- package/dist/sdk/terminal/tui/pages/hubPage.js +128 -0
- package/dist/sdk/terminal/tui/pages/logPage.d.ts +38 -0
- package/dist/sdk/terminal/tui/pages/logPage.js +189 -0
- package/dist/sdk/terminal/tui/pages/permissionsPage.d.ts +44 -0
- package/dist/sdk/terminal/tui/pages/permissionsPage.js +124 -0
- package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +57 -0
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +243 -0
- package/dist/sdk/terminal/tui/pages/settingsPage.d.ts +55 -0
- package/dist/sdk/terminal/tui/pages/settingsPage.js +88 -0
- package/dist/sdk/terminal/tui/pages/trajectoryPage.d.ts +58 -0
- package/dist/sdk/terminal/tui/pages/trajectoryPage.js +377 -0
- package/dist/sdk/terminal/tui/piAdapter.d.ts +18 -0
- package/dist/sdk/terminal/tui/piAdapter.js +48 -0
- package/dist/sdk/terminal/tui/planMode.d.ts +2 -0
- package/dist/sdk/terminal/tui/planMode.js +2 -0
- package/dist/sdk/terminal/tui/questionPicker.d.ts +20 -0
- package/dist/sdk/terminal/tui/questionPicker.js +150 -0
- package/dist/sdk/terminal/tui/questions.d.ts +19 -0
- package/dist/sdk/terminal/tui/questions.js +42 -0
- package/dist/sdk/terminal/tui/runtime.d.ts +114 -0
- package/dist/sdk/terminal/tui/runtime.js +441 -0
- package/dist/sdk/terminal/tui/search.d.ts +22 -0
- package/dist/sdk/terminal/tui/search.js +60 -0
- package/dist/sdk/terminal/tui/selection.d.ts +16 -0
- package/dist/sdk/terminal/tui/selection.js +16 -0
- package/dist/sdk/terminal/tui/sessionUi.d.ts +3 -0
- package/dist/sdk/terminal/tui/sessionUi.js +30 -0
- package/dist/sdk/terminal/tui/settingsModel.d.ts +12 -0
- package/dist/sdk/terminal/tui/settingsModel.js +84 -0
- package/dist/sdk/terminal/tui/sideQuestion.d.ts +53 -0
- package/dist/sdk/terminal/tui/sideQuestion.js +197 -0
- package/dist/sdk/terminal/tui/skillInvocation.d.ts +9 -0
- package/dist/sdk/terminal/tui/skillInvocation.js +43 -0
- package/dist/sdk/terminal/tui/status.d.ts +18 -0
- package/dist/sdk/terminal/tui/status.js +67 -0
- package/dist/sdk/terminal/tui/theme.d.ts +45 -0
- package/dist/sdk/terminal/tui/theme.js +176 -0
- package/dist/sdk/terminal/tui/treeModel.d.ts +79 -0
- package/dist/sdk/terminal/tui/treeModel.js +483 -0
- package/dist/sdk/terminal/tui/treePicker.d.ts +49 -0
- package/dist/sdk/terminal/tui/treePicker.js +251 -0
- package/dist/sdk/terminal/tui/ui.d.ts +47 -0
- package/dist/sdk/terminal/tui/ui.js +148 -0
- package/dist/sdk/terminal/tui/workingStatus.d.ts +9 -0
- package/dist/sdk/terminal/tui/workingStatus.js +229 -0
- package/dist/sdk/tools/askUser.d.ts +6 -0
- package/dist/sdk/tools/askUser.js +74 -0
- package/dist/sdk/tools/checklist.d.ts +9 -0
- package/dist/sdk/tools/checklist.js +54 -0
- package/dist/sdk/tools/definitions.d.ts +30 -0
- package/dist/sdk/tools/definitions.js +117 -0
- package/dist/sdk/tools/dispatch.d.ts +16 -0
- package/dist/sdk/tools/dispatch.js +67 -0
- package/dist/sdk/tools/harnessTools.d.ts +6 -0
- package/dist/sdk/tools/harnessTools.js +20 -0
- package/dist/sdk/tools/sessionName.d.ts +7 -0
- package/dist/sdk/tools/sessionName.js +31 -0
- package/dist/sdk/tools/spawnSubagent.d.ts +4 -0
- package/dist/sdk/tools/spawnSubagent.js +49 -0
- package/dist/sdk/tools/types.d.ts +48 -0
- package/dist/sdk/tools/types.js +1 -0
- package/dist/sdk/variant/components.d.ts +34 -0
- package/dist/sdk/variant/components.js +230 -0
- package/dist/sdk/variant/materialise.d.ts +66 -0
- package/dist/sdk/variant/materialise.js +81 -0
- package/dist/sdk/variant/migrate.d.ts +4 -0
- package/dist/sdk/variant/migrate.js +41 -0
- package/dist/sdk/variant/seed.d.ts +20 -0
- package/dist/sdk/variant/seed.js +116 -0
- package/dist/sdk/variant/store.d.ts +82 -0
- package/dist/sdk/variant/store.js +151 -0
- package/dist/sdk/variant/surface.d.ts +56 -0
- package/dist/sdk/variant/surface.js +81 -0
- package/dist/sdk/variant/trust.d.ts +27 -0
- package/dist/sdk/variant/trust.js +64 -0
- package/dist/sdk/variant/variant.d.ts +71 -0
- package/dist/sdk/variant/variant.js +331 -0
- package/dist/sdk/web/markdown.d.ts +10 -0
- package/dist/sdk/web/markdown.js +66 -0
- package/dist/sdk/web/safeRequest.d.ts +34 -0
- package/dist/sdk/web/safeRequest.js +175 -0
- package/dist/sdk/web/search.d.ts +53 -0
- package/dist/sdk/web/search.js +168 -0
- package/dist/sdk/web/text.d.ts +2 -0
- package/dist/sdk/web/text.js +7 -0
- package/dist/sdk/web/tools.d.ts +22 -0
- package/dist/sdk/web/tools.js +195 -0
- package/dist/sdk/web/turndown-plugin-gfm.d.ts +4 -0
- package/dist/sdk/wiki/notes.d.ts +68 -0
- package/dist/sdk/wiki/notes.js +297 -0
- package/dist/sdk/world/actions.d.ts +23 -0
- package/dist/sdk/world/actions.js +31 -0
- package/dist/sdk/world/cli.d.ts +12 -0
- package/dist/sdk/world/cli.js +166 -0
- package/dist/sdk/world/images.d.ts +20 -0
- package/dist/sdk/world/images.js +48 -0
- package/dist/sdk/world/untrusted.d.ts +12 -0
- package/dist/sdk/world/untrusted.js +20 -0
- package/dist/sdk/world/world.d.ts +65 -0
- package/dist/sdk/world/world.js +80 -0
- package/dist/terminal-opentui.js +13612 -0
- package/dist/terminal-pi.js +1170 -62
- package/docs/README.md +34 -0
- package/docs/architecture/00-overview.md +159 -0
- package/docs/architecture/01-extension-points.md +78 -0
- package/docs/architecture/02-sessions-and-runs.md +146 -0
- package/docs/architecture/03-variants.md +95 -0
- package/docs/architecture/04-prompt.md +102 -0
- package/docs/architecture/05-refinement.md +107 -0
- package/docs/architecture/06-plugins-and-capabilities.md +93 -0
- package/docs/architecture/07-tools-and-gate.md +88 -0
- package/docs/architecture/08-memory-and-history.md +94 -0
- package/docs/architecture/09-accounts-and-connectors.md +90 -0
- package/docs/architecture/10-server-protocol.md +84 -0
- package/docs/architecture/11-terminal.md +81 -0
- package/docs/architecture/12-storage.md +61 -0
- package/docs/architecture/13-world-tools.md +23 -0
- package/docs/architecture/README.md +114 -0
- package/docs/architecture/proposals/PROP-0001-reporting-section.md +106 -0
- package/docs/architecture/proposals/PROP-0002-retain-rollout-workspaces.md +104 -0
- package/docs/architecture/proposals/PROP-0003-executor-settings.md +81 -0
- package/docs/architecture/proposals/PROP-0004-flexible-reporting-guidance.md +71 -0
- package/docs/architecture/proposals/PROP-0005-xharness-cli-name.md +78 -0
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +68 -0
- package/docs/architecture/proposals/PROP-0007-run-continuation.md +80 -0
- package/docs/architecture/proposals/PROP-0008-lessons-from-runs.md +80 -0
- package/docs/architecture/proposals/PROP-0009-foundation-and-variants.md +78 -0
- package/docs/architecture/proposals/PROP-0010-session-names.md +73 -0
- package/docs/architecture/proposals/PROP-0011-doctor.md +63 -0
- package/docs/architecture/proposals/PROP-0012-release-baseline.md +56 -0
- package/docs/architecture/proposals/README.md +76 -0
- package/docs/decisions/ADR-0001-owned-terminal.md +87 -0
- package/docs/decisions/ADR-0002-native-subagent-lifecycle.md +49 -0
- package/docs/decisions/ADR-0003-shared-asynchronous-subagents.md +70 -0
- package/docs/decisions/ADR-0004-fullscreen-terminal-layout.md +50 -0
- package/docs/decisions/ADR-0005-app-provided-oauth-clients.md +54 -0
- package/docs/decisions/ADR-0006-opentui-only-terminal.md +47 -0
- package/docs/decisions/ADR-0007-reporting-section.md +33 -0
- package/docs/decisions/ADR-0008-flexible-reporting-guidance.md +38 -0
- package/docs/decisions/ADR-0009-xharness-command.md +38 -0
- package/docs/decisions/ADR-0010-lessons-in-the-wiki.md +31 -0
- package/docs/decisions/ADR-0011-foundation-and-variants.md +28 -0
- package/docs/decisions/ADR-0012-native-session-names.md +34 -0
- package/docs/decisions/ADR-0013-doctor.md +27 -0
- package/docs/decisions/ADR-0014-public-release-baseline.md +21 -0
- package/docs/doctor.md +74 -0
- package/docs/experiments/EXP-0001-terminal-input-latency/plan.md +81 -0
- package/docs/experiments/EXP-0001-terminal-input-latency/results.md +46 -0
- package/docs/experiments/EXP-0002-opentui-typing-latency/plan.md +70 -0
- package/docs/experiments/EXP-0002-opentui-typing-latency/results.md +92 -0
- package/docs/features/README.md +20 -0
- package/docs/features/automations/README.md +41 -0
- package/docs/features/cores/README.md +39 -0
- package/docs/features/google-harness2/README.md +52 -0
- package/docs/features/integrations/README.md +40 -0
- package/docs/features/interfaces/README.md +29 -0
- package/docs/features/memory/README.md +28 -0
- package/docs/releases/1.0.0.md +40 -0
- package/docs/releases/README.md +36 -0
- package/docs/study/00-index.md +78 -0
- package/docs/study/arms/00-matrix.md +20 -0
- package/docs/study/arms/01-system-prompt.md +211 -0
- package/docs/study/arms/02-tools.md +79 -0
- package/docs/study/arms/03-subagents.md +90 -0
- package/docs/study/arms/04-skills.md +75 -0
- package/docs/study/arms/05-context-management.md +90 -0
- package/docs/study/arms/06-memory.md +90 -0
- package/docs/study/arms/07-governance.md +71 -0
- package/docs/study/arms/08-execution.md +69 -0
- package/docs/study/arms/09-observability.md +60 -0
- package/docs/study/arms/10-refinement.md +110 -0
- package/docs/study/btw-comparison.md +37 -0
- package/docs/study/feature-gap-review.md +42 -0
- package/docs/study/insights.md +123 -0
- package/docs/study/roadmap.md +72 -0
- package/docs/study/sources/claude-code.md +44 -0
- package/docs/study/sources/deepseek-harness.md +102 -0
- package/docs/study/sources/harness2.md +50 -0
- package/docs/study/sources/kimi-cli.md +42 -0
- package/docs/study/sources/omp.md +93 -0
- package/docs/study/sources/opencode.md +43 -0
- package/docs/study/sources/openhands.md +46 -0
- package/docs/study/sources/smolagents.md +40 -0
- package/docs/study/sources/swe-agent.md +34 -0
- package/docs/system-prompt-design.md +196 -0
- package/docs/terminal-tui.md +671 -0
- package/package.json +48 -12
- package/src/accounts/accounts.test.ts +0 -223
- package/src/accounts/accounts.ts +0 -522
- package/src/accounts/env.test.ts +0 -95
- package/src/accounts/env.ts +0 -338
- package/src/accounts/secrets.ts +0 -50
- package/src/accounts/store.ts +0 -338
- package/src/capabilities/api.ts +0 -75
- package/src/capabilities/runSnapshot.ts +0 -59
- package/src/capabilities/types.ts +0 -34
- package/src/cli.ts +0 -114
- package/src/core/bus.ts +0 -34
- package/src/core/changes.ts +0 -33
- package/src/core/env.ts +0 -27
- package/src/core/errors.ts +0 -32
- package/src/core/frontmatter.ts +0 -47
- package/src/core/fs.test.ts +0 -38
- package/src/core/fs.ts +0 -92
- package/src/core/index.ts +0 -193
- package/src/core/path.test.ts +0 -14
- package/src/core/path.ts +0 -79
- package/src/core/projects.ts +0 -90
- package/src/core/text.ts +0 -39
- package/src/harness.ts +0 -299
- package/src/index.ts +0 -70
- package/src/mcp/connections.ts +0 -614
- package/src/mcp/manager.ts +0 -130
- package/src/mcp/oauth.ts +0 -195
- package/src/mcp/scopeMigration.test.ts +0 -79
- package/src/mcp/scopeMigration.ts +0 -204
- package/src/mcp/service.test.ts +0 -376
- package/src/mcp/service.ts +0 -549
- package/src/mcp/types.ts +0 -108
- package/src/memory/api.test.ts +0 -82
- package/src/memory/api.ts +0 -289
- package/src/memory/blocks.test.ts +0 -78
- package/src/memory/blocks.ts +0 -406
- package/src/memory/caller.ts +0 -57
- package/src/memory/dispatch.ts +0 -59
- package/src/memory/facts.test.ts +0 -41
- package/src/memory/facts.ts +0 -248
- package/src/memory/graph.ts +0 -103
- package/src/memory/health.test.ts +0 -130
- package/src/memory/health.ts +0 -74
- package/src/memory/maintainer.ts +0 -272
- package/src/memory/maintenance.test.ts +0 -176
- package/src/memory/maintenance.ts +0 -330
- package/src/memory/migrate.ts +0 -79
- package/src/memory/operations.ts +0 -138
- package/src/memory/proposals.test.ts +0 -168
- package/src/memory/proposals.ts +0 -576
- package/src/memory/recall.test.ts +0 -104
- package/src/memory/recall.ts +0 -271
- package/src/memory/references.ts +0 -77
- package/src/memory/runContext.ts +0 -93
- package/src/memory/secrets.ts +0 -6
- package/src/memory/store.test.ts +0 -66
- package/src/memory/store.ts +0 -385
- package/src/memory/wiki.test.ts +0 -47
- package/src/memory/wiki.ts +0 -422
- package/src/memory/wikiIndex.test.ts +0 -88
- package/src/memory/wikiIndex.ts +0 -116
- package/src/plugins/api.ts +0 -181
- package/src/plugins/attachments.test.ts +0 -84
- package/src/plugins/attachments.ts +0 -141
- package/src/plugins/builtin.ts +0 -82
- package/src/plugins/catalogue.test.ts +0 -254
- package/src/plugins/catalogue.ts +0 -251
- package/src/plugins/cli.ts +0 -184
- package/src/plugins/discover.ts +0 -151
- package/src/plugins/firstParty.test.ts +0 -143
- package/src/plugins/firstParty.ts +0 -129
- package/src/plugins/hooks.test.ts +0 -250
- package/src/plugins/hooks.ts +0 -239
- package/src/plugins/identity.ts +0 -37
- package/src/plugins/install.test.ts +0 -107
- package/src/plugins/install.ts +0 -106
- package/src/plugins/inventory.ts +0 -123
- package/src/plugins/manifest.ts +0 -76
- package/src/plugins/plugins.test.ts +0 -205
- package/src/plugins/plugins.ts +0 -189
- package/src/plugins/proposal.ts +0 -121
- package/src/plugins/runtime.test.ts +0 -40
- package/src/plugins/runtime.ts +0 -82
- package/src/plugins/trust.ts +0 -142
- package/src/plugins/types.ts +0 -149
- package/src/prompt/compile.test.ts +0 -261
- package/src/prompt/compile.ts +0 -732
- package/src/server/methods.ts +0 -314
- package/src/server/remote.ts +0 -77
- package/src/server/rpc.ts +0 -116
- package/src/server/serve.ts +0 -28
- package/src/server/server.test.ts +0 -174
- package/src/sessions/approvals.ts +0 -118
- package/src/sessions/contracts.ts +0 -310
- package/src/sessions/conversation.test.ts +0 -150
- package/src/sessions/extensionSession.test.ts +0 -74
- package/src/sessions/extensionUi.test.ts +0 -65
- package/src/sessions/extensionUi.ts +0 -102
- package/src/sessions/history.ts +0 -51
- package/src/sessions/lineageJoin.ts +0 -75
- package/src/sessions/policy.ts +0 -32
- package/src/sessions/pricing.ts +0 -60
- package/src/sessions/reasoningSummaries.ts +0 -59
- package/src/sessions/registry.ts +0 -294
- package/src/sessions/runIndex.ts +0 -99
- package/src/sessions/runStore.test.ts +0 -152
- package/src/sessions/runStore.ts +0 -332
- package/src/sessions/runner.ts +0 -569
- package/src/sessions/runtimeResources.test.ts +0 -61
- package/src/sessions/sessions.test.ts +0 -455
- package/src/sessions/sessions.ts +0 -295
- package/src/skills/registry.test.ts +0 -104
- package/src/skills/registry.ts +0 -407
- package/src/terminal/cli.ts +0 -149
- package/src/terminal/client.ts +0 -74
- package/src/terminal/controller.ts +0 -227
- package/src/terminal/launch.test.ts +0 -34
- package/src/terminal/launch.ts +0 -88
- package/src/terminal/piExtension.ts +0 -210
- package/src/terminal/pluginControl.test.ts +0 -146
- package/src/terminal/pluginMenu.ts +0 -112
- package/src/terminal/socket.test.ts +0 -71
- package/src/terminal/socket.ts +0 -221
- package/src/terminal/standalone.test.ts +0 -130
- package/src/terminal/standalone.ts +0 -144
- package/src/terminal/terminal.test.ts +0 -330
- package/src/terminal/terminals.ts +0 -443
- package/src/tools/askUser.ts +0 -45
- package/src/tools/definitions.ts +0 -187
- package/src/tools/memoryTools.ts +0 -22
- package/src/tools/pluginPropose.ts +0 -41
- package/src/tools/types.ts +0 -39
- package/src/web/safeRequest.ts +0 -171
- package/src/web/text.ts +0 -53
- package/src/web/tools.ts +0 -134
- package/src/web/web.test.ts +0 -141
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
// Whole replies, through everything the harness does for one: the compiled prompt, the plugins
|
|
2
|
-
// it chose to load, the gate, the run's events. Pi is real and so are the extensions; only the
|
|
3
|
-
// model is scripted, so no request leaves the machine.
|
|
4
|
-
import { existsSync, mkdtempSync } from 'node:fs'
|
|
5
|
-
import { tmpdir } from 'node:os'
|
|
6
|
-
import { join } from 'node:path'
|
|
7
|
-
import { afterEach, describe, expect, it } from 'vitest'
|
|
8
|
-
|
|
9
|
-
import { memorySecretStore } from '../accounts/secrets'
|
|
10
|
-
import { createHarness, type Harness } from '../harness'
|
|
11
|
-
import { listInbox } from '../memory/operations'
|
|
12
|
-
import { wikiDir } from '../memory/store'
|
|
13
|
-
import type { Plugin } from '../plugins/types'
|
|
14
|
-
import { methods } from '../server/methods'
|
|
15
|
-
import { fauxModel } from '../../tests/faux'
|
|
16
|
-
import { rootWithProject, writeFile } from '../../tests/helpers'
|
|
17
|
-
import type { RunEvent } from './contracts'
|
|
18
|
-
|
|
19
|
-
const open: Harness[] = []
|
|
20
|
-
afterEach(async () => {
|
|
21
|
-
for (const harness of open.splice(0)) await harness.close()
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
const EXTENSION = `import { Type } from 'typebox'
|
|
25
|
-
export default function (pi) {
|
|
26
|
-
pi.registerTool({ name: 'kit_scan', label: 'Scan', description: 'Scan a folder.', parameters: Type.Object({ path: Type.String() }), async execute(id, input, signal, onUpdate, ctx) {
|
|
27
|
-
ctx.ui.notify('Scanning ' + input.path, 'info')
|
|
28
|
-
return { content: [{ type: 'text', text: 'scanned ' + input.path }], details: undefined }
|
|
29
|
-
} })
|
|
30
|
-
}
|
|
31
|
-
`
|
|
32
|
-
|
|
33
|
-
async function setup() {
|
|
34
|
-
const root = rootWithProject()
|
|
35
|
-
const base = mkdtempSync(join(tmpdir(), 'conversation-'))
|
|
36
|
-
const harness = createHarness({ dataRoot: root, stateDir: join(base, 'state'), secrets: memorySecretStore(), maintainerFactory: async () => ({ prompt: async () => undefined, dispose: () => undefined, error: () => null }) })
|
|
37
|
-
open.push(harness)
|
|
38
|
-
harness.accounts.create('Main', 'apiKey')
|
|
39
|
-
const model = await fauxModel(harness)
|
|
40
|
-
const events: RunEvent[] = []
|
|
41
|
-
harness.runs.listen((event) => void events.push(event))
|
|
42
|
-
const table = methods(harness)
|
|
43
|
-
const call = <T>(method: string, params: Record<string, unknown> = {}) => table[method](params) as Promise<T>
|
|
44
|
-
const settled = (id: string) => events.some((event) => event.runId === id && (event.type === 'completed' || event.type === 'failed'))
|
|
45
|
-
const until = async (done: () => boolean) => {
|
|
46
|
-
for (let waited = 0; waited < 400 && !done(); waited += 1) await new Promise((resolve) => setTimeout(resolve, 25))
|
|
47
|
-
}
|
|
48
|
-
let count = 0
|
|
49
|
-
const start = async (extra: Record<string, unknown> = {}) => {
|
|
50
|
-
const id = `run-${(count += 1)}`
|
|
51
|
-
await harness.sessions.run({ run: { id, sessionId: 'a1' }, cwd: base, sessionDir: join(root, 'sessions', 'a1'), text: 'Go.', model: model.model, caller: { agentId: 'a1', agentName: 'Helper', actor: 'Chat' }, tools: { allow: ['read'] }, ...extra })
|
|
52
|
-
return id
|
|
53
|
-
}
|
|
54
|
-
const of = (id: string) => events.filter((event) => event.runId === id)
|
|
55
|
-
return { harness, root, base, model, call, start, until, settled, of }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
describe('a whole reply, with a scripted model', () => {
|
|
59
|
-
it('dispatches a registered command with notes without making a provider request', async () => {
|
|
60
|
-
const { harness, model, start, until, settled, of } = await setup()
|
|
61
|
-
const account = harness.accounts.context(harness.accounts.resolve(null, null))
|
|
62
|
-
writeFile(join(account.configDir, 'extensions/command.ts'), `export default function(pi) { pi.registerCommand('hello', { description: 'Hello', handler: async (args, ctx) => ctx.ui.notify('hello ' + args) }) }`)
|
|
63
|
-
const id = await start({ text: '/hello original arguments', preamble: '[A note for the next request]' })
|
|
64
|
-
await until(() => settled(id))
|
|
65
|
-
expect(of(id).at(-1)?.type).toBe('completed')
|
|
66
|
-
expect(of(id).find((event) => event.type === 'extension.notice')?.payload).toMatchObject({ text: 'hello original arguments' })
|
|
67
|
-
expect(model.requests()).toBe(0)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('asks before an extension tool that uses the name of an allowed native read', async () => {
|
|
71
|
-
const { harness, model, start, until, settled, of } = await setup()
|
|
72
|
-
const account = harness.accounts.context(harness.accounts.resolve(null, null))
|
|
73
|
-
writeFile(join(account.configDir, 'extensions/override.ts'), EXTENSION.replaceAll('kit_scan', 'read'))
|
|
74
|
-
model.script([model.calls(['read', { path: 'src' }]), model.says('Done.')])
|
|
75
|
-
const id = await start()
|
|
76
|
-
await until(() => of(id).some((event) => event.type === 'approval.requested') || settled(id))
|
|
77
|
-
const approval = of(id).find((event) => event.type === 'approval.requested')?.payload as { approvalId: string; target: string }
|
|
78
|
-
expect(approval?.target).toBe('read')
|
|
79
|
-
expect(of(id).some((event) => event.type === 'extension.notice')).toBe(false)
|
|
80
|
-
harness.runs.resolveApproval(id, approval.approvalId, 'denied')
|
|
81
|
-
await until(() => settled(id))
|
|
82
|
-
expect(model.lastToolResults().at(-1)).toContain('denied')
|
|
83
|
-
expect(of(id).some((event) => event.type === 'extension.notice')).toBe(false)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it("offers an installed plugin's tool only once the plugin is switched on and trusted, and asks before the tool runs", async () => {
|
|
87
|
-
const { harness, base, model, call, start, until, settled, of } = await setup()
|
|
88
|
-
writeFile(join(base, 'kit/package.json'), JSON.stringify({ name: 'review-kit', version: '1.0.0', description: 'Review helpers.' }))
|
|
89
|
-
writeFile(join(base, 'kit/extensions/scan.ts'), EXTENSION)
|
|
90
|
-
const plugin = (await call<Plugin[]>('plugins.install', { source: join(base, 'kit') })).find((candidate) => candidate.id === 'package:review-kit')!
|
|
91
|
-
const scan = () => model.script([model.calls(['kit_scan', { path: 'src' }]), model.says('Scanned.')])
|
|
92
|
-
|
|
93
|
-
// Installed, switched off, untrusted: the model names a tool the session does not have.
|
|
94
|
-
scan()
|
|
95
|
-
const first = await start()
|
|
96
|
-
await until(() => settled(first))
|
|
97
|
-
expect(of(first).some((event) => event.type === 'approval.requested')).toBe(false)
|
|
98
|
-
expect(model.lastToolResults().at(-1)).toMatch(/kit_scan/)
|
|
99
|
-
expect(model.lastToolResults().at(-1)).not.toContain('scanned src')
|
|
100
|
-
|
|
101
|
-
await call('plugins.attach', { id: plugin.id })
|
|
102
|
-
await call('plugins.trust', { hash: plugin.code!.hash })
|
|
103
|
-
scan()
|
|
104
|
-
const second = await start()
|
|
105
|
-
await until(() => of(second).some((event) => event.type === 'approval.requested') || settled(second))
|
|
106
|
-
const approval = of(second).find((event) => event.type === 'approval.requested')!.payload as { approvalId: string; target: string }
|
|
107
|
-
expect(approval.target).toBe('kit_scan')
|
|
108
|
-
// Nothing ran while the person was deciding.
|
|
109
|
-
expect(of(second).some((event) => event.type === 'extension.notice')).toBe(false)
|
|
110
|
-
harness.runs.resolveApproval(second, approval.approvalId, 'approved')
|
|
111
|
-
await until(() => settled(second))
|
|
112
|
-
expect(model.lastToolResults().at(-1)).toBe('scanned src')
|
|
113
|
-
// What the extension told the person arrived as a run event.
|
|
114
|
-
expect(of(second).find((event) => event.type === 'extension.notice')?.payload).toEqual({ kind: 'notify', level: 'info', text: 'Scanning src' })
|
|
115
|
-
expect(of(second).at(-1)?.type).toBe('completed')
|
|
116
|
-
// Having loaded once, the plugin's details say what its extension adds.
|
|
117
|
-
expect((await call<Plugin[]>('plugins.list')).find((candidate) => candidate.id === plugin.id)!.contributions[0].description).toBe('Adds tools kit_scan.')
|
|
118
|
-
|
|
119
|
-
// Denied, the tool does not run and the model is told.
|
|
120
|
-
scan()
|
|
121
|
-
const third = await start()
|
|
122
|
-
await until(() => of(third).some((event) => event.type === 'approval.requested') || settled(third))
|
|
123
|
-
harness.runs.resolveApproval(third, (of(third).find((event) => event.type === 'approval.requested')!.payload as { approvalId: string }).approvalId, 'denied')
|
|
124
|
-
await until(() => settled(third))
|
|
125
|
-
expect(model.lastToolResults().at(-1)).toMatch(/denied/)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('lets the agent propose a plugin, which reaches the Inbox and nothing else', async () => {
|
|
129
|
-
const { root, model, start, until, settled, of } = await setup()
|
|
130
|
-
model.script([
|
|
131
|
-
model.calls(['plugin_propose', { name: 'Memory to Docs', description: 'Copies each saved memory to the team document.', extends: ['memory'] as never, files: [{ path: 'hooks.mjs', content: "export default function (harness) { harness.on('memory.saved', async () => {}) }\n" }] as never }]),
|
|
132
|
-
model.says('I proposed it; it is in your Inbox.')
|
|
133
|
-
])
|
|
134
|
-
const id = await start()
|
|
135
|
-
await until(() => settled(id))
|
|
136
|
-
expect(model.lastToolResults().at(-1)).toContain('The plugin "memory-to-docs" is waiting in the Inbox')
|
|
137
|
-
expect(listInbox(wikiDir(root)).map((item) => item.title)).toEqual(['Proposed plugin "Memory to Docs"'])
|
|
138
|
-
expect(existsSync(join(root, 'plugins'))).toBe(false)
|
|
139
|
-
// Proposing is not something a person is asked to approve: it changes nothing until they accept.
|
|
140
|
-
expect(of(id).some((event) => event.type === 'approval.requested')).toBe(false)
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
it('has no plugin proposal tool in unattended work', async () => {
|
|
144
|
-
const { model, start, until, settled } = await setup()
|
|
145
|
-
model.script([model.calls(['plugin_propose', { name: 'x', description: 'y', files: [] as never }]), model.says('Done.')])
|
|
146
|
-
const id = await start({ automatic: true })
|
|
147
|
-
await until(() => settled(id))
|
|
148
|
-
expect(model.lastToolResults().at(-1)).not.toContain('waiting in the Inbox')
|
|
149
|
-
})
|
|
150
|
-
})
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
// A real Pi session with a real extension, and no provider: nothing here makes a model request.
|
|
2
|
-
import { mkdtempSync } from 'node:fs'
|
|
3
|
-
import { tmpdir } from 'node:os'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
import { afterEach, expect, it } from 'vitest'
|
|
6
|
-
|
|
7
|
-
import { memorySecretStore } from '../accounts/secrets'
|
|
8
|
-
import { createHarness, type Harness } from '../harness'
|
|
9
|
-
import { writeFile } from '../../tests/helpers'
|
|
10
|
-
import type { ExtensionNotice } from './extensionUi'
|
|
11
|
-
import type { QuestionInput } from './registry'
|
|
12
|
-
import { piSessionFactory, type TurnParams } from './runner'
|
|
13
|
-
|
|
14
|
-
const open: Harness[] = []
|
|
15
|
-
afterEach(async () => {
|
|
16
|
-
for (const harness of open.splice(0)) await harness.close()
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
const EXTENSION = `import { Type } from 'typebox'
|
|
20
|
-
export default function (pi) {
|
|
21
|
-
pi.registerTool({ name: 'kit_scan', label: 'Scan', description: 'Scan a folder.', parameters: Type.Object({ path: Type.String() }), async execute() { return { content: [{ type: 'text', text: 'scanned' }], details: undefined } } })
|
|
22
|
-
pi.registerCommand('hello', { description: 'Says hello', handler: async (args, ctx) => {
|
|
23
|
-
const sure = await ctx.ui.confirm('Say hello?', args)
|
|
24
|
-
ctx.ui.notify(sure ? 'hello ' + args : 'kept quiet', 'info')
|
|
25
|
-
} })
|
|
26
|
-
pi.on('session_start', async (_event, ctx) => { ctx.ui.setStatus('kit', 'ready in ' + ctx.mode) })
|
|
27
|
-
}
|
|
28
|
-
`
|
|
29
|
-
|
|
30
|
-
it('binds an extension in a session without a terminal: its start hook runs, its tool is offered and asks, its command can ask and tell', async () => {
|
|
31
|
-
const base = mkdtempSync(join(tmpdir(), 'ext-session-'))
|
|
32
|
-
const harness = createHarness({ dataRoot: join(base, 'data'), stateDir: join(base, 'state'), secrets: memorySecretStore() })
|
|
33
|
-
open.push(harness)
|
|
34
|
-
harness.accounts.create('Main', 'apiKey')
|
|
35
|
-
const account = harness.accounts.context(harness.accounts.resolve(null, null))
|
|
36
|
-
writeFile(join(account.configDir, 'extensions/kit.ts'), EXTENSION)
|
|
37
|
-
|
|
38
|
-
const notices: ExtensionNotice[] = []
|
|
39
|
-
const asked: QuestionInput[] = []
|
|
40
|
-
const gated: string[] = []
|
|
41
|
-
const params: TurnParams = {
|
|
42
|
-
runId: 'r1',
|
|
43
|
-
cwd: base,
|
|
44
|
-
sessionDir: join(base, 'sessions'),
|
|
45
|
-
historyFile: null,
|
|
46
|
-
model: null,
|
|
47
|
-
text: '',
|
|
48
|
-
account,
|
|
49
|
-
system: '',
|
|
50
|
-
update: null,
|
|
51
|
-
plugins: { loading: () => harness.plugins.loading({ cwd: base, agentDir: account.configDir }), loaded: (extensions) => harness.plugins.loaded(extensions) }
|
|
52
|
-
}
|
|
53
|
-
const session = await piSessionFactory({
|
|
54
|
-
params,
|
|
55
|
-
allow: ['read'],
|
|
56
|
-
gate: { check: async () => null, ask: (names) => void gated.push(...names) },
|
|
57
|
-
tools: [],
|
|
58
|
-
runtime: () => harness.accounts.runtime(account),
|
|
59
|
-
extensions: { ask: async (input) => (asked.push(input), { selected: ['Yes'] }), notice: (notice) => void notices.push(notice) }
|
|
60
|
-
})
|
|
61
|
-
try {
|
|
62
|
-
expect(notices).toEqual([{ kind: 'status', key: 'kit', text: 'ready in rpc' }])
|
|
63
|
-
expect(gated).toEqual(['kit_scan'])
|
|
64
|
-
expect(session.getActiveToolNames?.()).toEqual(expect.arrayContaining(['read', 'kit_scan']))
|
|
65
|
-
expect(session.getActiveToolNames?.()).not.toContain('bash')
|
|
66
|
-
|
|
67
|
-
await session.prompt('/hello there')
|
|
68
|
-
expect(asked).toEqual([{ question: 'Say hello?\n\nthere', options: [{ label: 'Yes' }, { label: 'No' }], multiSelect: false, allowText: false }])
|
|
69
|
-
expect(notices.at(-1)).toEqual({ kind: 'notify', level: 'info', text: 'hello there' })
|
|
70
|
-
expect(Object.values(harness.plugins.registered())[0]).toMatchObject({ tools: ['kit_scan'], commands: ['hello'], hooks: ['session_start'] })
|
|
71
|
-
} finally {
|
|
72
|
-
session.dispose()
|
|
73
|
-
}
|
|
74
|
-
})
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { extensionUi, type ExtensionNotice } from './extensionUi'
|
|
4
|
-
import type { QuestionAnswer, QuestionInput } from './registry'
|
|
5
|
-
|
|
6
|
-
function host(answer: QuestionAnswer | Promise<QuestionAnswer>) {
|
|
7
|
-
const asked: QuestionInput[] = []
|
|
8
|
-
const notices: ExtensionNotice[] = []
|
|
9
|
-
const ui = extensionUi({ ask: async (input) => (asked.push(input), answer), notice: (notice) => void notices.push(notice) })
|
|
10
|
-
return { ui, asked, notices }
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
describe('what an extension can ask and show without a terminal', () => {
|
|
14
|
-
it('turns a choice, a yes or no, and typed text into questions', async () => {
|
|
15
|
-
const choice = host({ selected: ['Block'] })
|
|
16
|
-
expect(await choice.ui.select('Allow this command?', ['Allow', 'Block'])).toBe('Block')
|
|
17
|
-
expect(choice.asked[0]).toEqual({ question: 'Allow this command?', options: [{ label: 'Allow' }, { label: 'Block' }], multiSelect: false, allowText: false })
|
|
18
|
-
|
|
19
|
-
const yes = host({ selected: ['Yes'] })
|
|
20
|
-
expect(await yes.ui.confirm('Clear the session?', 'All messages will be lost.')).toBe(true)
|
|
21
|
-
expect(yes.asked[0]).toMatchObject({ question: 'Clear the session?\n\nAll messages will be lost.', options: [{ label: 'Yes' }, { label: 'No' }] })
|
|
22
|
-
expect(await host({ selected: ['No'] }).ui.confirm('Sure?', '')).toBe(false)
|
|
23
|
-
// No answer is a no, never a yes.
|
|
24
|
-
expect(await host({ selected: [] }).ui.confirm('Sure?', '')).toBe(false)
|
|
25
|
-
|
|
26
|
-
const typed = host({ selected: [], text: 'main' })
|
|
27
|
-
expect(await typed.ui.input('Branch', 'a name')).toBe('main')
|
|
28
|
-
expect(typed.asked[0]).toMatchObject({ question: 'Branch (a name)', options: [], allowText: true })
|
|
29
|
-
expect(await host({ selected: [] }).ui.input('Branch')).toBeUndefined()
|
|
30
|
-
expect(await host({ selected: [], text: 'edited' }).ui.editor('Edit', 'before')).toBe('edited')
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('gives up a dialog when the extension dismisses it or its time runs out', async () => {
|
|
34
|
-
const never = new Promise<QuestionAnswer>(() => undefined)
|
|
35
|
-
expect(await host(never).ui.confirm('Sure?', '', { timeout: 10 })).toBe(false)
|
|
36
|
-
const controller = new AbortController()
|
|
37
|
-
const pending = host(never).ui.select('Pick', ['a'], { signal: controller.signal })
|
|
38
|
-
controller.abort()
|
|
39
|
-
expect(await pending).toBeUndefined()
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('relays notifications, status lines and text widgets, and ignores what needs a terminal', async () => {
|
|
43
|
-
const { ui, notices } = host({ selected: [] })
|
|
44
|
-
ui.notify('Blocked', 'warning')
|
|
45
|
-
ui.setStatus('budget', '3 of 10 requests')
|
|
46
|
-
ui.setStatus('budget', undefined)
|
|
47
|
-
ui.setWidget('plan', ['One', 'Two'])
|
|
48
|
-
ui.setWidget('plan', (() => ({})) as never)
|
|
49
|
-
ui.setTitle('Review')
|
|
50
|
-
ui.setFooter(undefined)
|
|
51
|
-
ui.setWorkingMessage('thinking')
|
|
52
|
-
expect(notices).toEqual([
|
|
53
|
-
{ kind: 'notify', level: 'warning', text: 'Blocked' },
|
|
54
|
-
{ kind: 'status', key: 'budget', text: '3 of 10 requests' },
|
|
55
|
-
{ kind: 'status', key: 'budget', text: '' },
|
|
56
|
-
{ kind: 'widget', key: 'plan', text: 'One\nTwo' },
|
|
57
|
-
{ kind: 'title', text: 'Review' }
|
|
58
|
-
])
|
|
59
|
-
expect(await ui.custom((() => ({})) as never)).toBeUndefined()
|
|
60
|
-
expect(ui.getEditorText()).toBe('')
|
|
61
|
-
// Styling returns the text unchanged: there is nothing to colour.
|
|
62
|
-
expect((ui.theme as unknown as { fg(color: string, text: string): string }).fg('accent', 'plain')).toBe('plain')
|
|
63
|
-
expect(ui.setTheme('dark')).toMatchObject({ success: false })
|
|
64
|
-
})
|
|
65
|
-
})
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
// What a Pi extension can show and ask in a session that has no terminal. Pi's own terminal
|
|
2
|
-
// draws an extension's dialogs and status lines; here they become the run's question events and
|
|
3
|
-
// notices, the same subset Pi's RPC mode supports. Anything that needs a real terminal (custom
|
|
4
|
-
// components, key handlers, themes) does nothing, as it does there.
|
|
5
|
-
import type { ExtensionUIContext } from '@earendil-works/pi-coding-agent'
|
|
6
|
-
|
|
7
|
-
import type { QuestionAnswer, QuestionInput } from './registry'
|
|
8
|
-
|
|
9
|
-
/** A status line, a notification, or an error from an extension. */
|
|
10
|
-
export interface ExtensionNotice {
|
|
11
|
-
kind: 'notify' | 'status' | 'widget' | 'title' | 'error'
|
|
12
|
-
/** `notify` only. */
|
|
13
|
-
level?: 'info' | 'warning' | 'error'
|
|
14
|
-
/** The extension's own name for a status entry or a widget, so a later one replaces it. */
|
|
15
|
-
key?: string
|
|
16
|
-
/** The text. Empty clears a status entry or a widget. */
|
|
17
|
-
text: string
|
|
18
|
-
/** The file of the extension, for an error. */
|
|
19
|
-
extension?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ExtensionHost {
|
|
23
|
-
ask(input: QuestionInput): Promise<QuestionAnswer>
|
|
24
|
-
notice(notice: ExtensionNotice): void
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const MAX_TEXT = 4_000
|
|
28
|
-
const clip = (text: string): string => (text.length > MAX_TEXT ? `${text.slice(0, MAX_TEXT)}…` : text)
|
|
29
|
-
|
|
30
|
-
/** A dialog that Pi can dismiss on its own: by a signal, or after a time. */
|
|
31
|
-
function dismissable<T>(answer: Promise<T>, fallback: T, options?: { signal?: AbortSignal; timeout?: number }): Promise<T> {
|
|
32
|
-
if (!options?.signal && !options?.timeout) return answer
|
|
33
|
-
return new Promise<T>((resolve) => {
|
|
34
|
-
let timer: ReturnType<typeof setTimeout> | undefined
|
|
35
|
-
const done = (value: T): void => {
|
|
36
|
-
if (timer) clearTimeout(timer)
|
|
37
|
-
resolve(value)
|
|
38
|
-
}
|
|
39
|
-
if (options.signal?.aborted) return done(fallback)
|
|
40
|
-
options.signal?.addEventListener('abort', () => done(fallback), { once: true })
|
|
41
|
-
if (options.timeout) timer = setTimeout(() => done(fallback), options.timeout)
|
|
42
|
-
void answer.then(done, () => done(fallback))
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Text styling has no meaning without a terminal: every style returns the text it was given.
|
|
47
|
-
const plainTheme = new Proxy({}, { get: () => (...args: unknown[]) => String(args[args.length - 1] ?? '') })
|
|
48
|
-
|
|
49
|
-
export function extensionUi(host: ExtensionHost): ExtensionUIContext {
|
|
50
|
-
const context = {
|
|
51
|
-
select: (title: string, options: string[], opts?: { signal?: AbortSignal; timeout?: number }) =>
|
|
52
|
-
dismissable(
|
|
53
|
-
host.ask({ question: clip(title), options: options.map((label) => ({ label })), multiSelect: false, allowText: false }).then((answer) => answer.selected[0]),
|
|
54
|
-
undefined,
|
|
55
|
-
opts
|
|
56
|
-
),
|
|
57
|
-
confirm: (title: string, message: string, opts?: { signal?: AbortSignal; timeout?: number }) =>
|
|
58
|
-
dismissable(
|
|
59
|
-
host.ask({ question: clip(message ? `${title}\n\n${message}` : title), options: [{ label: 'Yes' }, { label: 'No' }], multiSelect: false, allowText: false }).then((answer) => answer.selected[0] === 'Yes'),
|
|
60
|
-
false,
|
|
61
|
-
opts
|
|
62
|
-
),
|
|
63
|
-
input: (title: string, placeholder?: string, opts?: { signal?: AbortSignal; timeout?: number }) =>
|
|
64
|
-
dismissable(
|
|
65
|
-
host.ask({ question: clip(placeholder ? `${title} (${placeholder})` : title), options: [], multiSelect: false, allowText: true }).then((answer) => answer.text || undefined),
|
|
66
|
-
undefined,
|
|
67
|
-
opts
|
|
68
|
-
),
|
|
69
|
-
editor: (title: string, prefill?: string) =>
|
|
70
|
-
host.ask({ question: clip(prefill ? `${title}\n\n${prefill}` : title), options: [], multiSelect: false, allowText: true }).then((answer) => answer.text || undefined),
|
|
71
|
-
notify: (message: string, level?: 'info' | 'warning' | 'error') => host.notice({ kind: 'notify', level: level ?? 'info', text: clip(message) }),
|
|
72
|
-
setStatus: (key: string, text: string | undefined) => host.notice({ kind: 'status', key, text: clip(text ?? '') }),
|
|
73
|
-
setWidget: (key: string, content: unknown) => {
|
|
74
|
-
// Only lines of text can be shown; a component needs a terminal.
|
|
75
|
-
if (content === undefined || Array.isArray(content)) host.notice({ kind: 'widget', key, text: clip(((content as string[] | undefined) ?? []).join('\n')) })
|
|
76
|
-
},
|
|
77
|
-
setTitle: (title: string) => host.notice({ kind: 'title', text: clip(title) }),
|
|
78
|
-
onTerminalInput: () => () => {},
|
|
79
|
-
setWorkingMessage: () => {},
|
|
80
|
-
setWorkingVisible: () => {},
|
|
81
|
-
setWorkingIndicator: () => {},
|
|
82
|
-
setHiddenThinkingLabel: () => {},
|
|
83
|
-
setFooter: () => {},
|
|
84
|
-
setHeader: () => {},
|
|
85
|
-
custom: async () => undefined,
|
|
86
|
-
pasteToEditor: () => {},
|
|
87
|
-
setEditorText: () => {},
|
|
88
|
-
getEditorText: () => '',
|
|
89
|
-
addAutocompleteProvider: () => {},
|
|
90
|
-
setEditorComponent: () => {},
|
|
91
|
-
getEditorComponent: () => undefined,
|
|
92
|
-
get theme() {
|
|
93
|
-
return plainTheme
|
|
94
|
-
},
|
|
95
|
-
getAllThemes: () => [],
|
|
96
|
-
getTheme: () => undefined,
|
|
97
|
-
setTheme: () => ({ success: false, error: 'There is no terminal to theme in this session.' }),
|
|
98
|
-
getToolsExpanded: () => false,
|
|
99
|
-
setToolsExpanded: () => {}
|
|
100
|
-
}
|
|
101
|
-
return context as unknown as ExtensionUIContext
|
|
102
|
-
}
|
package/src/sessions/history.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// Which native history a session opens. The prompt compiler compares against this file and Pi
|
|
2
|
-
// opens it, so both always see the same conversation, including after a restart.
|
|
3
|
-
import { readFileSync, realpathSync } from 'node:fs'
|
|
4
|
-
import { isAbsolute, relative } from 'node:path'
|
|
5
|
-
|
|
6
|
-
import { HarnessError } from '../core/errors'
|
|
7
|
-
import { sessionFile } from '../prompt/compile'
|
|
8
|
-
|
|
9
|
-
export interface HistorySelection {
|
|
10
|
-
sessionDir: string
|
|
11
|
-
/** A recorded history to continue, with the native session id it must still carry. */
|
|
12
|
-
resume?: { sessionFile?: string | null; nativeSessionId?: string | null } | null
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const unavailable = (): HarnessError => new HarnessError('native_resume_unavailable', 'The saved conversation is unavailable; this reply cannot be continued.')
|
|
16
|
-
|
|
17
|
-
/** The session file's own id, when the file is a native history inside `sessionDir`. */
|
|
18
|
-
function nativeId(sessionDir: string, path: string): string {
|
|
19
|
-
const file = realpathSync(path)
|
|
20
|
-
const within = relative(realpathSync(sessionDir), file)
|
|
21
|
-
if (within.startsWith('..') || isAbsolute(within) || !file.endsWith('.jsonl')) throw new Error('Outside history')
|
|
22
|
-
const header = JSON.parse(readFileSync(file, 'utf8').split('\n', 1)[0]) as { type?: string; id?: string }
|
|
23
|
-
if (header.type !== 'session' || !header.id) throw new Error('Missing session identity')
|
|
24
|
-
return header.id
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The history to open: the recorded one for an explicit continuation, else the newest in the
|
|
29
|
-
* session's folder, else none (a fresh history). An explicit continuation never falls back to
|
|
30
|
-
* another session or a fresh history.
|
|
31
|
-
*/
|
|
32
|
-
export function selectHistory(selection: HistorySelection): string | null {
|
|
33
|
-
const resume = selection.resume
|
|
34
|
-
if (resume?.sessionFile || resume?.nativeSessionId) {
|
|
35
|
-
try {
|
|
36
|
-
if (!resume.sessionFile || !resume.nativeSessionId) throw new Error('Missing history')
|
|
37
|
-
if (nativeId(selection.sessionDir, resume.sessionFile) !== resume.nativeSessionId) throw new Error('Different history')
|
|
38
|
-
return realpathSync(resume.sessionFile)
|
|
39
|
-
} catch {
|
|
40
|
-
throw unavailable()
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const newest = sessionFile(selection.sessionDir)
|
|
44
|
-
if (!newest) return null
|
|
45
|
-
try {
|
|
46
|
-
nativeId(selection.sessionDir, newest)
|
|
47
|
-
return realpathSync(newest)
|
|
48
|
-
} catch {
|
|
49
|
-
throw unavailable()
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { AgentBeforeSettleEvent, BoundaryResult, ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent'
|
|
2
|
-
|
|
3
|
-
import { takeChars } from '../core/text'
|
|
4
|
-
import { isActiveStatus } from './contracts'
|
|
5
|
-
import { readEvents, runFamily } from './runStore'
|
|
6
|
-
import type { InlineExtension } from '../plugins/firstParty'
|
|
7
|
-
|
|
8
|
-
interface RunSummary {
|
|
9
|
-
id: string
|
|
10
|
-
status: string
|
|
11
|
-
goal: string | null
|
|
12
|
-
terminalReason: string | null
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function directChildren(root: string, runId: string): RunSummary[] {
|
|
16
|
-
try {
|
|
17
|
-
return runFamily(root, runId).runs
|
|
18
|
-
.map(({ run }) => run)
|
|
19
|
-
.filter((run) => run.parentRunId === runId)
|
|
20
|
-
.map((run) => ({ id: run.id, status: run.status, goal: run.goal, terminalReason: run.terminalReason }))
|
|
21
|
-
} catch {
|
|
22
|
-
return []
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function settled(root: string, runId: string): boolean {
|
|
27
|
-
return directChildren(root, runId).every((child) => !isActiveStatus(child.status))
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function alreadyIncluded(event: AgentBeforeSettleEvent, runId: string): boolean {
|
|
31
|
-
return JSON.stringify(event.context.contextMessages).includes(runId)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function childReport(root: string, child: RunSummary): string {
|
|
35
|
-
const reply = readEvents(root, child.id)
|
|
36
|
-
.filter((event) => event.type === 'message.completed' && (event.payload as { role?: unknown }).role === 'assistant')
|
|
37
|
-
.at(-1)
|
|
38
|
-
const payload = reply?.payload as { text?: unknown } | undefined
|
|
39
|
-
const text = payload && typeof payload.text === 'string' ? takeChars(payload.text, 8000) : ''
|
|
40
|
-
const lines = [`Run ${child.id} · ${child.status}`, `Goal: ${child.goal?.trim() || '(not recorded)'}`]
|
|
41
|
-
if (text) lines.push(`Result:\n${text}`)
|
|
42
|
-
if (child.status !== 'completed' && child.terminalReason) lines.push(`Stopped because: ${child.terminalReason}`)
|
|
43
|
-
return lines.join('\n')
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function waitForChildren(root: string, runId: string, signal?: AbortSignal): Promise<boolean> {
|
|
47
|
-
while (!signal?.aborted) {
|
|
48
|
-
if (settled(root, runId)) return true
|
|
49
|
-
await new Promise<void>((resolve) => setTimeout(resolve, 250))
|
|
50
|
-
}
|
|
51
|
-
return false
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function install(pi: ExtensionAPI, root: string, runId: string): void {
|
|
55
|
-
pi.on('agent_before_settle', async (event, context: ExtensionContext): Promise<BoundaryResult | undefined> => {
|
|
56
|
-
if (event.outcome !== 'completed' || settled(root, runId)) return undefined
|
|
57
|
-
const completed = await waitForChildren(root, runId, context.signal)
|
|
58
|
-
if (!completed || context.signal?.aborted) return undefined
|
|
59
|
-
|
|
60
|
-
const unreported = directChildren(root, runId).filter((child) => !alreadyIncluded(event, child.id))
|
|
61
|
-
if (!unreported.length) return undefined
|
|
62
|
-
const summary = takeChars(
|
|
63
|
-
`Independent run results. Include each result or failure in the reply; the user may open each run for its full history.\n\n${unreported.map((child) => childReport(root, child)).join('\n\n')}`,
|
|
64
|
-
32_000
|
|
65
|
-
)
|
|
66
|
-
return {
|
|
67
|
-
entries: [...event.entries, { type: 'custom_message', customType: 'run-lineage-results', content: summary, display: false }],
|
|
68
|
-
continue: true
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function runLineageJoin(root: string, runId: string): InlineExtension {
|
|
74
|
-
return { name: 'run-lineage-join', factory: (pi: ExtensionAPI) => install(pi, root, runId) }
|
|
75
|
-
}
|
package/src/sessions/policy.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// What the embedding application decides about a session's tool calls. The harness asks; it
|
|
2
|
-
// never assumes: with no policy, every enabled tool may run and gated tools ask the user.
|
|
3
|
-
import type { CallerContext } from '../memory/caller'
|
|
4
|
-
import type { ApprovalFacts } from './approvals'
|
|
5
|
-
import type { RunRecord } from './contracts'
|
|
6
|
-
|
|
7
|
-
export interface ToolCall {
|
|
8
|
-
run: RunRecord
|
|
9
|
-
caller: CallerContext
|
|
10
|
-
tool: string
|
|
11
|
-
args: unknown
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface ScopeDecision {
|
|
15
|
-
/** The run is unattended: nothing may ask a person, so what is not allowed is refused. */
|
|
16
|
-
automatic?: boolean
|
|
17
|
-
allowed: boolean
|
|
18
|
-
/** Not allowed as it stands, but a person may approve this one call. */
|
|
19
|
-
requiresApproval?: boolean
|
|
20
|
-
reason?: string | null
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface ToolPolicy {
|
|
24
|
-
/** Whether the call is inside what this run may do. Asked before every tool call. */
|
|
25
|
-
scope?(call: ToolCall): Promise<ScopeDecision>
|
|
26
|
-
/** Whether the tool is still switched on. Asked before a call and again after an approval; throws to block. */
|
|
27
|
-
authorize?(call: ToolCall): Promise<void>
|
|
28
|
-
/** Facts about a pending approval that only the application knows. */
|
|
29
|
-
approval?(input: { run: RunRecord; target: string; operation?: { tool: string; args: unknown } }): Promise<ApprovalFacts>
|
|
30
|
-
/** The connected apps this session may reach, out of those its scope already allows. */
|
|
31
|
-
connections?(input: { sessionId: string; runId: string | null; caller: CallerContext; connectionIds: string[] }): Promise<string[]>
|
|
32
|
-
}
|
package/src/sessions/pricing.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// List-price cost of token usage, for when a provider reports tokens but no cost.
|
|
2
|
-
import type { ModelRuntime } from '@earendil-works/pi-coding-agent'
|
|
3
|
-
|
|
4
|
-
/** Dollars per million tokens. */
|
|
5
|
-
export interface PriceRates {
|
|
6
|
-
input: number
|
|
7
|
-
output: number
|
|
8
|
-
cacheRead: number
|
|
9
|
-
cacheWrite: number
|
|
10
|
-
/** The highest tier whose threshold the request's whole input exceeds replaces the base rates. */
|
|
11
|
-
tiers?: Array<{ input: number; output: number; cacheRead: number; cacheWrite: number; inputTokensAbove: number }>
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface PricedTokens {
|
|
15
|
-
inputTokens?: number
|
|
16
|
-
outputTokens?: number
|
|
17
|
-
cacheReadTokens?: number
|
|
18
|
-
cacheWriteTokens?: number
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** The cost of one request at `rates`. Same arithmetic as Pi's own `calculateCost`. */
|
|
22
|
-
export function costAt(rates: PriceRates, usage: PricedTokens): number {
|
|
23
|
-
const input = usage.inputTokens ?? 0
|
|
24
|
-
const cacheRead = usage.cacheReadTokens ?? 0
|
|
25
|
-
const cacheWrite = usage.cacheWriteTokens ?? 0
|
|
26
|
-
const whole = input + cacheRead + cacheWrite
|
|
27
|
-
let applied: Omit<PriceRates, 'tiers'> = rates
|
|
28
|
-
let threshold = -1
|
|
29
|
-
for (const tier of rates.tiers ?? []) {
|
|
30
|
-
if (whole > tier.inputTokensAbove && tier.inputTokensAbove > threshold) {
|
|
31
|
-
applied = tier
|
|
32
|
-
threshold = tier.inputTokensAbove
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return (applied.input * input + applied.output * (usage.outputTokens ?? 0) + applied.cacheRead * cacheRead + applied.cacheWrite * cacheWrite) / 1_000_000
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
type Catalogue = { getModel(provider: string, model: string): { cost?: PriceRates } | undefined }
|
|
39
|
-
let catalogue: Promise<Catalogue> | null = null
|
|
40
|
-
|
|
41
|
-
async function loadCatalogue(): Promise<Catalogue> {
|
|
42
|
-
const { ModelRuntime } = await import('@earendil-works/pi-coding-agent')
|
|
43
|
-
const none = { read: async () => undefined, list: async () => [], modify: async () => undefined, delete: async () => undefined }
|
|
44
|
-
return (await ModelRuntime.create({ credentials: none, modelsPath: null, refreshOnCreate: false })) as ModelRuntime as unknown as Catalogue
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* The list rates Pi's catalogue has for a model, or null when it has none. A dated model id
|
|
49
|
-
* (`…-20250929`) also matches its undated entry. Rates of zero are "unknown", not "free".
|
|
50
|
-
*/
|
|
51
|
-
export async function modelRates(provider: string | undefined, model: string | undefined, lookup: Promise<Catalogue> = (catalogue ??= loadCatalogue())): Promise<PriceRates | null> {
|
|
52
|
-
if (!provider || !model) return null
|
|
53
|
-
const models = await lookup.catch(() => null)
|
|
54
|
-
if (!models) return null
|
|
55
|
-
for (const id of new Set([model, model.replace(/-\d{8}$/, '')])) {
|
|
56
|
-
const cost = models.getModel(provider, id)?.cost
|
|
57
|
-
if (cost && (cost.input > 0 || cost.output > 0)) return cost
|
|
58
|
-
}
|
|
59
|
-
return null
|
|
60
|
-
}
|