@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
package/src/terminal/socket.ts
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
// The session socket: how a process the harness did not start in-process (the `pi` command in
|
|
2
|
-
// a terminal, with its extension and the `xagent-wiki` / `xagent-mcp` commands) reaches the
|
|
3
|
-
// session it belongs to. Owner-only Unix socket (a named pipe on Windows), newline-delimited JSON,
|
|
4
|
-
// one request per connection. Each terminal session holds a random token bound to its caller, its
|
|
5
|
-
// connected-apps grant, and its controller; the token stops working when the terminal ends.
|
|
6
|
-
import { createHash, randomBytes } from 'node:crypto'
|
|
7
|
-
import { chmodSync, mkdirSync, rmSync, statSync } from 'node:fs'
|
|
8
|
-
import { createServer, type Server, type Socket } from 'node:net'
|
|
9
|
-
import { tmpdir } from 'node:os'
|
|
10
|
-
import { dirname, join } from 'node:path'
|
|
11
|
-
|
|
12
|
-
import { log } from '../core/bus'
|
|
13
|
-
import { HarnessError, toErrorShape } from '../core/errors'
|
|
14
|
-
import { newId } from '../core/fs'
|
|
15
|
-
import type { BrokerSession } from '../mcp/service'
|
|
16
|
-
import type { CallerContext } from '../memory/caller'
|
|
17
|
-
|
|
18
|
-
/** What a token stands for. */
|
|
19
|
-
export interface SocketGrant {
|
|
20
|
-
sessionId: string
|
|
21
|
-
/** Who memory, Wiki, and recall calls are made as. The run is filled in while a turn is live. */
|
|
22
|
-
caller: () => CallerContext
|
|
23
|
-
/** The session's connected-apps grant, when it has one. */
|
|
24
|
-
broker: BrokerSession | null
|
|
25
|
-
/** Telemetry from the native process, and what to do when it hangs up mid-request. */
|
|
26
|
-
terminal?: { accept(event: string, data: Record<string, unknown>): Promise<unknown>; disconnected(): void; control?(call: string, data: Record<string, unknown>): unknown }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface SocketHandlers {
|
|
30
|
-
/** `wiki.*`, `memory.*`, `recall.*`. */
|
|
31
|
-
memory(caller: CallerContext, call: string, params: Record<string, unknown>): string
|
|
32
|
-
/** `mcp.list`, `mcp.call`, `mcp.revision`, `tools.list`, `tools.call`. */
|
|
33
|
-
apps(session: BrokerSession, call: string, params: Record<string, unknown>): Promise<unknown>
|
|
34
|
-
/**
|
|
35
|
-
* `plugins.*`. `control` is true only for the session's own extension, which holds a key the
|
|
36
|
-
* session's shell never sees; anything that changes what loads needs it.
|
|
37
|
-
*/
|
|
38
|
-
plugins?(grant: SocketGrant, call: string, params: Record<string, unknown>, control: boolean): Promise<unknown>
|
|
39
|
-
/** `accounts.*`. Every call needs `control`: adding, signing in, or removing an account is a person's choice. */
|
|
40
|
-
accounts?(grant: SocketGrant, call: string, params: Record<string, unknown>, control: boolean): Promise<unknown>
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const MAX_REQUEST_BYTES = 1024 * 1024
|
|
44
|
-
/** macOS refuses a socket path longer than 103 bytes. */
|
|
45
|
-
const MAX_SOCKET_PATH = 100
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Windows has no socket files for Node's `net`: it listens on a named pipe, which lives in a
|
|
49
|
-
* machine-wide namespace. A random name keeps another local process from guessing it and standing
|
|
50
|
-
* in front of the session; the token still decides what a caller may do.
|
|
51
|
-
*/
|
|
52
|
-
export function socketPath(stateDir: string): string {
|
|
53
|
-
if (process.platform === 'win32') return `\\\\.\\pipe\\xingular-harness-${randomBytes(12).toString('hex')}`
|
|
54
|
-
const path = join(stateDir, 'run', 'harness.sock')
|
|
55
|
-
if (Buffer.byteLength(path) <= MAX_SOCKET_PATH) return path
|
|
56
|
-
return join(tmpdir(), `xh-${createHash('sha256').update(path).digest('hex').slice(0, 16)}.sock`)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** A named pipe has no folder, file mode, or owner to check or clean up. */
|
|
60
|
-
const isPipePath = (path: string): boolean => path.startsWith('\\\\.\\pipe\\')
|
|
61
|
-
|
|
62
|
-
/** Names the folder and the user when the operating system refuses the socket, so the cause shows. */
|
|
63
|
-
function refusedBind(path: string, error: unknown): unknown {
|
|
64
|
-
const code = (error as { code?: unknown } | null)?.code
|
|
65
|
-
if (code !== 'EACCES' && code !== 'EPERM') return error
|
|
66
|
-
if (isPipePath(path)) {
|
|
67
|
-
return new HarnessError(
|
|
68
|
-
'session_socket_denied',
|
|
69
|
-
`The session pipe ${path} could not be created (${code}).`,
|
|
70
|
-
'Start the harness again from your own user session. If it keeps failing, report this code and the Windows version.'
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
let folder: string
|
|
74
|
-
try {
|
|
75
|
-
const stat = statSync(dirname(path))
|
|
76
|
-
folder = `${dirname(path)} is owned by uid ${stat.uid} with mode ${(stat.mode & 0o777).toString(8)}`
|
|
77
|
-
} catch {
|
|
78
|
-
folder = `${dirname(path)} could not be read`
|
|
79
|
-
}
|
|
80
|
-
const uid = process.getuid?.() ?? 'unknown'
|
|
81
|
-
return new HarnessError(
|
|
82
|
-
'session_socket_denied',
|
|
83
|
-
`The session socket ${path} could not be created (${code}). ${folder}; this process runs as uid ${uid}.`,
|
|
84
|
-
'Run the harness from your own terminal, as the user that owns its state folder, or choose a state folder you own with --state.'
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export class SessionSocket {
|
|
89
|
-
private server: Server | null = null
|
|
90
|
-
private listening: Promise<void> | null = null
|
|
91
|
-
private readonly grants = new Map<string, SocketGrant>()
|
|
92
|
-
/**
|
|
93
|
-
* A second key per token, handed out once. The token is in the session's environment, where a
|
|
94
|
-
* command the model runs can read it; this key is claimed by the session's extension as it
|
|
95
|
-
* loads, before any model turn, and kept in its memory only.
|
|
96
|
-
*/
|
|
97
|
-
private readonly controls = new Map<string, string>()
|
|
98
|
-
private readonly clients = new Set<Socket>()
|
|
99
|
-
|
|
100
|
-
constructor(
|
|
101
|
-
readonly path: string,
|
|
102
|
-
private readonly handlers: SocketHandlers
|
|
103
|
-
) {}
|
|
104
|
-
|
|
105
|
-
issue(grant: SocketGrant): string {
|
|
106
|
-
const token = `${newId()}${newId()}`.replaceAll('-', '')
|
|
107
|
-
this.grants.set(token, grant)
|
|
108
|
-
return token
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
revoke(token: string): void {
|
|
112
|
-
this.grants.delete(token)
|
|
113
|
-
this.controls.delete(token)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
revokeSession(sessionId: string): void {
|
|
117
|
-
for (const [token, grant] of this.grants) if (grant.sessionId === sessionId) this.revoke(token)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Runs one request line and returns the reply line's object. */
|
|
121
|
-
async handle(line: string, connection?: { waiting: (grant: SocketGrant | null) => void }): Promise<{ result: unknown } | { error: unknown }> {
|
|
122
|
-
try {
|
|
123
|
-
let request: Record<string, unknown>
|
|
124
|
-
try {
|
|
125
|
-
request = JSON.parse(line) as Record<string, unknown>
|
|
126
|
-
} catch {
|
|
127
|
-
throw new HarnessError('invalid_request', 'The request is not JSON.')
|
|
128
|
-
}
|
|
129
|
-
const token = typeof request.token === 'string' ? request.token : ''
|
|
130
|
-
const grant = this.grants.get(token)
|
|
131
|
-
if (!grant) throw new HarnessError('unauthorized', 'This session token is not valid. It ends when its terminal session ends.')
|
|
132
|
-
if (typeof request.event === 'string') {
|
|
133
|
-
if (!grant.terminal) throw new HarnessError('unknown_method', 'This session has no terminal controller.')
|
|
134
|
-
connection?.waiting(grant)
|
|
135
|
-
try {
|
|
136
|
-
return { result: (await grant.terminal.accept(request.event, (request.data ?? {}) as Record<string, unknown>)) ?? {} }
|
|
137
|
-
} finally {
|
|
138
|
-
connection?.waiting(null)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
const call = typeof request.call === 'string' ? request.call : ''
|
|
142
|
-
const params = (request.params && typeof request.params === 'object' ? request.params : {}) as Record<string, unknown>
|
|
143
|
-
if (['wiki.', 'memory.', 'recall.'].some((prefix) => call.startsWith(prefix))) return { result: this.handlers.memory(grant.caller(), call, params) }
|
|
144
|
-
if (['mcp.', 'tools.'].some((prefix) => call.startsWith(prefix))) {
|
|
145
|
-
if (!grant.broker) throw new HarnessError('mcp_unauthorized', 'Connected apps are not available to this session.')
|
|
146
|
-
return { result: await this.handlers.apps(grant.broker, call, params) }
|
|
147
|
-
}
|
|
148
|
-
if (call === 'control.claim') {
|
|
149
|
-
if (this.controls.has(token)) throw new HarnessError('unauthorized', "This session's control key was already claimed.")
|
|
150
|
-
const key = `${newId()}${newId()}`.replaceAll('-', '')
|
|
151
|
-
this.controls.set(token, key)
|
|
152
|
-
return { result: { key } }
|
|
153
|
-
}
|
|
154
|
-
if (call.startsWith('terminal.') && grant.terminal?.control) {
|
|
155
|
-
const key = this.controls.get(token)
|
|
156
|
-
if (!key || request.control !== key) throw new HarnessError('unauthorized', 'Only a person at this terminal can answer decisions or change its draft state.')
|
|
157
|
-
return { result: await grant.terminal.control(call, params) }
|
|
158
|
-
}
|
|
159
|
-
if ((call.startsWith('plugins.') || call.startsWith('hub.')) && this.handlers.plugins) {
|
|
160
|
-
const key = this.controls.get(token)
|
|
161
|
-
return { result: await this.handlers.plugins(grant, call, params, key !== undefined && request.control === key) }
|
|
162
|
-
}
|
|
163
|
-
if (call.startsWith('accounts.') && this.handlers.accounts) {
|
|
164
|
-
const key = this.controls.get(token)
|
|
165
|
-
return { result: await this.handlers.accounts(grant, call, params, key !== undefined && request.control === key) }
|
|
166
|
-
}
|
|
167
|
-
throw new HarnessError('unknown_method', `Unknown call ${call}.`)
|
|
168
|
-
} catch (error) {
|
|
169
|
-
return { error: toErrorShape(error) }
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private serve(client: Socket): void {
|
|
174
|
-
let buffer = ''
|
|
175
|
-
let waiting: SocketGrant | null = null
|
|
176
|
-
this.clients.add(client)
|
|
177
|
-
client.on('error', () => {})
|
|
178
|
-
client.once('close', () => {
|
|
179
|
-
this.clients.delete(client)
|
|
180
|
-
// A decision the native process was waiting on can no longer reach it.
|
|
181
|
-
waiting?.terminal?.disconnected()
|
|
182
|
-
})
|
|
183
|
-
client.on('data', (chunk) => {
|
|
184
|
-
buffer += chunk.toString('utf8')
|
|
185
|
-
if (Buffer.byteLength(buffer) > MAX_REQUEST_BYTES) {
|
|
186
|
-
client.destroy()
|
|
187
|
-
return
|
|
188
|
-
}
|
|
189
|
-
const newline = buffer.indexOf('\n')
|
|
190
|
-
if (newline < 0) return
|
|
191
|
-
client.pause()
|
|
192
|
-
void this.handle(buffer.slice(0, newline), { waiting: (grant) => void (waiting = grant) }).then((reply) => client.end(`${JSON.stringify(reply)}\n`))
|
|
193
|
-
})
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/** Binds the socket once; later calls wait for the same bind. */
|
|
197
|
-
listen(): Promise<void> {
|
|
198
|
-
this.listening ??= new Promise<void>((resolve, reject) => {
|
|
199
|
-
if (!isPipePath(this.path)) {
|
|
200
|
-
mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 })
|
|
201
|
-
rmSync(this.path, { force: true })
|
|
202
|
-
}
|
|
203
|
-
const server = createServer((client) => this.serve(client))
|
|
204
|
-
server.once('error', (error) => {
|
|
205
|
-
this.listening = null
|
|
206
|
-
reject(refusedBind(this.path, error))
|
|
207
|
-
})
|
|
208
|
-
server.listen(this.path, () => {
|
|
209
|
-
if (!isPipePath(this.path)) {
|
|
210
|
-
try {
|
|
211
|
-
chmodSync(this.path, 0o600)
|
|
212
|
-
} catch (error) {
|
|
213
|
-
log('session socket permissions', error instanceof Error ? error.message : String(error))
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
this.server = server
|
|
217
|
-
resolve()
|
|
218
|
-
})
|
|
219
|
-
})
|
|
220
|
-
return this.listening
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
async close(): Promise<void> {
|
|
224
|
-
this.grants.clear()
|
|
225
|
-
const server = this.server
|
|
226
|
-
this.server = null
|
|
227
|
-
this.listening = null
|
|
228
|
-
for (const client of this.clients) client.destroy()
|
|
229
|
-
if (server) await new Promise<void>((resolve) => server.close(() => resolve()))
|
|
230
|
-
if (!isPipePath(this.path)) rmSync(this.path, { force: true })
|
|
231
|
-
}
|
|
232
|
-
}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'node:events'
|
|
2
|
-
import { createHash } from 'node:crypto'
|
|
3
|
-
import { existsSync, mkdtempSync, rmSync } from 'node:fs'
|
|
4
|
-
import { tmpdir } from 'node:os'
|
|
5
|
-
import { join, resolve } from 'node:path'
|
|
6
|
-
import type { ChildProcess, spawn as nodeSpawn } from 'node:child_process'
|
|
7
|
-
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
8
|
-
|
|
9
|
-
import { memorySecretStore } from '../accounts/secrets'
|
|
10
|
-
import { createHarness, type Harness, type HarnessOptions } from '../harness'
|
|
11
|
-
import { defaultAssets } from './terminals'
|
|
12
|
-
import { parseStandaloneOptions, runStandaloneTerminal, standaloneArguments } from './standalone'
|
|
13
|
-
import { socketPath } from './socket'
|
|
14
|
-
import { SOCKET_ENV, TOKEN_ENV, socketCall } from './client'
|
|
15
|
-
|
|
16
|
-
const scratch: string[] = []
|
|
17
|
-
const harnesses: Harness[] = []
|
|
18
|
-
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
for (const harness of harnesses.splice(0)) await harness.close()
|
|
21
|
-
for (const path of scratch.splice(0)) rmSync(path, { recursive: true, force: true })
|
|
22
|
-
vi.restoreAllMocks()
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
function temporaryDirectory(prefix: string): string {
|
|
26
|
-
const path = mkdtempSync(join(tmpdir(), prefix))
|
|
27
|
-
scratch.push(path)
|
|
28
|
-
return path
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function fakeSpawn(onSpawn: (program: string, args: string[], options: { cwd?: string; env?: NodeJS.ProcessEnv; stdio?: string }) => void, outcome: { code?: number; error?: Error } = {}): typeof nodeSpawn {
|
|
32
|
-
return ((program: string, args: string[], options: { cwd?: string; env?: NodeJS.ProcessEnv; stdio?: string }) => {
|
|
33
|
-
onSpawn(program, args, options)
|
|
34
|
-
const child = new EventEmitter() as ChildProcess
|
|
35
|
-
child.kill = vi.fn(() => true)
|
|
36
|
-
queueMicrotask(() => {
|
|
37
|
-
if (outcome.error) child.emit('error', outcome.error)
|
|
38
|
-
else child.emit('exit', outcome.code ?? 0, null)
|
|
39
|
-
})
|
|
40
|
-
return child
|
|
41
|
-
}) as unknown as typeof nodeSpawn
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function harnessFactory(options: HarnessOptions): Harness {
|
|
45
|
-
const harness = createHarness({ ...options, secrets: memorySecretStore(), piProgram: () => '/opt/pi/bin/pi' })
|
|
46
|
-
harnesses.push(harness)
|
|
47
|
-
return harness
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
describe('standalone terminal startup', () => {
|
|
51
|
-
it('routes the no-command and terminal invocations to Pi, leaving host commands explicit', () => {
|
|
52
|
-
expect(standaloneArguments([])).toEqual([])
|
|
53
|
-
expect(standaloneArguments(['--data', '/tmp/data'])).toEqual(['--data', '/tmp/data'])
|
|
54
|
-
expect(standaloneArguments(['terminal', '--state', '/tmp/state'])).toEqual(['--state', '/tmp/state'])
|
|
55
|
-
expect(standaloneArguments(['serve', '--state', '/tmp/state'])).toBeNull()
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('uses a dedicated default data and state root and accepts path overrides', () => {
|
|
59
|
-
expect(parseStandaloneOptions([], '/home/test', '/work/project')).toEqual({
|
|
60
|
-
dataRoot: '/home/test/.xingular-harness/data',
|
|
61
|
-
stateDir: '/home/test/.xingular-harness/state'
|
|
62
|
-
})
|
|
63
|
-
expect(parseStandaloneOptions(['--data', './knowledge', '--state', './device'], '/home/test', '/work/project')).toEqual({
|
|
64
|
-
dataRoot: '/work/project/knowledge',
|
|
65
|
-
stateDir: '/work/project/device'
|
|
66
|
-
})
|
|
67
|
-
expect(() => parseStandaloneOptions(['--data'])).toThrow('--data needs a directory path.')
|
|
68
|
-
expect(() => parseStandaloneOptions(['--model', 'unknown'])).toThrow('Unknown standalone option: --model')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('refuses default folders under a home that is not a folder path, and accepts explicit absolute folders', () => {
|
|
72
|
-
expect(() => parseStandaloneOptions([], 'HOME', '/work/project')).toThrow('is not an absolute path')
|
|
73
|
-
expect(parseStandaloneOptions(['--data', '/data', '--state', '/state'], 'HOME', '/work/project')).toEqual({ dataRoot: '/data', stateDir: '/state' })
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('starts Pi in the current directory, creates the first isolated account, and cleans up on exit', async () => {
|
|
77
|
-
const home = temporaryDirectory('harness-standalone-')
|
|
78
|
-
const cwd = temporaryDirectory('harness-cwd-')
|
|
79
|
-
const paths = parseStandaloneOptions([], home)
|
|
80
|
-
let invocation: { program: string; args: string[]; options: { cwd?: string; env?: NodeJS.ProcessEnv; stdio?: string } } | undefined
|
|
81
|
-
const spawn = fakeSpawn((program, args, options) => {
|
|
82
|
-
invocation = { program, args, options }
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
expect(await runStandaloneTerminal([], { home, cwd, stdinIsTTY: true, stdoutIsTTY: true, createSessionId: () => 'standalone', createHarness: harnessFactory, spawn })).toBe(0)
|
|
86
|
-
|
|
87
|
-
const harness = harnesses.shift()!
|
|
88
|
-
expect(harness.accounts.overview().accounts).toMatchObject([{ label: 'Default', isDefault: true }])
|
|
89
|
-
expect(invocation?.program).toBe('/opt/pi/bin/pi')
|
|
90
|
-
expect(invocation?.options).toMatchObject({ cwd, stdio: 'inherit' })
|
|
91
|
-
expect(invocation?.args).toContain('--extension')
|
|
92
|
-
expect(invocation?.args).toContain(defaultAssets().piExtension)
|
|
93
|
-
expect(invocation?.args).toContain(join(paths.dataRoot, 'sessions', 'terminal', createHash('sha256').update(resolve(cwd)).digest('hex'), 'pi'))
|
|
94
|
-
expect(invocation?.options.env?.PI_CODING_AGENT_DIR).toContain(join(paths.stateDir, 'accounts'))
|
|
95
|
-
expect(invocation?.options.env?.XINGULAR_HARNESS_SOCKET).toBeTruthy()
|
|
96
|
-
expect(invocation?.options.env?.XINGULAR_HARNESS_TOKEN).toBeTruthy()
|
|
97
|
-
expect(existsSync(join(paths.stateDir, 'accounts', harness.accounts.overview().accounts[0].id, 'home', 'pi', 'auth.json'))).toBe(false)
|
|
98
|
-
expect(existsSync(socketPath(paths.stateDir))).toBe(false)
|
|
99
|
-
expect(harness.terminals.has('standalone')).toBe(false)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('requires an interactive TTY before creating Harness data', async () => {
|
|
103
|
-
const create = vi.fn(harnessFactory)
|
|
104
|
-
await expect(runStandaloneTerminal([], { stdinIsTTY: false, stdoutIsTTY: true, createHarness: create })).rejects.toThrow('needs a TTY')
|
|
105
|
-
expect(create).not.toHaveBeenCalled()
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('cleans up the prepared terminal when Pi cannot be spawned', async () => {
|
|
109
|
-
const home = temporaryDirectory('harness-standalone-fail-')
|
|
110
|
-
const cwd = temporaryDirectory('harness-cwd-fail-')
|
|
111
|
-
const spawn = fakeSpawn(() => undefined, { error: new Error('spawn failed') })
|
|
112
|
-
|
|
113
|
-
await expect(runStandaloneTerminal([], { home, cwd, stdinIsTTY: true, stdoutIsTTY: true, createSessionId: () => 'failed', createHarness: harnessFactory, spawn })).rejects.toThrow('spawn failed')
|
|
114
|
-
|
|
115
|
-
const harness = harnesses.shift()!
|
|
116
|
-
const account = harness.accounts.overview().accounts[0]
|
|
117
|
-
expect(existsSync(join(harness.accounts.dir, account.id, 'home', 'pi', 'auth.json'))).toBe(false)
|
|
118
|
-
expect(existsSync(socketPath(harness.stateDir))).toBe(false)
|
|
119
|
-
expect(harness.terminals.has('failed')).toBe(false)
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('explains a missing Pi executable in terms of the terminal launch', async () => {
|
|
123
|
-
const home = temporaryDirectory('harness-standalone-enoent-')
|
|
124
|
-
const cwd = temporaryDirectory('harness-cwd-enoent-')
|
|
125
|
-
const error = Object.assign(new Error('spawn /opt/pi/bin/pi ENOENT'), { code: 'ENOENT' })
|
|
126
|
-
const spawn = fakeSpawn(() => undefined, { error })
|
|
127
|
-
|
|
128
|
-
await expect(runStandaloneTerminal([], { home, cwd, stdinIsTTY: true, stdoutIsTTY: true, createSessionId: () => 'missing-pi', createHarness: harnessFactory, spawn }))
|
|
129
|
-
.rejects.toThrow(/Could not start Pi terminal.*Pi executable.*\/opt\/pi\/bin\/pi/)
|
|
130
|
-
})
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
describe('switching the account of a running terminal', () => {
|
|
134
|
-
it('ends Pi when the person asks for another account, and starts it again on that account', async () => {
|
|
135
|
-
const home = temporaryDirectory('harness-switch-')
|
|
136
|
-
const cwd = temporaryDirectory('harness-switch-cwd-')
|
|
137
|
-
const paths = parseStandaloneOptions([], home)
|
|
138
|
-
const spawned: { args: string[]; env: NodeJS.ProcessEnv }[] = []
|
|
139
|
-
let spareId = ''
|
|
140
|
-
const spawn = ((_program: string, args: string[], options: { cwd?: string; env?: NodeJS.ProcessEnv; stdio?: string }) => {
|
|
141
|
-
spawned.push({ args, env: options.env ?? {} })
|
|
142
|
-
const child = new EventEmitter() as ChildProcess
|
|
143
|
-
child.kill = vi.fn(() => true)
|
|
144
|
-
void (async () => {
|
|
145
|
-
// Deferred, as a real process is: the launcher attaches its listeners after spawn returns.
|
|
146
|
-
await Promise.resolve()
|
|
147
|
-
if (spawned.length === 1) {
|
|
148
|
-
// The first Pi asks for the other account through its own extension, then ends.
|
|
149
|
-
spareId = harnesses[0].accounts.create('Spare', 'apiKey').accounts.find((account) => account.label === 'Spare')!.id
|
|
150
|
-
const connection = { socket: options.env![SOCKET_ENV]!, token: options.env![TOKEN_ENV]! }
|
|
151
|
-
const { key } = await socketCall<{ key: string }>(connection, 'control.claim')
|
|
152
|
-
await socketCall(connection, 'accounts.relaunch', { accountId: spareId }, 5_000, key)
|
|
153
|
-
}
|
|
154
|
-
child.emit('exit', 0, null)
|
|
155
|
-
})()
|
|
156
|
-
return child
|
|
157
|
-
}) as unknown as typeof nodeSpawn
|
|
158
|
-
|
|
159
|
-
expect(await runStandaloneTerminal([], { home, cwd, stdinIsTTY: true, stdoutIsTTY: true, createHarness: harnessFactory, spawn })).toBe(0)
|
|
160
|
-
|
|
161
|
-
expect(spawned).toHaveLength(2)
|
|
162
|
-
expect(spawned[1].env.PI_CODING_AGENT_DIR).toContain(join(paths.stateDir, 'accounts', spareId))
|
|
163
|
-
expect(spawned[1].env[TOKEN_ENV]).not.toBe(spawned[0].env[TOKEN_ENV])
|
|
164
|
-
// The session it ended is closed; nothing is left running in the harness.
|
|
165
|
-
expect(harnesses[0].terminals.has('standalone')).toBe(false)
|
|
166
|
-
})
|
|
167
|
-
})
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
// The standalone host prepares an isolated Harness terminal and supervises its frontend.
|
|
2
|
-
import { spawn as nodeSpawn } from 'node:child_process'
|
|
3
|
-
import { createHash, randomUUID } from 'node:crypto'
|
|
4
|
-
import { homedir } from 'node:os'
|
|
5
|
-
import { isAbsolute, join, resolve } from 'node:path'
|
|
6
|
-
|
|
7
|
-
import { createHarness, type Harness, type HarnessOptions } from '../harness'
|
|
8
|
-
import type { TerminalLaunch } from './terminals'
|
|
9
|
-
|
|
10
|
-
export interface StandalonePaths {
|
|
11
|
-
dataRoot: string
|
|
12
|
-
stateDir: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface StandaloneDependencies {
|
|
16
|
-
home?: string
|
|
17
|
-
cwd?: string
|
|
18
|
-
stdinIsTTY?: boolean
|
|
19
|
-
stdoutIsTTY?: boolean
|
|
20
|
-
createSessionId?: () => string
|
|
21
|
-
createHarness?: (options: HarnessOptions) => Harness
|
|
22
|
-
spawn?: typeof nodeSpawn
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function standaloneArguments(argv: string[]): string[] | null {
|
|
26
|
-
const command = argv[0]
|
|
27
|
-
if (!command || command.startsWith('--') || command === '-h') return argv
|
|
28
|
-
return command === 'terminal' ? argv.slice(1) : null
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function parseStandaloneOptions(argv: string[], home = homedir(), cwd = process.cwd()): StandalonePaths {
|
|
32
|
-
const root = join(home, '.xingular-harness')
|
|
33
|
-
const paths: StandalonePaths = {
|
|
34
|
-
dataRoot: join(root, 'data'),
|
|
35
|
-
stateDir: join(root, 'state')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const given = new Set<string>()
|
|
39
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
40
|
-
const argument = argv[index]
|
|
41
|
-
if (argument !== '--data' && argument !== '--state') throw new Error(`Unknown standalone option: ${argument}`)
|
|
42
|
-
const value = argv[index + 1]
|
|
43
|
-
if (!value || value.startsWith('--')) throw new Error(`${argument} needs a directory path.`)
|
|
44
|
-
paths[argument === '--data' ? 'dataRoot' : 'stateDir'] = resolve(cwd, value)
|
|
45
|
-
given.add(argument)
|
|
46
|
-
index += 1
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// A home that is not a folder path (for example HOME set to the word "HOME") would put the
|
|
50
|
-
// defaults, and the session socket, in whatever folder the command was started from.
|
|
51
|
-
if (!isAbsolute(home) && !(given.has('--data') && given.has('--state'))) {
|
|
52
|
-
throw new Error(`The home folder "${home}" is not an absolute path, so the default data and state folders cannot be placed. Set HOME to your home folder, or pass --data and --state with absolute folders.`)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return paths
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function processEnvironment(overrides: Record<string, string | null>): NodeJS.ProcessEnv {
|
|
59
|
-
const environment = { ...process.env }
|
|
60
|
-
for (const [name, value] of Object.entries(overrides)) {
|
|
61
|
-
if (value === null) delete environment[name]
|
|
62
|
-
else environment[name] = value
|
|
63
|
-
}
|
|
64
|
-
return environment
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function exitCode(code: number | null, signal: NodeJS.Signals | null): number {
|
|
68
|
-
if (code !== null) return code
|
|
69
|
-
if (signal === 'SIGINT') return 130
|
|
70
|
-
if (signal === 'SIGTERM') return 143
|
|
71
|
-
if (signal === 'SIGHUP') return 129
|
|
72
|
-
return 1
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function spawnError(error: Error, launch: TerminalLaunch): Error {
|
|
76
|
-
const code = (error as NodeJS.ErrnoException).code
|
|
77
|
-
if (code !== 'ENOENT' && code !== 'EACCES' && code !== 'EPERM') return error
|
|
78
|
-
if (launch.program === process.execPath || launch.env.XINGULAR_HARNESS_NODE === launch.program) {
|
|
79
|
-
return new Error(`Could not start the Harness terminal because Node could not run "${launch.args[0] ?? 'the bundled Harness CLI'}" (${code}). Verify Node and the Harness package are installed.`, { cause: error })
|
|
80
|
-
}
|
|
81
|
-
const problem = code === 'ENOENT' ? 'was not found' : `could not start (${code})`
|
|
82
|
-
return new Error(`Could not start Pi terminal. Pi executable "${launch.program}" ${problem}; verify it is installed and executable or check the configured terminal executable.`, { cause: error })
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function spawnPi(launch: TerminalLaunch, cwd: string, spawn = nodeSpawn): Promise<number> {
|
|
86
|
-
return new Promise((resolveExit, reject) => {
|
|
87
|
-
const child = spawn(launch.program, launch.args, {
|
|
88
|
-
cwd,
|
|
89
|
-
env: processEnvironment(launch.env),
|
|
90
|
-
stdio: 'inherit'
|
|
91
|
-
})
|
|
92
|
-
let settled = false
|
|
93
|
-
const cleanup = (): void => {
|
|
94
|
-
process.off('SIGINT', onInterrupt)
|
|
95
|
-
process.off('SIGTERM', onTerminate)
|
|
96
|
-
process.off('SIGHUP', onHangup)
|
|
97
|
-
}
|
|
98
|
-
const finish = (action: () => void): void => {
|
|
99
|
-
if (settled) return
|
|
100
|
-
settled = true
|
|
101
|
-
cleanup()
|
|
102
|
-
action()
|
|
103
|
-
}
|
|
104
|
-
// Ctrl+C is delivered to both processes in the foreground process group. Keep this host
|
|
105
|
-
// alive while Pi handles it so the account credentials and harness socket are cleaned up.
|
|
106
|
-
const onInterrupt = (): void => undefined
|
|
107
|
-
const onTerminate = (): void => {
|
|
108
|
-
child.kill('SIGTERM')
|
|
109
|
-
}
|
|
110
|
-
const onHangup = (): void => undefined
|
|
111
|
-
|
|
112
|
-
process.on('SIGINT', onInterrupt)
|
|
113
|
-
process.on('SIGTERM', onTerminate)
|
|
114
|
-
process.on('SIGHUP', onHangup)
|
|
115
|
-
child.once('error', (error) => finish(() => reject(spawnError(error, launch))))
|
|
116
|
-
child.once('exit', (code, signal) => finish(() => resolveExit(exitCode(code, signal))))
|
|
117
|
-
})
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export async function runStandaloneTerminal(argv: string[], dependencies: StandaloneDependencies = {}): Promise<number> {
|
|
121
|
-
if (!(dependencies.stdinIsTTY ?? process.stdin.isTTY) || !(dependencies.stdoutIsTTY ?? process.stdout.isTTY)) {
|
|
122
|
-
throw new Error('The interactive Harness terminal needs a TTY. Run it from a terminal, or use `xingular-harness serve` for a host process.')
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const cwd = resolve(dependencies.cwd ?? process.cwd())
|
|
126
|
-
const paths = parseStandaloneOptions(argv, dependencies.home, cwd)
|
|
127
|
-
const makeHarness = dependencies.createHarness ?? createHarness
|
|
128
|
-
const harness = makeHarness({ dataRoot: paths.dataRoot, stateDir: paths.stateDir, productName: 'Xingular Harness' })
|
|
129
|
-
const newSessionId = dependencies.createSessionId ?? randomUUID
|
|
130
|
-
let current = { sessionId: newSessionId(), accountId: null as string | null, nativeSessionId: null as string | null }
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
await harness.ready
|
|
134
|
-
if (harness.accounts.overview().accounts.length === 0) harness.accounts.create('Default', 'subscription')
|
|
135
|
-
|
|
136
|
-
const cwdHash = createHash('sha256').update(cwd).digest('hex')
|
|
137
|
-
const sessionFolder = join(paths.dataRoot, 'sessions', 'terminal', cwdHash)
|
|
138
|
-
for (;;) {
|
|
139
|
-
const launch = await harness.terminals.prepare({ ...current, cwd, sessionFolder })
|
|
140
|
-
const code = await spawnPi(launch, cwd, dependencies.spawn)
|
|
141
|
-
// `/accounts` asks for a switch and ends Pi. The conversation resumes on the chosen account.
|
|
142
|
-
const next = harness.terminals.takeRelaunch(current.sessionId)
|
|
143
|
-
if (!next) return code
|
|
144
|
-
const label = harness.accounts.find(next.accountId).label
|
|
145
|
-
process.stdout.write(`Switching this session to account ${label}.\n`)
|
|
146
|
-
await harness.terminals.dispose(current.sessionId)
|
|
147
|
-
current = { sessionId: newSessionId(), accountId: next.accountId, nativeSessionId: next.nativeSessionId }
|
|
148
|
-
}
|
|
149
|
-
} finally {
|
|
150
|
-
await harness.terminals.dispose(current.sessionId)
|
|
151
|
-
await harness.close()
|
|
152
|
-
}
|
|
153
|
-
}
|