@xingular/harness 0.1.7 → 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 +97 -1
- package/LICENSE +202 -0
- package/NOTICE.md +22 -0
- package/README.md +187 -85
- package/THIRD_PARTY_LICENSES.md +942 -0
- package/dist/cli.js +13701 -13498
- 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 +808 -151
- package/docs/README.md +22 -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-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 +473 -56
- package/package.json +43 -13
- package/docs/evidence/terminal-2026-10-17/120x40-completed-transcript.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17/40x20-settings-interaction.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17/40x20-settings-keyboard.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17/40x20-welcome.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17/80x24-approval-0.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17/80x24-question.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17/verification.json +0 -130
- package/docs/evidence/terminal-2026-10-17-followup/120x40-completed-transcript.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/40x20-settings-interaction.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/40x20-settings-keyboard.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/40x20-welcome.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/80x24-approval-0.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/80x24-plugin-light.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/80x24-question-batch.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/80x24-question.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/app-bundle/80x24-approval-0.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/app-bundle/80x24-plugin-light.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/app-bundle/80x24-question-batch.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/app-bundle/80x24-question.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/app-bundle/verification.json +0 -95
- package/docs/evidence/terminal-2026-10-17-followup/global-install/80x24-approval-0.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/global-install/80x24-plugin-light.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/global-install/80x24-question-batch.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/global-install/80x24-question.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/global-install/README.md +0 -27
- package/docs/evidence/terminal-2026-10-17-followup/global-install/shell-command-80x24.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/global-install/shell-command.json +0 -18
- package/docs/evidence/terminal-2026-10-17-followup/global-install/verification.json +0 -95
- package/docs/evidence/terminal-2026-10-17-followup/harness-reference-80x24.json.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/harness-reference-final-80x24.json.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/harness-reference-scenario.json +0 -60
- package/docs/evidence/terminal-2026-10-17-followup/live-codex/README.md +0 -24
- package/docs/evidence/terminal-2026-10-17-followup/live-codex/complete-80x24.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/live-codex/verification.json +0 -57
- package/docs/evidence/terminal-2026-10-17-followup/omp-reference-80x24-early.json.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/omp-reference-80x24.json.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/omp-reference-scenario.json +0 -58
- package/docs/evidence/terminal-2026-10-17-followup/omp-supplemental-static.json.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/rea-artifacts.json +0 -320
- package/docs/evidence/terminal-2026-10-17-followup/reference-comparison.json +0 -30
- package/docs/evidence/terminal-2026-10-17-followup/reference-comparison.md +0 -133
- package/docs/evidence/terminal-2026-10-17-followup/reference-source-observations.json +0 -20
- package/docs/evidence/terminal-2026-10-17-followup/verification.json +0 -207
- package/src/accounts/accounts.test.ts +0 -333
- package/src/accounts/accounts.ts +0 -658
- 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 -118
- 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 -312
- package/src/hub/catalog.json +0 -123
- package/src/hub/cli.ts +0 -59
- package/src/hub/hub.test.ts +0 -205
- package/src/hub/hub.ts +0 -256
- package/src/hub/schema.ts +0 -81
- package/src/index.ts +0 -75
- 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 -303
- package/src/memory/blocks.test.ts +0 -78
- package/src/memory/blocks.ts +0 -408
- package/src/memory/caller.ts +0 -69
- package/src/memory/curation.test.ts +0 -163
- package/src/memory/curation.ts +0 -64
- 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 -518
- package/src/memory/migrate.ts +0 -79
- package/src/memory/operations.ts +0 -150
- package/src/memory/proposals.test.ts +0 -168
- package/src/memory/proposals.ts +0 -584
- package/src/memory/recall.test.ts +0 -115
- package/src/memory/recall.ts +0 -278
- 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 -349
- 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 -336
- package/src/sessions/conversation.test.ts +0 -177
- 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 -36
- package/src/sessions/pricing.ts +0 -60
- package/src/sessions/questionBatches.test.ts +0 -110
- package/src/sessions/reasoningSummaries.ts +0 -59
- package/src/sessions/registry.ts +0 -381
- 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 -604
- package/src/sessions/runtimeResources.test.ts +0 -61
- package/src/sessions/sessions.test.ts +0 -549
- package/src/sessions/sessions.ts +0 -339
- package/src/skills/registry.test.ts +0 -104
- package/src/skills/registry.ts +0 -413
- package/src/terminal/accountsMenu.test.ts +0 -225
- package/src/terminal/accountsMenu.ts +0 -382
- package/src/terminal/cli.ts +0 -149
- package/src/terminal/client.ts +0 -74
- package/src/terminal/control.test.ts +0 -23
- package/src/terminal/control.ts +0 -23
- package/src/terminal/controller.ts +0 -291
- package/src/terminal/hubMenu.ts +0 -24
- package/src/terminal/launch.test.ts +0 -25
- package/src/terminal/launch.ts +0 -48
- package/src/terminal/piExtension.lifecycle.test.ts +0 -55
- package/src/terminal/piExtension.ts +0 -259
- 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 -232
- package/src/terminal/standalone.test.ts +0 -167
- package/src/terminal/standalone.ts +0 -153
- package/src/terminal/terminal.test.ts +0 -399
- package/src/terminal/terminals.ts +0 -553
- package/src/terminal/tui/application.test.ts +0 -114
- package/src/terminal/tui/application.ts +0 -491
- package/src/terminal/tui/commands.test.ts +0 -218
- package/src/terminal/tui/commands.ts +0 -361
- package/src/terminal/tui/dialogs.ts +0 -301
- package/src/terminal/tui/editor.test.ts +0 -84
- package/src/terminal/tui/editor.ts +0 -79
- package/src/terminal/tui/keybindings.test.ts +0 -22
- package/src/terminal/tui/keybindings.ts +0 -50
- package/src/terminal/tui/piAdapter.ts +0 -62
- package/src/terminal/tui/presentation.test.ts +0 -79
- package/src/terminal/tui/questions.test.ts +0 -41
- package/src/terminal/tui/questions.ts +0 -47
- package/src/terminal/tui/runtime.socket.test.ts +0 -104
- package/src/terminal/tui/runtime.test.ts +0 -215
- package/src/terminal/tui/runtime.ts +0 -388
- package/src/terminal/tui/sessionUi.ts +0 -24
- package/src/terminal/tui/settings.test.ts +0 -150
- package/src/terminal/tui/settings.ts +0 -188
- package/src/terminal/tui/status.test.ts +0 -70
- package/src/terminal/tui/status.ts +0 -109
- package/src/terminal/tui/theme.ts +0 -174
- package/src/terminal/tui/tools.test.ts +0 -145
- package/src/terminal/tui/tools.ts +0 -142
- package/src/terminal/tui/transcript.test.ts +0 -25
- package/src/terminal/tui/transcript.ts +0 -124
- package/src/terminal/tui/ui.test.ts +0 -313
- package/src/terminal/tui/ui.ts +0 -166
- package/src/terminal/tui/welcome.ts +0 -102
- package/src/tools/askUser.test.ts +0 -69
- package/src/tools/askUser.ts +0 -59
- package/src/tools/definitions.ts +0 -187
- package/src/tools/maintenance.ts +0 -24
- package/src/tools/memoryTools.ts +0 -22
- package/src/tools/pluginPropose.ts +0 -41
- package/src/tools/registryWrite.test.ts +0 -101
- package/src/tools/registryWrite.ts +0 -182
- package/src/tools/spawnSubagent.ts +0 -92
- 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,730 @@
|
|
|
1
|
+
// The accounts service: everything that needs the secret store, the Pi runtime, or a notice.
|
|
2
|
+
// Credentials go to the embedding application's secret store; nothing here writes one to the
|
|
3
|
+
// harness's own files, except a terminal session's `auth.json` while that terminal runs.
|
|
4
|
+
import { chmodSync, existsSync, mkdirSync, readdirSync, rmSync } from 'node:fs';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { log } from "../core/bus.js";
|
|
8
|
+
import { HarnessError, invalid } from "../core/errors.js";
|
|
9
|
+
import { atomicWrite, now, readJson, safeId } from "../core/fs.js";
|
|
10
|
+
import { loadPi } from "../pi/runtime.js";
|
|
11
|
+
import * as choices from "./choices.js";
|
|
12
|
+
import * as env from "./env.js";
|
|
13
|
+
import { secretKey } from "./secrets.js";
|
|
14
|
+
import * as store from "./store.js";
|
|
15
|
+
/** Best-effort classification of a provider error message. */
|
|
16
|
+
export function classifyMessage(message) {
|
|
17
|
+
const text = message.toLowerCase();
|
|
18
|
+
if (/\b401\b|unauthori[sz]ed|invalid (api )?key|invalid x-api-key|authentication|not logged in|log ?in again|expired token|token (has )?expired|refresh token|oauth/.test(text))
|
|
19
|
+
return 'auth_expired';
|
|
20
|
+
if (/usage limit|quota|insufficient_quota|billing|credit balance|out of credits/.test(text))
|
|
21
|
+
return 'quota';
|
|
22
|
+
if (/\b429\b|rate.?limit|too many requests/.test(text))
|
|
23
|
+
return 'rate_limited';
|
|
24
|
+
if (/econnrefused|enotfound|etimedout|network|socket hang up|fetch failed/.test(text))
|
|
25
|
+
return 'network';
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/** The lease files of an account: one per process that has a terminal on it. */
|
|
29
|
+
const leaseFolder = (dir, accountId) => join(dir, accountId, 'terminal-leases');
|
|
30
|
+
/** Whether a process with this id exists. One that exists but belongs to another user still counts. */
|
|
31
|
+
function processAlive(pid) {
|
|
32
|
+
try {
|
|
33
|
+
process.kill(pid, 0);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
return error.code === 'EPERM';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** The credentials in an auth.json, or null when there is no readable file. */
|
|
41
|
+
function readAuth(path) {
|
|
42
|
+
try {
|
|
43
|
+
const parsed = readJson(path);
|
|
44
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Compares two credentials by their fields, not their key order, which Pi may change when it rewrites the file. */
|
|
51
|
+
function sameCredential(a, b) {
|
|
52
|
+
if (a === b)
|
|
53
|
+
return true;
|
|
54
|
+
if (!a || !b)
|
|
55
|
+
return false;
|
|
56
|
+
return JSON.stringify(a, Object.keys(a).sort()) === JSON.stringify(b, Object.keys(b).sort());
|
|
57
|
+
}
|
|
58
|
+
function validProvider(provider) {
|
|
59
|
+
if (provider && provider.length <= 64 && /^[A-Za-z0-9._-]+$/.test(provider))
|
|
60
|
+
return provider;
|
|
61
|
+
throw invalid('Invalid provider name.');
|
|
62
|
+
}
|
|
63
|
+
export class Accounts {
|
|
64
|
+
dir;
|
|
65
|
+
secrets;
|
|
66
|
+
bus;
|
|
67
|
+
runtimes = new Map();
|
|
68
|
+
logins = new Map();
|
|
69
|
+
latestLogins = new Map();
|
|
70
|
+
/** How many of this process's terminals use each account's auth.json. */
|
|
71
|
+
terminals = new Map();
|
|
72
|
+
/** The credentials this process wrote to, or found in, each account's auth.json when its first terminal began. */
|
|
73
|
+
terminalSnapshots = new Map();
|
|
74
|
+
installationDeviceIdPromise = null;
|
|
75
|
+
constructor(options) {
|
|
76
|
+
this.dir = options.dir;
|
|
77
|
+
this.secrets = options.secrets;
|
|
78
|
+
this.bus = options.bus;
|
|
79
|
+
}
|
|
80
|
+
changed(payload = {}) {
|
|
81
|
+
this.bus.emit({ type: 'accounts.changed', ...payload });
|
|
82
|
+
}
|
|
83
|
+
overview() {
|
|
84
|
+
return store.overview(this.dir);
|
|
85
|
+
}
|
|
86
|
+
find(accountId) {
|
|
87
|
+
return store.find(this.dir, accountId);
|
|
88
|
+
}
|
|
89
|
+
/** The model and effort each mode last used on this account. */
|
|
90
|
+
choices(accountId) {
|
|
91
|
+
this.find(accountId);
|
|
92
|
+
return choices.readChoices(this.dir, accountId);
|
|
93
|
+
}
|
|
94
|
+
/** Records a mode's model or effort on this account. Every selection counts, session-only ones too. */
|
|
95
|
+
choose(accountId, mode, patch) {
|
|
96
|
+
this.find(accountId);
|
|
97
|
+
return choices.saveChoice(this.dir, accountId, mode, patch);
|
|
98
|
+
}
|
|
99
|
+
/** The account a new session uses; a session that already has one passes it and keeps it. */
|
|
100
|
+
resolve(accountId, poolId) {
|
|
101
|
+
return store.resolve(this.dir, accountId, poolId);
|
|
102
|
+
}
|
|
103
|
+
context(account) {
|
|
104
|
+
return env.accountContext(this.dir, account);
|
|
105
|
+
}
|
|
106
|
+
// ---- Stored credentials -----------------------------------------------------------------
|
|
107
|
+
async readCredential(accountId, provider) {
|
|
108
|
+
this.find(accountId);
|
|
109
|
+
const text = await this.secrets.get(secretKey(accountId, `pi-auth/${validProvider(provider)}`));
|
|
110
|
+
if (!text)
|
|
111
|
+
return null;
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(text);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Stores a provider's credential, or removes it when `credential` is `null`. */
|
|
120
|
+
async writeCredential(accountId, provider, credential) {
|
|
121
|
+
this.find(accountId);
|
|
122
|
+
const name = validProvider(provider);
|
|
123
|
+
if (credential === null)
|
|
124
|
+
await this.secrets.delete(secretKey(accountId, `pi-auth/${name}`));
|
|
125
|
+
else
|
|
126
|
+
await this.secrets.set(secretKey(accountId, `pi-auth/${name}`), JSON.stringify(credential));
|
|
127
|
+
store.updateState(this.dir, accountId, (state) => {
|
|
128
|
+
state.piProviders = state.piProviders.filter((existing) => existing !== name);
|
|
129
|
+
if (credential !== null)
|
|
130
|
+
state.piProviders = [...state.piProviders, name].sort();
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async listCredentials(accountId) {
|
|
134
|
+
this.find(accountId);
|
|
135
|
+
const listed = [];
|
|
136
|
+
for (const provider of store.loadState(this.dir, accountId).piProviders) {
|
|
137
|
+
const credential = await this.readCredential(accountId, provider).catch(() => null);
|
|
138
|
+
if (credential && typeof credential.type === 'string')
|
|
139
|
+
listed.push({ providerId: provider, type: credential.type });
|
|
140
|
+
}
|
|
141
|
+
return listed;
|
|
142
|
+
}
|
|
143
|
+
/** Pi's `CredentialStore` for one account. Writes are serialized, so a refresh never races another. */
|
|
144
|
+
credentialStore(accountId) {
|
|
145
|
+
let chain = Promise.resolve();
|
|
146
|
+
const serialize = (work) => {
|
|
147
|
+
const next = chain.then(work);
|
|
148
|
+
chain = next.catch(() => undefined);
|
|
149
|
+
return next;
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
read: async (providerId) => (await this.readCredential(accountId, providerId)) ?? undefined,
|
|
153
|
+
list: () => this.listCredentials(accountId),
|
|
154
|
+
modify: (providerId, change) => serialize(async () => {
|
|
155
|
+
const current = (await this.readCredential(accountId, providerId)) ?? undefined;
|
|
156
|
+
const updated = await change(current);
|
|
157
|
+
if (updated !== undefined)
|
|
158
|
+
await this.writeCredential(accountId, providerId, updated);
|
|
159
|
+
return updated ?? current;
|
|
160
|
+
}),
|
|
161
|
+
delete: (providerId) => serialize(() => this.writeCredential(accountId, providerId, null))
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/** Pi's OAuth device ID belongs to this Harness installation, not to one isolated account. */
|
|
165
|
+
installationDeviceId() {
|
|
166
|
+
if (!this.installationDeviceIdPromise) {
|
|
167
|
+
this.installationDeviceIdPromise = this.loadInstallationDeviceId().catch((error) => {
|
|
168
|
+
this.installationDeviceIdPromise = null;
|
|
169
|
+
throw error;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return this.installationDeviceIdPromise;
|
|
173
|
+
}
|
|
174
|
+
async loadInstallationDeviceId() {
|
|
175
|
+
const { SettingsManager } = await loadPi();
|
|
176
|
+
const settings = SettingsManager.create(process.cwd(), env.ensureSharedDir(this.dir));
|
|
177
|
+
const deviceId = settings.getOrCreateDeviceId();
|
|
178
|
+
await settings.flush();
|
|
179
|
+
return deviceId;
|
|
180
|
+
}
|
|
181
|
+
// ---- The Pi runtime ---------------------------------------------------------------------
|
|
182
|
+
/** The lazily loaded Pi runtime of one account: its models and its credentials. Pi's own `~/.pi` is never used. */
|
|
183
|
+
runtime(account) {
|
|
184
|
+
const id = account.id;
|
|
185
|
+
let runtime = this.runtimes.get(id);
|
|
186
|
+
if (!runtime) {
|
|
187
|
+
const configDir = 'configDir' in account ? account.configDir : env.ensureHome(this.dir, account);
|
|
188
|
+
runtime = loadPi()
|
|
189
|
+
.then(({ ModelRuntime }) => ModelRuntime.create({ credentials: this.credentialStore(id), modelsPath: join(configDir, 'models.json') }))
|
|
190
|
+
.catch((error) => {
|
|
191
|
+
this.runtimes.delete(id);
|
|
192
|
+
throw error;
|
|
193
|
+
});
|
|
194
|
+
this.runtimes.set(id, runtime);
|
|
195
|
+
}
|
|
196
|
+
return runtime;
|
|
197
|
+
}
|
|
198
|
+
/** Drops a cached runtime after its credentials changed outside it (sign-in, key edits, terminal sessions). */
|
|
199
|
+
invalidate(accountId) {
|
|
200
|
+
this.runtimes.delete(accountId);
|
|
201
|
+
}
|
|
202
|
+
async models(accountId) {
|
|
203
|
+
const account = accountId ? this.find(accountId) : store.defaultAccount(this.dir);
|
|
204
|
+
if (!account)
|
|
205
|
+
return [];
|
|
206
|
+
const available = await (await this.runtime(account)).getAvailable();
|
|
207
|
+
return available.map((model) => ({ id: `${model.provider}/${model.id}`, name: model.name || model.id, provider: String(model.provider), supportsImages: model.input.includes('image'), reasoning: model.reasoning }));
|
|
208
|
+
}
|
|
209
|
+
/** Offline inspection never refreshes OAuth tokens, contacts a provider, or changes credentials. */
|
|
210
|
+
async inspect(accountId, signal) {
|
|
211
|
+
this.find(accountId);
|
|
212
|
+
const { ModelRuntime } = await loadPi();
|
|
213
|
+
const credentials = this.credentialStore(accountId);
|
|
214
|
+
const readOnly = { read: credentials.read, list: credentials.list, modify: async () => undefined, delete: async () => undefined };
|
|
215
|
+
const runtime = await ModelRuntime.create({ credentials: readOnly, modelsPath: join(env.configDir(this.dir, { id: accountId }), 'models.json'), refreshOnCreate: false, allowModelNetwork: false, signal });
|
|
216
|
+
const saved = await runtime.listCredentials({ signal });
|
|
217
|
+
const providers = new Set(saved.map(item => item.providerId));
|
|
218
|
+
return { credentials: saved.length, models: runtime.getModels().filter(model => providers.has(String(model.provider))).map(model => ({ id: `${model.provider}/${model.id}`, name: model.name || model.id, provider: String(model.provider), supportsImages: model.input.includes('image'), reasoning: model.reasoning })) };
|
|
219
|
+
}
|
|
220
|
+
/** Whether sessions can run, and why not. */
|
|
221
|
+
async status(accountId) {
|
|
222
|
+
const account = accountId ? this.find(accountId) : store.defaultAccount(this.dir);
|
|
223
|
+
if (!account)
|
|
224
|
+
return { available: false, reason: 'Add a Pi account in Accounts to use Chat.', models: 0 };
|
|
225
|
+
try {
|
|
226
|
+
const models = await this.models(account.id);
|
|
227
|
+
return models.length ? { available: true, reason: null, models: models.length } : { available: false, reason: `The Pi account "${account.label}" has no signed-in provider. Sign in or add a key in Accounts.`, models: 0 };
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
log('Pi runtime failed to load', error instanceof Error ? error.message : String(error));
|
|
231
|
+
return { available: false, reason: 'The Pi SDK could not be loaded.', models: 0 };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/** Providers Pi can sign in to (OAuth) or take a key for, independent of any account. */
|
|
235
|
+
async providers() {
|
|
236
|
+
const { ModelRuntime } = await loadPi();
|
|
237
|
+
const none = { read: async () => undefined, list: async () => [], modify: async () => undefined, delete: async () => undefined };
|
|
238
|
+
const runtime = await ModelRuntime.create({ credentials: none, modelsPath: null, refreshOnCreate: false });
|
|
239
|
+
return runtime
|
|
240
|
+
.getProviders()
|
|
241
|
+
.map((provider) => ({ id: provider.id, name: provider.name, oauth: Boolean(provider.auth.oauth), oauthLabel: provider.auth.oauth?.loginLabel ?? provider.auth.oauth?.name ?? null, subscription: Boolean(provider.auth.oauth?.isSubscription), apiKey: Boolean(provider.auth.apiKey?.login) }))
|
|
242
|
+
.filter((provider) => provider.oauth || provider.apiKey)
|
|
243
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
244
|
+
}
|
|
245
|
+
// ---- Status checks ----------------------------------------------------------------------
|
|
246
|
+
async probe(account) {
|
|
247
|
+
const runtime = await this.runtime(account);
|
|
248
|
+
const credentials = await runtime.listCredentials();
|
|
249
|
+
const available = await runtime.getAvailable();
|
|
250
|
+
const subscription = credentials.filter((credential) => runtime.isUsingSubscription(credential.providerId)).map((credential) => credential.providerId);
|
|
251
|
+
return {
|
|
252
|
+
signedIn: credentials.length > 0 && available.length > 0,
|
|
253
|
+
authMethod: [...new Set(credentials.map((credential) => credential.type))].join(', ') || null,
|
|
254
|
+
plan: subscription.length ? `Subscription: ${subscription.join(', ')}` : null,
|
|
255
|
+
identity: credentials.map((credential) => credential.providerId).join(', ') || null,
|
|
256
|
+
capabilities: [`models:${available.length}`, ...credentials.map((credential) => `provider:${credential.providerId}`)]
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/** Checks one account and records what Pi reports. */
|
|
260
|
+
async refreshOne(account) {
|
|
261
|
+
let probe = null;
|
|
262
|
+
let failure = null;
|
|
263
|
+
try {
|
|
264
|
+
probe = await this.probe(account);
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
failure = error instanceof Error ? error.message : String(error);
|
|
268
|
+
}
|
|
269
|
+
const { previous, state } = store.updateState(this.dir, account.id, (current) => {
|
|
270
|
+
current.lastCheckedAt = now();
|
|
271
|
+
if (probe) {
|
|
272
|
+
current.signedIn = probe.signedIn;
|
|
273
|
+
current.authMethod = probe.authMethod;
|
|
274
|
+
current.plan = probe.plan ?? current.plan;
|
|
275
|
+
current.identity = probe.identity;
|
|
276
|
+
if (probe.limits)
|
|
277
|
+
current.limits = probe.limits;
|
|
278
|
+
current.capabilities = probe.capabilities;
|
|
279
|
+
const keepLimitError = current.lastError !== null && store.isLimitCategory(current.lastError.category) && store.limitErrorActive(current.lastError);
|
|
280
|
+
if (probe.signedIn === true && !keepLimitError)
|
|
281
|
+
current.lastError = null;
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
current.lastError = { category: classifyMessage(failure ?? '') ?? 'unknown', message: store.scrub(failure ?? ''), at: now(), resetsAt: null };
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
if (previous !== state.health)
|
|
288
|
+
store.audit(this.dir, account.id, 'health.changed', `${previous} → ${state.health} (status check)`);
|
|
289
|
+
this.changed({ accountId: account.id });
|
|
290
|
+
}
|
|
291
|
+
async refresh(accountId) {
|
|
292
|
+
const accounts = accountId ? [this.find(accountId)] : store.loadRegistry(this.dir).accounts.filter((account) => account.provider === store.PROVIDER);
|
|
293
|
+
for (const account of accounts)
|
|
294
|
+
await this.refreshOne(account);
|
|
295
|
+
return this.overview();
|
|
296
|
+
}
|
|
297
|
+
// ---- Create, rename, remove -------------------------------------------------------------
|
|
298
|
+
create(label, authKind) {
|
|
299
|
+
const account = store.addAccount(this.dir, label, authKind);
|
|
300
|
+
env.ensureHome(this.dir, account);
|
|
301
|
+
store.audit(this.dir, account.id, 'account.created', `Pi account "${account.label}" (${account.authKind})`);
|
|
302
|
+
this.changed({ accountId: account.id });
|
|
303
|
+
return this.overview();
|
|
304
|
+
}
|
|
305
|
+
rename(accountId, label) {
|
|
306
|
+
store.renameAccount(this.dir, accountId, label);
|
|
307
|
+
store.audit(this.dir, accountId, 'account.renamed', store.cleanLabel(label));
|
|
308
|
+
this.changed({ accountId });
|
|
309
|
+
return this.overview();
|
|
310
|
+
}
|
|
311
|
+
setDefault(accountId) {
|
|
312
|
+
store.setDefault(this.dir, accountId);
|
|
313
|
+
store.audit(this.dir, accountId, 'default.changed', 'Default Pi account for new sessions.');
|
|
314
|
+
this.changed({ accountId });
|
|
315
|
+
return this.overview();
|
|
316
|
+
}
|
|
317
|
+
async forgetCredentials(account) {
|
|
318
|
+
for (const provider of store.loadState(this.dir, account.id).piProviders)
|
|
319
|
+
await this.writeCredential(account.id, provider, null);
|
|
320
|
+
this.invalidate(account.id);
|
|
321
|
+
}
|
|
322
|
+
/** Deletes the account: removes its stored credentials and its runtime folder. Sessions pinned to it ask for another account next time they start. */
|
|
323
|
+
async remove(accountId) {
|
|
324
|
+
const account = this.find(accountId);
|
|
325
|
+
for (const login of this.logins.values())
|
|
326
|
+
if (login.accountId === accountId)
|
|
327
|
+
login.cancel();
|
|
328
|
+
this.latestLogins.delete(accountId);
|
|
329
|
+
await this.forgetCredentials(account).catch(() => undefined);
|
|
330
|
+
await this.secrets.delete(secretKey(account.id, 'apiKey')).catch(() => undefined);
|
|
331
|
+
store.forgetAccount(this.dir, account);
|
|
332
|
+
rmSync(join(this.dir, safeId(account.id)), { recursive: true, force: true });
|
|
333
|
+
store.audit(this.dir, account.id, 'account.deleted', `"${account.label}" and its stored sign-in were removed.`);
|
|
334
|
+
this.changed({ accountId: account.id });
|
|
335
|
+
return this.overview();
|
|
336
|
+
}
|
|
337
|
+
savePool(pool) {
|
|
338
|
+
const saved = store.savePool(this.dir, pool);
|
|
339
|
+
store.audit(this.dir, null, 'pool.saved', `${saved.label}: ${saved.accountIds.length} account(s)`);
|
|
340
|
+
this.changed();
|
|
341
|
+
return this.overview();
|
|
342
|
+
}
|
|
343
|
+
removePool(poolId) {
|
|
344
|
+
store.removePool(this.dir, poolId);
|
|
345
|
+
store.audit(this.dir, null, 'pool.removed', poolId);
|
|
346
|
+
this.changed();
|
|
347
|
+
return this.overview();
|
|
348
|
+
}
|
|
349
|
+
// ---- Keys and sign-in -------------------------------------------------------------------
|
|
350
|
+
async setApiKey(accountId, rawKey, provider) {
|
|
351
|
+
const account = this.find(accountId);
|
|
352
|
+
const key = rawKey.trim();
|
|
353
|
+
if (!key || key.length > 4096 || /\s/.test(key))
|
|
354
|
+
throw invalid('Paste the key without spaces.');
|
|
355
|
+
if (!provider)
|
|
356
|
+
throw invalid('Choose which Pi provider the key is for.');
|
|
357
|
+
await this.writeCredential(account.id, provider, { type: 'api_key', key });
|
|
358
|
+
this.invalidate(account.id);
|
|
359
|
+
store.audit(this.dir, account.id, 'key.set', 'Provider key stored in the Keychain.');
|
|
360
|
+
await this.refreshOne(account);
|
|
361
|
+
return this.overview();
|
|
362
|
+
}
|
|
363
|
+
async removeProvider(accountId, provider) {
|
|
364
|
+
const account = this.find(accountId);
|
|
365
|
+
await this.writeCredential(account.id, provider, null);
|
|
366
|
+
this.invalidate(account.id);
|
|
367
|
+
store.audit(this.dir, account.id, 'key.removed', provider);
|
|
368
|
+
await this.refreshOne(account);
|
|
369
|
+
return this.overview();
|
|
370
|
+
}
|
|
371
|
+
async signOut(accountId) {
|
|
372
|
+
const account = this.find(accountId);
|
|
373
|
+
await this.forgetCredentials(account);
|
|
374
|
+
store.audit(this.dir, account.id, 'logout', 'Signed out; stored credentials removed.');
|
|
375
|
+
store.updateState(this.dir, account.id, (current) => {
|
|
376
|
+
current.signedIn = false;
|
|
377
|
+
current.identity = null;
|
|
378
|
+
current.lastError = null;
|
|
379
|
+
});
|
|
380
|
+
this.changed({ accountId: account.id });
|
|
381
|
+
return this.overview();
|
|
382
|
+
}
|
|
383
|
+
finishLogin(loginId, success, error, cancelled = false) {
|
|
384
|
+
const login = this.logins.get(loginId);
|
|
385
|
+
if (!login)
|
|
386
|
+
return;
|
|
387
|
+
this.logins.delete(loginId);
|
|
388
|
+
for (const prompt of login.prompts.values())
|
|
389
|
+
prompt.reject(new Error('Sign-in ended.'));
|
|
390
|
+
if (success)
|
|
391
|
+
this.invalidate(login.accountId);
|
|
392
|
+
store.audit(this.dir, login.accountId, success ? 'login.completed' : 'login.failed', error ?? '');
|
|
393
|
+
// A fresh sign-in starts clean; a limit that still applies is reported again by the next run.
|
|
394
|
+
if (success)
|
|
395
|
+
store.updateState(this.dir, login.accountId, (current) => void (current.lastError = null));
|
|
396
|
+
login.revision += 1;
|
|
397
|
+
const status = success ? 'completed' : cancelled ? 'cancelled' : 'failed';
|
|
398
|
+
if (this.latestLogins.get(login.accountId)?.loginId === loginId) {
|
|
399
|
+
this.latestLogins.set(login.accountId, {
|
|
400
|
+
accountId: login.accountId,
|
|
401
|
+
loginId,
|
|
402
|
+
provider: login.provider,
|
|
403
|
+
method: login.method,
|
|
404
|
+
revision: login.revision,
|
|
405
|
+
status,
|
|
406
|
+
// A terminal result is enough to reconcile the client. In particular, do not retain a
|
|
407
|
+
// device code after the attempt ends.
|
|
408
|
+
events: [],
|
|
409
|
+
prompt: null,
|
|
410
|
+
success,
|
|
411
|
+
error
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
this.bus.emit({ type: 'accounts.login', accountId: login.accountId, loginId, revision: login.revision, success, error });
|
|
415
|
+
try {
|
|
416
|
+
void this.refreshOne(this.find(login.accountId)).catch(() => undefined);
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
// The account was removed while its sign-in was running.
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Starts a provider's own sign-in for this account. Progress, links to open, and prompts
|
|
424
|
+
* arrive as `accounts.loginEvent` notices; the outcome as `accounts.login`.
|
|
425
|
+
*/
|
|
426
|
+
async loginStart(accountId, provider, method) {
|
|
427
|
+
const account = this.find(accountId);
|
|
428
|
+
if (!provider)
|
|
429
|
+
throw invalid('Choose which Pi provider to sign in to.');
|
|
430
|
+
store.audit(this.dir, account.id, 'login.started', `${method ?? 'oauth'} · ${provider}`);
|
|
431
|
+
const loginId = randomUUID();
|
|
432
|
+
const controller = new AbortController();
|
|
433
|
+
const login = {
|
|
434
|
+
accountId: account.id,
|
|
435
|
+
provider,
|
|
436
|
+
method: method === 'api_key' ? 'api_key' : 'oauth',
|
|
437
|
+
prompts: new Map(),
|
|
438
|
+
cancel: () => controller.abort(),
|
|
439
|
+
revision: 0,
|
|
440
|
+
events: [],
|
|
441
|
+
prompt: null
|
|
442
|
+
};
|
|
443
|
+
this.logins.set(loginId, login);
|
|
444
|
+
this.latestLogins.set(account.id, {
|
|
445
|
+
accountId: account.id,
|
|
446
|
+
loginId,
|
|
447
|
+
provider: login.provider,
|
|
448
|
+
method: login.method,
|
|
449
|
+
revision: 0,
|
|
450
|
+
status: 'running',
|
|
451
|
+
events: [],
|
|
452
|
+
prompt: null,
|
|
453
|
+
success: null,
|
|
454
|
+
error: null
|
|
455
|
+
});
|
|
456
|
+
const event = (payload) => {
|
|
457
|
+
login.revision += 1;
|
|
458
|
+
if (payload.type === 'prompt')
|
|
459
|
+
login.prompt = payload;
|
|
460
|
+
else if (payload.type === 'promptClosed' && login.prompt?.promptId === payload.promptId)
|
|
461
|
+
login.prompt = null;
|
|
462
|
+
else {
|
|
463
|
+
login.events.push(payload);
|
|
464
|
+
if (login.events.length > 50)
|
|
465
|
+
login.events.shift();
|
|
466
|
+
}
|
|
467
|
+
if (this.latestLogins.get(account.id)?.loginId === loginId) {
|
|
468
|
+
this.latestLogins.set(account.id, {
|
|
469
|
+
accountId: account.id,
|
|
470
|
+
loginId,
|
|
471
|
+
provider: login.provider,
|
|
472
|
+
method: login.method,
|
|
473
|
+
revision: login.revision,
|
|
474
|
+
status: 'running',
|
|
475
|
+
events: login.events.map((step) => ({ ...step })),
|
|
476
|
+
prompt: login.prompt ? { ...login.prompt } : null,
|
|
477
|
+
success: null,
|
|
478
|
+
error: null
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
this.bus.emit({ type: 'accounts.loginEvent', accountId: account.id, loginId, revision: login.revision, event: payload });
|
|
482
|
+
};
|
|
483
|
+
const interaction = {
|
|
484
|
+
signal: controller.signal,
|
|
485
|
+
notify: (notification) => event(notification),
|
|
486
|
+
prompt: (prompt) => new Promise((resolve, reject) => {
|
|
487
|
+
const promptId = randomUUID();
|
|
488
|
+
login.prompts.set(promptId, { resolve, reject });
|
|
489
|
+
prompt.signal?.addEventListener('abort', () => {
|
|
490
|
+
login.prompts.delete(promptId);
|
|
491
|
+
event({ type: 'promptClosed', promptId });
|
|
492
|
+
reject(new Error('Prompt closed.'));
|
|
493
|
+
});
|
|
494
|
+
event({ type: 'prompt', promptId, prompt: { type: prompt.type, message: prompt.message, placeholder: prompt.placeholder ?? null, options: prompt.options ?? null } });
|
|
495
|
+
})
|
|
496
|
+
};
|
|
497
|
+
void this.runtime(account)
|
|
498
|
+
.then(async (runtime) => {
|
|
499
|
+
if (login.method !== 'oauth')
|
|
500
|
+
return runtime.login(provider, login.method, interaction);
|
|
501
|
+
const deviceId = await this.installationDeviceId();
|
|
502
|
+
return runtime.login(provider, login.method, interaction, { getDeviceId: () => deviceId });
|
|
503
|
+
})
|
|
504
|
+
.then(() => this.finishLogin(loginId, true, null))
|
|
505
|
+
.catch((error) => this.finishLogin(loginId, false, controller.signal.aborted ? 'Sign-in was cancelled.' : error instanceof Error ? error.message : String(error), controller.signal.aborted));
|
|
506
|
+
return { loginId };
|
|
507
|
+
}
|
|
508
|
+
/** Current in-memory sign-in progress for one account; codes and prompts are never written to disk. */
|
|
509
|
+
loginStatus(accountId) {
|
|
510
|
+
this.find(accountId);
|
|
511
|
+
const snapshot = this.latestLogins.get(accountId);
|
|
512
|
+
return snapshot ? structuredClone(snapshot) : null;
|
|
513
|
+
}
|
|
514
|
+
loginRespond(loginId, promptId, value) {
|
|
515
|
+
const login = this.logins.get(loginId);
|
|
516
|
+
const prompt = login?.prompts.get(promptId);
|
|
517
|
+
if (!login || !prompt)
|
|
518
|
+
throw new HarnessError('not_found', 'That sign-in step is no longer waiting.');
|
|
519
|
+
login.prompts.delete(promptId);
|
|
520
|
+
// The renderer's recoverable snapshot must stop presenting a prompt as soon as it is answered.
|
|
521
|
+
const current = this.latestLogins.get(login.accountId);
|
|
522
|
+
if (current?.loginId === loginId && current.prompt?.promptId === promptId) {
|
|
523
|
+
login.prompt = null;
|
|
524
|
+
current.revision += 1;
|
|
525
|
+
login.revision = current.revision;
|
|
526
|
+
current.prompt = null;
|
|
527
|
+
this.latestLogins.set(login.accountId, current);
|
|
528
|
+
this.bus.emit({ type: 'accounts.loginEvent', accountId: login.accountId, loginId, revision: current.revision, event: { type: 'promptClosed', promptId } });
|
|
529
|
+
}
|
|
530
|
+
if (value === null)
|
|
531
|
+
prompt.reject(new Error('Cancelled.'));
|
|
532
|
+
else
|
|
533
|
+
prompt.resolve(value);
|
|
534
|
+
}
|
|
535
|
+
loginCancel(accountId, loginId) {
|
|
536
|
+
if (loginId) {
|
|
537
|
+
const login = this.logins.get(loginId);
|
|
538
|
+
if (login?.accountId === accountId)
|
|
539
|
+
login.cancel();
|
|
540
|
+
}
|
|
541
|
+
else
|
|
542
|
+
for (const login of this.logins.values())
|
|
543
|
+
if (login.accountId === accountId)
|
|
544
|
+
login.cancel();
|
|
545
|
+
store.audit(this.dir, accountId, 'login.cancelled', '');
|
|
546
|
+
}
|
|
547
|
+
// ---- Terminal sessions --------------------------------------------------------------------
|
|
548
|
+
/**
|
|
549
|
+
* The `pi` command reads `auth.json` from its agent folder. For a terminal session the
|
|
550
|
+
* account's stored credentials are written there (mode 0600) while the terminal runs, then
|
|
551
|
+
* read back and removed.
|
|
552
|
+
*/
|
|
553
|
+
/** How many terminal sessions are running on this account right now. */
|
|
554
|
+
terminalCount(accountId) {
|
|
555
|
+
return this.terminals.get(accountId) ?? 0;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Starts a terminal's use of the account's auth.json. The file is written from the store when the
|
|
559
|
+
* first terminal of the account begins. While another process's terminal still uses it, the tokens
|
|
560
|
+
* already in the file stay, because that terminal may have refreshed them; only a provider the file
|
|
561
|
+
* lacks is added from the store, so a key saved after the other terminal began still reaches this one.
|
|
562
|
+
*/
|
|
563
|
+
async terminalBegin(account) {
|
|
564
|
+
const count = (this.terminals.get(account.id) ?? 0) + 1;
|
|
565
|
+
this.terminals.set(account.id, count);
|
|
566
|
+
if (count > 1)
|
|
567
|
+
return;
|
|
568
|
+
const path = join(env.configDir(this.dir, account), 'auth.json');
|
|
569
|
+
let shared = false;
|
|
570
|
+
try {
|
|
571
|
+
shared = this.otherTerminalProcesses(account.id);
|
|
572
|
+
this.claimLease(account.id);
|
|
573
|
+
const existing = shared && existsSync(path) ? readAuth(path) : null;
|
|
574
|
+
const data = await this.storedCredentials(account.id, existing ?? {});
|
|
575
|
+
if (!existing || Object.keys(data).length > Object.keys(existing).length) {
|
|
576
|
+
atomicWrite(path, JSON.stringify(data, null, 2));
|
|
577
|
+
chmodSync(path, 0o600);
|
|
578
|
+
}
|
|
579
|
+
this.terminalSnapshots.set(account.id, data);
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
this.terminals.delete(account.id);
|
|
583
|
+
this.terminalSnapshots.delete(account.id);
|
|
584
|
+
this.releaseLease(account.id);
|
|
585
|
+
if (!shared)
|
|
586
|
+
rmSync(path, { force: true });
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Ends a terminal's use of the auth.json. Each terminal saves what its Pi changed since it began (a
|
|
592
|
+
* refreshed or new sign-in, or a provider it signed out of), even while another process still uses the
|
|
593
|
+
* file, so no refresh is lost when the other terminal ends first. A credential saved to the store after
|
|
594
|
+
* the terminal began is in neither the file nor the snapshot, so it stays. The last terminal of the
|
|
595
|
+
* account to end, in every process, removes the file.
|
|
596
|
+
*/
|
|
597
|
+
async terminalEnd(account) {
|
|
598
|
+
const remaining = Math.max(0, (this.terminals.get(account.id) ?? 1) - 1);
|
|
599
|
+
if (remaining > 0) {
|
|
600
|
+
this.terminals.set(account.id, remaining);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
this.terminals.delete(account.id);
|
|
604
|
+
const snapshot = this.terminalSnapshots.get(account.id) ?? {};
|
|
605
|
+
this.terminalSnapshots.delete(account.id);
|
|
606
|
+
this.releaseLease(account.id);
|
|
607
|
+
const path = join(env.configDir(this.dir, account), 'auth.json');
|
|
608
|
+
const data = readAuth(path);
|
|
609
|
+
if (data) {
|
|
610
|
+
let changed = false;
|
|
611
|
+
for (const provider of new Set([...Object.keys(snapshot), ...Object.keys(data)])) {
|
|
612
|
+
if (provider in data) {
|
|
613
|
+
if (sameCredential(data[provider], snapshot[provider]))
|
|
614
|
+
continue;
|
|
615
|
+
await this.writeCredential(account.id, provider, data[provider]).catch(() => undefined);
|
|
616
|
+
changed = true;
|
|
617
|
+
}
|
|
618
|
+
else if (provider in snapshot) {
|
|
619
|
+
await this.writeCredential(account.id, provider, null).catch(() => undefined);
|
|
620
|
+
changed = true;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (changed) {
|
|
624
|
+
this.invalidate(account.id);
|
|
625
|
+
store.audit(this.dir, account.id, 'credentials.synced', 'Pi terminal sign-in saved to the Keychain.');
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (!this.otherTerminalProcesses(account.id))
|
|
629
|
+
rmSync(path, { force: true });
|
|
630
|
+
}
|
|
631
|
+
/** The account's stored credentials, with `existing` kept as it is for every provider it already has. */
|
|
632
|
+
async storedCredentials(accountId, existing) {
|
|
633
|
+
const data = { ...existing };
|
|
634
|
+
for (const provider of store.loadState(this.dir, accountId).piProviders) {
|
|
635
|
+
if (provider in data)
|
|
636
|
+
continue;
|
|
637
|
+
const credential = await this.readCredential(accountId, provider).catch(() => null);
|
|
638
|
+
if (credential)
|
|
639
|
+
data[provider] = credential;
|
|
640
|
+
}
|
|
641
|
+
return data;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Whether another live process has a terminal on this account. Lease files of processes that have
|
|
645
|
+
* ended are removed, so a crash never leaves the account looking busy.
|
|
646
|
+
*/
|
|
647
|
+
otherTerminalProcesses(accountId) {
|
|
648
|
+
const folder = leaseFolder(this.dir, accountId);
|
|
649
|
+
let names;
|
|
650
|
+
try {
|
|
651
|
+
names = readdirSync(folder);
|
|
652
|
+
}
|
|
653
|
+
catch {
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
let others = false;
|
|
657
|
+
for (const name of names) {
|
|
658
|
+
const pid = Number(name);
|
|
659
|
+
if (!/^\d+$/.test(name) || pid <= 0 || pid === process.pid)
|
|
660
|
+
continue;
|
|
661
|
+
if (processAlive(pid))
|
|
662
|
+
others = true;
|
|
663
|
+
else
|
|
664
|
+
rmSync(join(folder, name), { force: true });
|
|
665
|
+
}
|
|
666
|
+
return others;
|
|
667
|
+
}
|
|
668
|
+
claimLease(accountId) {
|
|
669
|
+
const folder = leaseFolder(this.dir, accountId);
|
|
670
|
+
mkdirSync(folder, { recursive: true, mode: 0o700 });
|
|
671
|
+
atomicWrite(join(folder, String(process.pid)), JSON.stringify({ pid: process.pid, startedAt: now() }));
|
|
672
|
+
}
|
|
673
|
+
releaseLease(accountId) {
|
|
674
|
+
rmSync(join(leaseFolder(this.dir, accountId), String(process.pid)), { force: true });
|
|
675
|
+
}
|
|
676
|
+
// ---- Health from runs ---------------------------------------------------------------------
|
|
677
|
+
/** Tracks account health and limits from run events. `run` is the event's run record, when it is known. */
|
|
678
|
+
onRunEvent(event, run) {
|
|
679
|
+
if (event.type === 'account.status')
|
|
680
|
+
this.applyStatus(event);
|
|
681
|
+
else if (event.type === 'completed' && run?.accountId) {
|
|
682
|
+
const accountId = run.accountId;
|
|
683
|
+
const { previous, state } = store.updateState(this.dir, accountId, (current) => {
|
|
684
|
+
// A finished run proves the sign-in works; limit windows and limit errors stay until they reset.
|
|
685
|
+
if (current.lastError && (!store.isLimitCategory(current.lastError.category) || !store.limitErrorActive(current.lastError)))
|
|
686
|
+
current.lastError = null;
|
|
687
|
+
current.signedIn = true;
|
|
688
|
+
});
|
|
689
|
+
if (previous !== state.health)
|
|
690
|
+
this.changed({ accountId, health: state.health, previous });
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
applyStatus(event) {
|
|
694
|
+
const payload = event.payload;
|
|
695
|
+
if (typeof payload.accountId !== 'string')
|
|
696
|
+
return;
|
|
697
|
+
const accountId = payload.accountId;
|
|
698
|
+
const text = (value) => (typeof value === 'string' ? value : null);
|
|
699
|
+
const category = text(payload.category);
|
|
700
|
+
const message = text(payload.message) === null ? null : store.scrub(payload.message);
|
|
701
|
+
const limits = Array.isArray(payload.limits) ? payload.limits : null;
|
|
702
|
+
let result;
|
|
703
|
+
try {
|
|
704
|
+
result = store.updateState(this.dir, accountId, (state) => {
|
|
705
|
+
if (limits) {
|
|
706
|
+
for (const limit of limits)
|
|
707
|
+
state.limits = [...state.limits.filter((existing) => existing.id !== limit.id), limit];
|
|
708
|
+
state.limits.sort((left, right) => (left.id < right.id ? -1 : left.id > right.id ? 1 : 0));
|
|
709
|
+
}
|
|
710
|
+
if (text(payload.plan) !== null)
|
|
711
|
+
state.plan = text(payload.plan);
|
|
712
|
+
if (category) {
|
|
713
|
+
state.lastError = { category, message: message ?? '', at: now(), resetsAt: text(payload.resetsAt) };
|
|
714
|
+
if (category === 'auth_expired')
|
|
715
|
+
state.signedIn = false;
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (result.previous !== result.state.health) {
|
|
723
|
+
store.audit(this.dir, accountId, 'health.changed', `${result.previous} → ${result.state.health}${message ? `: ${message}` : ''}`);
|
|
724
|
+
this.changed({ accountId, health: result.state.health, previous: result.previous, message, resetsAt: text(payload.resetsAt) });
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
this.changed({ accountId });
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|