@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
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// Accounts. One profile (settings, registry, memory) and any number of Pi accounts, each with
|
|
2
|
+
// its own isolated agent folder, stored credentials, sign-in state, limits, health, and audit
|
|
3
|
+
// trail.
|
|
4
|
+
//
|
|
5
|
+
// Device-local layout under `<state dir>/accounts/`:
|
|
6
|
+
// accounts.json accounts, the default, pools (no secrets)
|
|
7
|
+
// <id>/state.json health, plan, limits, last error (no secrets)
|
|
8
|
+
// <id>/home/pi/ Pi's own agent folder for this account
|
|
9
|
+
// _shared/pi/ settings, skills, prompts linked into every account's agent folder
|
|
10
|
+
// audit.jsonl append-only account actions (secrets scrubbed)
|
|
11
|
+
//
|
|
12
|
+
// Switching is always manual. A pool only chooses an account when a session starts; a running
|
|
13
|
+
// or resumed session keeps the account it started with.
|
|
14
|
+
import { mkdirSync, readFileSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { HarnessError, invalid, notFound } from "../core/errors.js";
|
|
17
|
+
import { appendLine, newId, now, readJsonOr, safeId, writeJson } from "../core/fs.js";
|
|
18
|
+
import { charCount, compareText, lines, takeChars } from "../core/text.js";
|
|
19
|
+
/** The only provider the harness runs. Records of other providers are kept in the file and ignored. */
|
|
20
|
+
export const PROVIDER = 'pi';
|
|
21
|
+
export const emptyState = () => ({ health: 'unknown', signedIn: null, authMethod: null, plan: null, identity: null, limits: [], lastError: null, lastCheckedAt: null, cliVersion: null, capabilities: [], piProviders: [] });
|
|
22
|
+
// ---- Files ------------------------------------------------------------------------------
|
|
23
|
+
const registryPath = (dir) => join(dir, 'accounts.json');
|
|
24
|
+
const statePath = (dir, accountId) => join(dir, safeId(accountId), 'state.json');
|
|
25
|
+
export function loadRegistry(dir) {
|
|
26
|
+
const stored = readJsonOr(registryPath(dir), {}) ?? {};
|
|
27
|
+
return { accounts: stored.accounts ?? [], defaults: stored.defaults ?? {}, pools: stored.pools ?? [], migratedProviderKeys: stored.migratedProviderKeys ?? false };
|
|
28
|
+
}
|
|
29
|
+
export function saveRegistry(dir, registry) {
|
|
30
|
+
mkdirSync(dir, { recursive: true });
|
|
31
|
+
writeJson(registryPath(dir), registry);
|
|
32
|
+
}
|
|
33
|
+
export function loadState(dir, accountId) {
|
|
34
|
+
try {
|
|
35
|
+
return { ...emptyState(), ...(readJsonOr(statePath(dir, accountId), {}) ?? {}) };
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return emptyState();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function saveState(dir, accountId, state) {
|
|
42
|
+
writeJson(statePath(dir, accountId), state);
|
|
43
|
+
}
|
|
44
|
+
/** Replaces anything that looks like a token so audit lines and stored errors never hold secrets. */
|
|
45
|
+
export function scrub(text) {
|
|
46
|
+
const words = text.split(' ').map((word) => {
|
|
47
|
+
const bytes = Buffer.byteLength(word);
|
|
48
|
+
const tokenLike = bytes >= 24 && (word.match(/[A-Za-z0-9]/g)?.length ?? 0) * 10 >= bytes * 8;
|
|
49
|
+
return tokenLike || word.startsWith('sk-') || word.startsWith('sess-') ? '[redacted]' : word;
|
|
50
|
+
});
|
|
51
|
+
return takeChars(words.join(' '), 500);
|
|
52
|
+
}
|
|
53
|
+
export function audit(dir, accountId, action, detail) {
|
|
54
|
+
try {
|
|
55
|
+
mkdirSync(dir, { recursive: true });
|
|
56
|
+
appendLine(join(dir, 'audit.jsonl'), JSON.stringify({ at: now(), accountId, action, detail: scrub(detail) }));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// An audit line that cannot be written never fails the action it describes.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** The newest audit entries first, optionally for one account. */
|
|
63
|
+
export function readAudit(dir, accountId, limit = 100) {
|
|
64
|
+
let text = '';
|
|
65
|
+
try {
|
|
66
|
+
text = readFileSync(join(dir, 'audit.jsonl'), 'utf8');
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
text = '';
|
|
70
|
+
}
|
|
71
|
+
const entries = lines(text).flatMap((line) => {
|
|
72
|
+
try {
|
|
73
|
+
const entry = JSON.parse(line);
|
|
74
|
+
return entry && typeof entry.at === 'string' && typeof entry.action === 'string' && (!accountId || entry.accountId === accountId) ? [entry] : [];
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return entries.reverse().slice(0, Math.min(limit, 1000));
|
|
81
|
+
}
|
|
82
|
+
// ---- Health -----------------------------------------------------------------------------
|
|
83
|
+
function inFuture(timestamp) {
|
|
84
|
+
const time = timestamp ? Date.parse(timestamp) : Number.NaN;
|
|
85
|
+
return !Number.isNaN(time) && time > Date.now();
|
|
86
|
+
}
|
|
87
|
+
// How long a limit error that came without a reset time keeps the account limited. Providers
|
|
88
|
+
// report no limit windows, so nothing else would ever lift it.
|
|
89
|
+
const UNTIMED_LIMIT_ERROR_SECS = 3600;
|
|
90
|
+
export const isLimitCategory = (category) => category === 'rate_limited' || category === 'quota';
|
|
91
|
+
/**
|
|
92
|
+
* Whether a `rate_limited` or `quota` error still counts. With a reset time it counts until
|
|
93
|
+
* then; without one it counts for a short while after it was recorded.
|
|
94
|
+
*/
|
|
95
|
+
export function limitErrorActive(issue) {
|
|
96
|
+
if (issue.resetsAt)
|
|
97
|
+
return inFuture(issue.resetsAt);
|
|
98
|
+
const at = Date.parse(issue.at);
|
|
99
|
+
return !Number.isNaN(at) && Math.trunc((Date.now() - at) / 1000) < UNTIMED_LIMIT_ERROR_SECS;
|
|
100
|
+
}
|
|
101
|
+
export function deriveHealth(state) {
|
|
102
|
+
const limitedByWindow = state.limits.some((limit) => (limit.status === 'rejected' || (limit.usedPercent ?? 0) >= 100) && (!limit.resetsAt || inFuture(limit.resetsAt)));
|
|
103
|
+
const issue = state.lastError;
|
|
104
|
+
if (issue?.category === 'auth_expired')
|
|
105
|
+
return 'auth_expired';
|
|
106
|
+
if (issue && isLimitCategory(issue.category) && limitErrorActive(issue))
|
|
107
|
+
return 'limited';
|
|
108
|
+
if (state.signedIn === false)
|
|
109
|
+
return 'signed_out';
|
|
110
|
+
if (limitedByWindow)
|
|
111
|
+
return 'limited';
|
|
112
|
+
if (issue && !isLimitCategory(issue.category))
|
|
113
|
+
return 'error';
|
|
114
|
+
return state.signedIn === true ? 'ok' : 'unknown';
|
|
115
|
+
}
|
|
116
|
+
/** Read-modify-write on one account's state. Returns the previous health and the new state. */
|
|
117
|
+
export function updateState(dir, accountId, change) {
|
|
118
|
+
const state = loadState(dir, accountId);
|
|
119
|
+
const previous = state.health;
|
|
120
|
+
change(state);
|
|
121
|
+
state.health = deriveHealth(state);
|
|
122
|
+
saveState(dir, accountId, state);
|
|
123
|
+
return { previous, state };
|
|
124
|
+
}
|
|
125
|
+
// ---- Lookup and pinning -----------------------------------------------------------------
|
|
126
|
+
export function find(dir, accountId) {
|
|
127
|
+
const account = loadRegistry(dir).accounts.find((entry) => entry.id === accountId);
|
|
128
|
+
if (!account)
|
|
129
|
+
throw new HarnessError('account_required', "This session's account was removed.", 'Choose another account for it.');
|
|
130
|
+
return account;
|
|
131
|
+
}
|
|
132
|
+
const usable = (state) => state.health === 'ok' || state.health === 'unknown';
|
|
133
|
+
/**
|
|
134
|
+
* Chooses the account for a new session: the explicit account, else the pool's first usable
|
|
135
|
+
* account, else the default, else the first account. It never skips to another one silently.
|
|
136
|
+
*/
|
|
137
|
+
export function resolve(dir, accountId, poolId) {
|
|
138
|
+
const registry = loadRegistry(dir);
|
|
139
|
+
if (accountId) {
|
|
140
|
+
const account = find(dir, accountId);
|
|
141
|
+
if (account.provider !== PROVIDER)
|
|
142
|
+
throw invalid('That account belongs to another provider.');
|
|
143
|
+
return account;
|
|
144
|
+
}
|
|
145
|
+
if (poolId) {
|
|
146
|
+
const pool = registry.pools.find((entry) => entry.id === poolId);
|
|
147
|
+
if (!pool)
|
|
148
|
+
throw notFound('Pool');
|
|
149
|
+
const members = pool.accountIds.flatMap((id) => registry.accounts.find((account) => account.id === id) ?? []);
|
|
150
|
+
const ready = members.find((account) => usable(loadState(dir, account.id)));
|
|
151
|
+
if (ready)
|
|
152
|
+
return ready;
|
|
153
|
+
const resets = members.flatMap((account) => {
|
|
154
|
+
const state = loadState(dir, account.id);
|
|
155
|
+
return [...state.limits.map((limit) => limit.resetsAt), state.lastError?.resetsAt].filter((reset) => Boolean(reset));
|
|
156
|
+
});
|
|
157
|
+
const earliest = resets.sort(compareText)[0];
|
|
158
|
+
throw new HarnessError('account_unavailable', `Every account in ${pool.label} is unavailable.`, earliest ? `The earliest reset is ${earliest}.` : 'Reauthenticate an account in Accounts.');
|
|
159
|
+
}
|
|
160
|
+
const account = registry.accounts.find((entry) => entry.id === registry.defaults[PROVIDER]) ?? registry.accounts.find((entry) => entry.provider === PROVIDER);
|
|
161
|
+
if (!account)
|
|
162
|
+
throw new HarnessError('account_required', 'Add a Pi account first.', 'Open Accounts (⌘4) to sign in.');
|
|
163
|
+
return account;
|
|
164
|
+
}
|
|
165
|
+
/** The default account, if any exists. */
|
|
166
|
+
export function defaultAccount(dir) {
|
|
167
|
+
try {
|
|
168
|
+
return resolve(dir);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export function summaryAuthentication(dir) {
|
|
175
|
+
const states = loadRegistry(dir).accounts.filter((account) => account.provider === PROVIDER).map((account) => loadState(dir, account.id));
|
|
176
|
+
if (states.some((state) => state.signedIn === true))
|
|
177
|
+
return 'authenticated';
|
|
178
|
+
return states.some((state) => state.signedIn === false) ? 'unauthenticated' : 'unknown';
|
|
179
|
+
}
|
|
180
|
+
export function overview(dir) {
|
|
181
|
+
const registry = loadRegistry(dir);
|
|
182
|
+
return {
|
|
183
|
+
accounts: registry.accounts.filter((account) => account.provider === PROVIDER).map((account) => ({ ...account, isDefault: registry.defaults[account.provider] === account.id, state: loadState(dir, account.id) })),
|
|
184
|
+
pools: registry.pools.filter((pool) => pool.provider === PROVIDER),
|
|
185
|
+
defaults: registry.defaults
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
// ---- Changes ----------------------------------------------------------------------------
|
|
189
|
+
export function cleanLabel(label) {
|
|
190
|
+
const cleaned = label.trim();
|
|
191
|
+
if (!cleaned || charCount(cleaned) > 60)
|
|
192
|
+
throw invalid('Give the account a name of 1–60 characters.');
|
|
193
|
+
return cleaned;
|
|
194
|
+
}
|
|
195
|
+
export function addAccount(dir, label, authKind) {
|
|
196
|
+
if (authKind !== 'subscription' && authKind !== 'apiKey')
|
|
197
|
+
throw invalid('Unknown sign-in type.');
|
|
198
|
+
const account = { id: newId(), provider: PROVIDER, label: cleanLabel(label), authKind, credentialStore: 'keyring', createdAt: now() };
|
|
199
|
+
const registry = loadRegistry(dir);
|
|
200
|
+
registry.defaults[PROVIDER] ??= account.id;
|
|
201
|
+
registry.accounts.push(account);
|
|
202
|
+
saveRegistry(dir, registry);
|
|
203
|
+
return account;
|
|
204
|
+
}
|
|
205
|
+
export function renameAccount(dir, accountId, label) {
|
|
206
|
+
const cleaned = cleanLabel(label);
|
|
207
|
+
const registry = loadRegistry(dir);
|
|
208
|
+
const account = registry.accounts.find((entry) => entry.id === accountId);
|
|
209
|
+
if (!account)
|
|
210
|
+
throw notFound('Account');
|
|
211
|
+
account.label = cleaned;
|
|
212
|
+
saveRegistry(dir, registry);
|
|
213
|
+
}
|
|
214
|
+
export function setDefault(dir, accountId) {
|
|
215
|
+
const account = find(dir, accountId);
|
|
216
|
+
const registry = loadRegistry(dir);
|
|
217
|
+
registry.defaults[account.provider] = account.id;
|
|
218
|
+
saveRegistry(dir, registry);
|
|
219
|
+
return account;
|
|
220
|
+
}
|
|
221
|
+
/** Drops the account from the registry, its pools, and the default. Its folder and secrets are the caller's to remove. */
|
|
222
|
+
export function forgetAccount(dir, account) {
|
|
223
|
+
const registry = loadRegistry(dir);
|
|
224
|
+
registry.accounts = registry.accounts.filter((entry) => entry.id !== account.id);
|
|
225
|
+
if (registry.defaults[account.provider] === account.id) {
|
|
226
|
+
const next = registry.accounts.find((entry) => entry.provider === account.provider);
|
|
227
|
+
if (next)
|
|
228
|
+
registry.defaults[account.provider] = next.id;
|
|
229
|
+
else
|
|
230
|
+
delete registry.defaults[account.provider];
|
|
231
|
+
}
|
|
232
|
+
for (const pool of registry.pools)
|
|
233
|
+
pool.accountIds = pool.accountIds.filter((id) => id !== account.id);
|
|
234
|
+
saveRegistry(dir, registry);
|
|
235
|
+
}
|
|
236
|
+
export function savePool(dir, pool) {
|
|
237
|
+
const label = cleanLabel(pool.label);
|
|
238
|
+
const registry = loadRegistry(dir);
|
|
239
|
+
for (const id of pool.accountIds) {
|
|
240
|
+
const account = registry.accounts.find((entry) => entry.id === id);
|
|
241
|
+
if (!account)
|
|
242
|
+
throw notFound('Account');
|
|
243
|
+
if (account.provider !== PROVIDER)
|
|
244
|
+
throw invalid('A pool holds accounts of one provider.');
|
|
245
|
+
}
|
|
246
|
+
const next = { id: pool.id ? safeId(pool.id) : newId(), provider: PROVIDER, label, accountIds: [...pool.accountIds] };
|
|
247
|
+
registry.pools = [...registry.pools.filter((existing) => existing.id !== next.id), next];
|
|
248
|
+
saveRegistry(dir, registry);
|
|
249
|
+
return next;
|
|
250
|
+
}
|
|
251
|
+
export function removePool(dir, poolId) {
|
|
252
|
+
const registry = loadRegistry(dir);
|
|
253
|
+
registry.pools = registry.pools.filter((pool) => pool.id !== poolId);
|
|
254
|
+
saveRegistry(dir, registry);
|
|
255
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface BrowserConfig {
|
|
2
|
+
/** Show the browser window instead of running it headless. */
|
|
3
|
+
headed: boolean;
|
|
4
|
+
/** Attach to a browser a person already runs (its DevTools address) instead of launching one. */
|
|
5
|
+
cdpUrl: string | null;
|
|
6
|
+
/** A browser program to launch; otherwise Chrome, then Edge, then Playwright's Chromium. */
|
|
7
|
+
executablePath: string | null;
|
|
8
|
+
/** Let pages reach this machine and private networks (local development servers). */
|
|
9
|
+
allowPrivate: boolean;
|
|
10
|
+
/** When set, pages may only reach these sites (and their subdomains). */
|
|
11
|
+
allowedDomains: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare const DEFAULT_BROWSER_CONFIG: BrowserConfig;
|
|
14
|
+
export declare const configPath: (stateDir: string) => string;
|
|
15
|
+
export declare function readBrowserConfig(stateDir: string): BrowserConfig;
|
|
16
|
+
export declare function writeBrowserConfig(stateDir: string, change: Partial<BrowserConfig>): BrowserConfig;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// How the harness's browser runs, kept on this device: `<state dir>/browser.json`. Only a person
|
|
2
|
+
// (through a command or the application) writes it; no tool argument can point the browser at
|
|
3
|
+
// another endpoint or profile.
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { readJsonOr, writeJson } from "../core/fs.js";
|
|
6
|
+
export const DEFAULT_BROWSER_CONFIG = { headed: false, cdpUrl: null, executablePath: null, allowPrivate: false, allowedDomains: [] };
|
|
7
|
+
export const configPath = (stateDir) => join(stateDir, 'browser.json');
|
|
8
|
+
export function readBrowserConfig(stateDir) {
|
|
9
|
+
const saved = readJsonOr(configPath(stateDir), {});
|
|
10
|
+
return {
|
|
11
|
+
headed: saved.headed === true,
|
|
12
|
+
cdpUrl: typeof saved.cdpUrl === 'string' && /^(?:https?|wss?):\/\/(?:127\.0\.0\.1|localhost|\[::1\])(?::\d+)?(?:\/|$)/.test(saved.cdpUrl) ? saved.cdpUrl : null,
|
|
13
|
+
executablePath: typeof saved.executablePath === 'string' && saved.executablePath.trim() ? saved.executablePath : null,
|
|
14
|
+
allowPrivate: saved.allowPrivate === true,
|
|
15
|
+
allowedDomains: Array.isArray(saved.allowedDomains) ? saved.allowedDomains.filter((domain) => typeof domain === 'string' && /^[a-z0-9.-]+$/i.test(domain)).map((domain) => domain.toLowerCase()) : []
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function writeBrowserConfig(stateDir, change) {
|
|
19
|
+
const next = { ...readBrowserConfig(stateDir), ...change };
|
|
20
|
+
writeJson(configPath(stateDir), next);
|
|
21
|
+
return readBrowserConfig(stateDir);
|
|
22
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BrowserConfig } from "./config.js";
|
|
2
|
+
import type { DoctorCheck } from "../doctor/types.js";
|
|
3
|
+
/** Exercises the production tool path against a synthetic page in a throwaway profile. */
|
|
4
|
+
export declare function browserDiagnostic(configure: () => BrowserConfig): DoctorCheck;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { accessSync, constants } from 'node:fs';
|
|
2
|
+
const outputText = (result) => typeof result === 'string' ? result : result.text;
|
|
3
|
+
/** Exercises the production tool path against a synthetic page in a throwaway profile. */
|
|
4
|
+
export function browserDiagnostic(configure) {
|
|
5
|
+
return {
|
|
6
|
+
id: 'browser', category: 'browser', title: 'Isolated browser',
|
|
7
|
+
tools: ['browser_navigate', 'browser_read', 'browser_snapshot', 'browser_click'],
|
|
8
|
+
local: async () => {
|
|
9
|
+
const config = configure();
|
|
10
|
+
if (config.executablePath)
|
|
11
|
+
accessSync(config.executablePath, constants.X_OK);
|
|
12
|
+
return { status: 'pass', evidence: `Browser tools are installed. ${config.cdpUrl ? 'A CDP endpoint is configured; diagnostics will not attach to it.' : 'A dedicated browser will be launched only after live-check confirmation.'} Browser launch is unverified.` };
|
|
13
|
+
},
|
|
14
|
+
live: {
|
|
15
|
+
notice: 'Launch a dedicated headless browser with a disposable profile, open an offline synthetic page, read its marker, and click its test button. No existing browser or profile is used.',
|
|
16
|
+
run: async (probe) => {
|
|
17
|
+
if (configure().cdpUrl)
|
|
18
|
+
return { status: 'blocked', evidence: 'This backend is configured to attach to a browser. Isolated diagnostics will not attach to it.', remedy: 'Use a dedicated launch configuration to test browser interaction.' };
|
|
19
|
+
const needed = ['browser_navigate', 'browser_read', 'browser_snapshot', 'browser_click'];
|
|
20
|
+
if (needed.some(name => !probe.context.tools.includes(name)))
|
|
21
|
+
return { status: 'blocked', evidence: 'The browser interaction test requires navigate, read, snapshot, and click capabilities.' };
|
|
22
|
+
const html = '<!doctype html><title>Harness Doctor</title><p>doctor-ready</p><button onclick="document.querySelector(\'p\').textContent=\'doctor-clicked\'">Diagnostic test</button>';
|
|
23
|
+
await probe.call('browser_navigate', { url: `data:text/html,${encodeURIComponent(html)}` });
|
|
24
|
+
const before = outputText(await probe.call('browser_read', {}));
|
|
25
|
+
if (!before.includes('doctor-ready'))
|
|
26
|
+
return { status: 'fail', evidence: 'The diagnostic page marker could not be read.' };
|
|
27
|
+
const snapshot = outputText(await probe.call('browser_snapshot', {}));
|
|
28
|
+
const line = snapshot.split('\n').find(line => line.includes('button') && line.includes('Diagnostic test'));
|
|
29
|
+
const ref = line && /\b(e\d+)\b/.exec(line)?.[1];
|
|
30
|
+
if (!ref)
|
|
31
|
+
return { status: 'fail', evidence: 'The test button was absent from the browser snapshot.' };
|
|
32
|
+
await probe.call('browser_click', { ref });
|
|
33
|
+
const after = outputText(await probe.call('browser_read', {}));
|
|
34
|
+
return after.includes('doctor-clicked') ? { status: 'pass', evidence: 'Navigation, reading, element discovery, and clicking succeeded in the disposable diagnostic browser.' } : { status: 'fail', evidence: 'Clicking did not change the expected page marker.' };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PluginDescriptor } from "../plugins/types.js";
|
|
2
|
+
import type { WorldPart } from "../world/world.js";
|
|
3
|
+
import { NetworkPolicy } from "./network.js";
|
|
4
|
+
import { BrowserSession, type Launcher } from "./session.js";
|
|
5
|
+
export declare const BROWSER_PLUGIN: PluginDescriptor;
|
|
6
|
+
/** A browser closes after this long without a call. */
|
|
7
|
+
export declare const IDLE_MS: number;
|
|
8
|
+
export interface BrowserPartOptions {
|
|
9
|
+
stateDir: string;
|
|
10
|
+
launch?: Launcher;
|
|
11
|
+
/** Replaces the address check in tests. */
|
|
12
|
+
network?: NetworkPolicy;
|
|
13
|
+
idleMs?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function browserPart(options: BrowserPartOptions): WorldPart & {
|
|
16
|
+
sessions: Map<string, BrowserSession>;
|
|
17
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// The browser, as one part of the harness's world tools: a plugin a person can switch off, and one
|
|
2
|
+
// live browser per session.
|
|
3
|
+
import { rmSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { readBrowserConfig } from "./config.js";
|
|
6
|
+
import { browserDiagnostic } from "./diagnostics.js";
|
|
7
|
+
import { NetworkPolicy } from "./network.js";
|
|
8
|
+
import { BrowserSession, fileName, launchBrowser } from "./session.js";
|
|
9
|
+
import { BROWSER_TOOL_NAMES, browserTools } from "./tools.js";
|
|
10
|
+
export const BROWSER_PLUGIN = {
|
|
11
|
+
id: 'browser',
|
|
12
|
+
title: 'Browser',
|
|
13
|
+
description: 'A browser the agent drives: open pages that need scripts, read them, click, type and choose. Each session has its own; sign-ins are kept per agent on this device. Sending, buying, deleting, account changes and granting access always ask you.',
|
|
14
|
+
manage: 'tools',
|
|
15
|
+
switchable: { on: true },
|
|
16
|
+
// Its tools are listed in the World group, which the Web plugin owns.
|
|
17
|
+
tools: BROWSER_TOOL_NAMES.map((name) => ({ name }))
|
|
18
|
+
};
|
|
19
|
+
/** A browser closes after this long without a call. */
|
|
20
|
+
export const IDLE_MS = 15 * 60_000;
|
|
21
|
+
export function browserPart(options) {
|
|
22
|
+
const sessions = new Map();
|
|
23
|
+
const diagnosticKeys = new Set();
|
|
24
|
+
const config = () => readBrowserConfig(options.stateDir);
|
|
25
|
+
const network = options.network ?? new NetworkPolicy(config);
|
|
26
|
+
const sessionFor = (context) => {
|
|
27
|
+
let session = sessions.get(context.sessionKey);
|
|
28
|
+
if (!session) {
|
|
29
|
+
const owner = context.caller.agentId ?? context.caller.projectId ?? 'default';
|
|
30
|
+
const key = context.sessionKey;
|
|
31
|
+
const diagnostic = context.caller.surface === 'doctor';
|
|
32
|
+
if (diagnostic)
|
|
33
|
+
diagnosticKeys.add(key);
|
|
34
|
+
session = new BrowserSession({
|
|
35
|
+
key,
|
|
36
|
+
profileDir: diagnostic ? null : join(options.stateDir, 'browser', 'profiles', fileName(owner)),
|
|
37
|
+
downloadsDir: join(options.stateDir, 'browser', 'downloads', fileName(key)),
|
|
38
|
+
config: diagnostic ? () => ({ ...config(), cdpUrl: null, headed: false }) : config,
|
|
39
|
+
network,
|
|
40
|
+
launch: options.launch ?? launchBrowser,
|
|
41
|
+
idleMs: options.idleMs ?? IDLE_MS,
|
|
42
|
+
onIdle: () => void release(key)
|
|
43
|
+
});
|
|
44
|
+
sessions.set(key, session);
|
|
45
|
+
}
|
|
46
|
+
return session;
|
|
47
|
+
};
|
|
48
|
+
const release = async (key) => {
|
|
49
|
+
const session = sessions.get(key);
|
|
50
|
+
sessions.delete(key);
|
|
51
|
+
await session?.close();
|
|
52
|
+
if (diagnosticKeys.delete(key))
|
|
53
|
+
rmSync(join(options.stateDir, 'browser', 'downloads', fileName(key)), { recursive: true, force: true });
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
plugin: BROWSER_PLUGIN,
|
|
57
|
+
diagnostics: () => [browserDiagnostic(config)],
|
|
58
|
+
sessions,
|
|
59
|
+
tools: (context) => browserTools(context, () => sessionFor(context)),
|
|
60
|
+
release,
|
|
61
|
+
close: async () => {
|
|
62
|
+
await Promise.all([...sessions.keys()].map(release));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BrowserConfig } from "./config.js";
|
|
2
|
+
export declare class NetworkPolicy {
|
|
3
|
+
private readonly config;
|
|
4
|
+
private readonly resolve;
|
|
5
|
+
private readonly hosts;
|
|
6
|
+
constructor(config: () => BrowserConfig, resolve?: (host: string) => Promise<string[]>);
|
|
7
|
+
/** Why an address may not be loaded, or null when it may. */
|
|
8
|
+
refusal(address: string): Promise<string | null>;
|
|
9
|
+
private isPublic;
|
|
10
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// What a page in the harness's browser may reach. The same rule as `web_fetch`: public addresses
|
|
2
|
+
// only, unless a person allowed private ones for local development. This filters the browser's
|
|
3
|
+
// requests; it is a guard, not a sandbox (a page's own DNS can still differ from ours).
|
|
4
|
+
import { lookup } from 'node:dns/promises';
|
|
5
|
+
import { isIP } from 'node:net';
|
|
6
|
+
import { isPublicAddress } from "../web/safeRequest.js";
|
|
7
|
+
const OPEN_SCHEMES = new Set(['data:', 'blob:', 'about:']);
|
|
8
|
+
const CACHE_MS = 60_000;
|
|
9
|
+
export class NetworkPolicy {
|
|
10
|
+
config;
|
|
11
|
+
resolve;
|
|
12
|
+
hosts = new Map();
|
|
13
|
+
constructor(config, resolve = async (host) => (await lookup(host, { all: true, verbatim: true })).map((entry) => entry.address)) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.resolve = resolve;
|
|
16
|
+
}
|
|
17
|
+
/** Why an address may not be loaded, or null when it may. */
|
|
18
|
+
async refusal(address) {
|
|
19
|
+
let url;
|
|
20
|
+
try {
|
|
21
|
+
url = new URL(address);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return 'That is not a web address.';
|
|
25
|
+
}
|
|
26
|
+
if (OPEN_SCHEMES.has(url.protocol))
|
|
27
|
+
return null;
|
|
28
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
29
|
+
return `${url.protocol} addresses cannot be opened.`;
|
|
30
|
+
if (url.username || url.password)
|
|
31
|
+
return 'Addresses with a username or password cannot be opened.';
|
|
32
|
+
const host = url.hostname.replace(/^\[|\]$/g, '').toLowerCase();
|
|
33
|
+
const { allowedDomains, allowPrivate } = this.config();
|
|
34
|
+
if (allowedDomains.length && !allowedDomains.some((domain) => host === domain || host.endsWith(`.${domain}`)))
|
|
35
|
+
return `${host} is not among the sites this browser may reach.`;
|
|
36
|
+
if (allowPrivate)
|
|
37
|
+
return null;
|
|
38
|
+
return (await this.isPublic(host)) ? null : `${host} is not a public address. A person can allow private addresses in the browser settings.`;
|
|
39
|
+
}
|
|
40
|
+
async isPublic(host) {
|
|
41
|
+
if (isIP(host))
|
|
42
|
+
return isPublicAddress(host);
|
|
43
|
+
if (host === 'localhost' || host.endsWith('.localhost'))
|
|
44
|
+
return false;
|
|
45
|
+
const cached = this.hosts.get(host);
|
|
46
|
+
if (cached && Date.now() - cached.at < CACHE_MS)
|
|
47
|
+
return cached.ok;
|
|
48
|
+
let ok = false;
|
|
49
|
+
try {
|
|
50
|
+
const addresses = await this.resolve(host);
|
|
51
|
+
ok = addresses.length > 0 && addresses.every(isPublicAddress);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
ok = false;
|
|
55
|
+
}
|
|
56
|
+
this.hosts.set(host, { at: Date.now(), ok });
|
|
57
|
+
return ok;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Browser, BrowserContext, Page } from 'playwright-core';
|
|
2
|
+
import type { BrowserConfig } from "./config.js";
|
|
3
|
+
import type { NetworkPolicy } from "./network.js";
|
|
4
|
+
/** Any name as a file or folder name: other characters become `_`. */
|
|
5
|
+
export declare const fileName: (name: string) => string;
|
|
6
|
+
export declare const VIEWPORT: {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
};
|
|
10
|
+
export declare const ACTION_TIMEOUT_MS = 15000;
|
|
11
|
+
export declare const NAVIGATION_TIMEOUT_MS = 30000;
|
|
12
|
+
export interface Tab {
|
|
13
|
+
id: string;
|
|
14
|
+
page: Page;
|
|
15
|
+
/** Bumped on every navigation of the tab's main frame: refs of an earlier generation are stale. */
|
|
16
|
+
generation: number;
|
|
17
|
+
/** Refs of the latest snapshot, with the snapshot line that names each. */
|
|
18
|
+
refs: Map<string, string>;
|
|
19
|
+
refsGeneration: number;
|
|
20
|
+
}
|
|
21
|
+
export interface Download {
|
|
22
|
+
tab: string;
|
|
23
|
+
path: string;
|
|
24
|
+
name: string;
|
|
25
|
+
}
|
|
26
|
+
/** Opens a browser context; tests replace it. */
|
|
27
|
+
export type Launcher = (input: {
|
|
28
|
+
profileDir: string | null;
|
|
29
|
+
downloads: string;
|
|
30
|
+
config: BrowserConfig;
|
|
31
|
+
}) => Promise<{
|
|
32
|
+
context: BrowserContext;
|
|
33
|
+
browser: Browser | null;
|
|
34
|
+
attached: boolean;
|
|
35
|
+
}>;
|
|
36
|
+
export declare const launchBrowser: Launcher;
|
|
37
|
+
export interface BrowserSessionOptions {
|
|
38
|
+
key: string;
|
|
39
|
+
/** The profile kept for this caller, or null for a throwaway one. */
|
|
40
|
+
profileDir: string | null;
|
|
41
|
+
downloadsDir: string;
|
|
42
|
+
config: () => BrowserConfig;
|
|
43
|
+
network: NetworkPolicy;
|
|
44
|
+
launch: Launcher;
|
|
45
|
+
idleMs: number;
|
|
46
|
+
onIdle: () => void;
|
|
47
|
+
}
|
|
48
|
+
export declare class BrowserSession {
|
|
49
|
+
private readonly options;
|
|
50
|
+
private closed;
|
|
51
|
+
private context;
|
|
52
|
+
private browser;
|
|
53
|
+
private attached;
|
|
54
|
+
private starting;
|
|
55
|
+
private readonly tabs;
|
|
56
|
+
private current;
|
|
57
|
+
private nextTab;
|
|
58
|
+
private queue;
|
|
59
|
+
private idle;
|
|
60
|
+
private temporaryProfile;
|
|
61
|
+
readonly downloads: Download[];
|
|
62
|
+
/** Set when the kept profile was in use and a throwaway one was opened instead. */
|
|
63
|
+
profileNote: string | null;
|
|
64
|
+
constructor(options: BrowserSessionOptions);
|
|
65
|
+
/** Runs `work` after every earlier call of this session has finished. */
|
|
66
|
+
run<T>(work: () => Promise<T>): Promise<T>;
|
|
67
|
+
private touch;
|
|
68
|
+
private start;
|
|
69
|
+
private adopt;
|
|
70
|
+
/** A tab by id, or the current one, starting the browser and opening a first tab when needed. */
|
|
71
|
+
tab(id?: string | null): Promise<Tab>;
|
|
72
|
+
open(): Promise<Tab>;
|
|
73
|
+
select(id: string): Tab;
|
|
74
|
+
list(): {
|
|
75
|
+
id: string;
|
|
76
|
+
url: string;
|
|
77
|
+
current: boolean;
|
|
78
|
+
}[];
|
|
79
|
+
/** What pages in this browser may reach. */
|
|
80
|
+
get network(): NetworkPolicy;
|
|
81
|
+
get currentId(): string | null;
|
|
82
|
+
get started(): boolean;
|
|
83
|
+
close(): Promise<void>;
|
|
84
|
+
}
|