@xingular/harness 0.1.6 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -0
- package/LICENSE +202 -0
- package/NOTICE.md +60 -0
- package/README.md +265 -71
- package/THIRD_PARTY_LICENSES.md +942 -0
- package/dist/cli.js +14842 -9445
- package/dist/sdk/accounts/accounts.d.ts +165 -0
- package/dist/sdk/accounts/accounts.js +730 -0
- package/dist/sdk/accounts/choices.d.ts +23 -0
- package/dist/sdk/accounts/choices.js +62 -0
- package/dist/sdk/accounts/env.d.ts +78 -0
- package/dist/sdk/accounts/env.js +294 -0
- package/dist/sdk/accounts/secrets.d.ts +16 -0
- package/dist/sdk/accounts/secrets.js +40 -0
- package/dist/sdk/accounts/store.d.ts +114 -0
- package/dist/sdk/accounts/store.js +255 -0
- package/dist/sdk/browser/config.d.ts +16 -0
- package/dist/sdk/browser/config.js +22 -0
- package/dist/sdk/browser/diagnostics.d.ts +4 -0
- package/dist/sdk/browser/diagnostics.js +38 -0
- package/dist/sdk/browser/index.d.ts +17 -0
- package/dist/sdk/browser/index.js +65 -0
- package/dist/sdk/browser/network.d.ts +10 -0
- package/dist/sdk/browser/network.js +59 -0
- package/dist/sdk/browser/session.d.ts +84 -0
- package/dist/sdk/browser/session.js +192 -0
- package/dist/sdk/browser/tools.d.ts +8 -0
- package/dist/sdk/browser/tools.js +329 -0
- package/dist/sdk/capabilities/api.d.ts +22 -0
- package/dist/sdk/capabilities/api.js +54 -0
- package/dist/sdk/capabilities/runSnapshot.d.ts +18 -0
- package/dist/sdk/capabilities/runSnapshot.js +47 -0
- package/dist/sdk/capabilities/types.d.ts +29 -0
- package/dist/sdk/capabilities/types.js +1 -0
- package/dist/sdk/cli.d.ts +2 -0
- package/dist/sdk/cli.js +142 -0
- package/dist/sdk/core/bus.d.ts +23 -0
- package/dist/sdk/core/bus.js +76 -0
- package/dist/sdk/core/caller.d.ts +22 -0
- package/dist/sdk/core/caller.js +16 -0
- package/dist/sdk/core/changes.d.ts +15 -0
- package/dist/sdk/core/changes.js +15 -0
- package/dist/sdk/core/effort.d.ts +2 -0
- package/dist/sdk/core/effort.js +1 -0
- package/dist/sdk/core/env.d.ts +18 -0
- package/dist/sdk/core/env.js +8 -0
- package/dist/sdk/core/errors.d.ts +15 -0
- package/dist/sdk/core/errors.js +24 -0
- package/dist/sdk/core/frontmatter.d.ts +12 -0
- package/dist/sdk/core/frontmatter.js +50 -0
- package/dist/sdk/core/fs.d.ts +18 -0
- package/dist/sdk/core/fs.js +106 -0
- package/dist/sdk/core/index.d.ts +24 -0
- package/dist/sdk/core/index.js +187 -0
- package/dist/sdk/core/path.d.ts +7 -0
- package/dist/sdk/core/path.js +79 -0
- package/dist/sdk/core/projects.d.ts +18 -0
- package/dist/sdk/core/projects.js +67 -0
- package/dist/sdk/core/secrets.d.ts +2 -0
- package/dist/sdk/core/secrets.js +5 -0
- package/dist/sdk/core/text.d.ts +7 -0
- package/dist/sdk/core/text.js +40 -0
- package/dist/sdk/doctor/checks.d.ts +5 -0
- package/dist/sdk/doctor/checks.js +208 -0
- package/dist/sdk/doctor/probe.d.ts +4 -0
- package/dist/sdk/doctor/probe.js +92 -0
- package/dist/sdk/doctor/service.d.ts +23 -0
- package/dist/sdk/doctor/service.js +144 -0
- package/dist/sdk/doctor/types.d.ts +141 -0
- package/dist/sdk/doctor/types.js +29 -0
- package/dist/sdk/harness.d.ts +133 -0
- package/dist/sdk/harness.js +299 -0
- package/dist/sdk/history/lessonPrompt.d.ts +2 -0
- package/dist/sdk/history/lessonPrompt.js +15 -0
- package/dist/sdk/history/lessons.d.ts +52 -0
- package/dist/sdk/history/lessons.js +173 -0
- package/dist/sdk/history/recall.d.ts +68 -0
- package/dist/sdk/history/recall.js +199 -0
- package/dist/sdk/history/trajectory.d.ts +7 -0
- package/dist/sdk/history/trajectory.js +216 -0
- package/dist/sdk/hub/catalog.json +123 -0
- package/dist/sdk/hub/cli.d.ts +7 -0
- package/dist/sdk/hub/cli.js +81 -0
- package/dist/sdk/hub/hub.d.ts +74 -0
- package/dist/sdk/hub/hub.js +474 -0
- package/dist/sdk/hub/schema.d.ts +280 -0
- package/dist/sdk/hub/schema.js +78 -0
- package/dist/sdk/index.d.ts +63 -0
- package/dist/sdk/index.js +48 -0
- package/dist/sdk/mcp/connections.d.ts +83 -0
- package/dist/sdk/mcp/connections.js +669 -0
- package/dist/sdk/mcp/manager.d.ts +26 -0
- package/dist/sdk/mcp/manager.js +124 -0
- package/dist/sdk/mcp/oauth.d.ts +35 -0
- package/dist/sdk/mcp/oauth.js +171 -0
- package/dist/sdk/mcp/scopeMigration.d.ts +19 -0
- package/dist/sdk/mcp/scopeMigration.js +198 -0
- package/dist/sdk/mcp/service.d.ts +160 -0
- package/dist/sdk/mcp/service.js +510 -0
- package/dist/sdk/mcp/types.d.ts +131 -0
- package/dist/sdk/mcp/types.js +49 -0
- package/dist/sdk/pi/runtime.d.ts +13 -0
- package/dist/sdk/pi/runtime.js +31 -0
- package/dist/sdk/plugins/api.d.ts +66 -0
- package/dist/sdk/plugins/api.js +155 -0
- package/dist/sdk/plugins/attachments.d.ts +46 -0
- package/dist/sdk/plugins/attachments.js +111 -0
- package/dist/sdk/plugins/builtin.d.ts +6 -0
- package/dist/sdk/plugins/builtin.js +68 -0
- package/dist/sdk/plugins/catalogue.d.ts +29 -0
- package/dist/sdk/plugins/catalogue.js +196 -0
- package/dist/sdk/plugins/cli.d.ts +13 -0
- package/dist/sdk/plugins/cli.js +187 -0
- package/dist/sdk/plugins/discover.d.ts +42 -0
- package/dist/sdk/plugins/discover.js +115 -0
- package/dist/sdk/plugins/firstParty.d.ts +50 -0
- package/dist/sdk/plugins/firstParty.js +113 -0
- package/dist/sdk/plugins/hooks.d.ts +94 -0
- package/dist/sdk/plugins/hooks.js +162 -0
- package/dist/sdk/plugins/identity.d.ts +19 -0
- package/dist/sdk/plugins/identity.js +48 -0
- package/dist/sdk/plugins/install.d.ts +20 -0
- package/dist/sdk/plugins/install.js +110 -0
- package/dist/sdk/plugins/inventory.d.ts +22 -0
- package/dist/sdk/plugins/inventory.js +110 -0
- package/dist/sdk/plugins/manifest.d.ts +13 -0
- package/dist/sdk/plugins/manifest.js +56 -0
- package/dist/sdk/plugins/plugins.d.ts +104 -0
- package/dist/sdk/plugins/plugins.js +164 -0
- package/dist/sdk/plugins/runtime.d.ts +9 -0
- package/dist/sdk/plugins/runtime.js +95 -0
- package/dist/sdk/plugins/trust.d.ts +44 -0
- package/dist/sdk/plugins/trust.js +120 -0
- package/dist/sdk/plugins/types.d.ts +159 -0
- package/dist/sdk/plugins/types.js +7 -0
- package/dist/sdk/prompt/compile.d.ts +174 -0
- package/dist/sdk/prompt/compile.js +396 -0
- package/dist/sdk/refine/cli.d.ts +4 -0
- package/dist/sdk/refine/cli.js +186 -0
- package/dist/sdk/refine/demo.d.ts +21 -0
- package/dist/sdk/refine/demo.js +114 -0
- package/dist/sdk/refine/evidence.d.ts +55 -0
- package/dist/sdk/refine/evidence.js +330 -0
- package/dist/sdk/refine/host.d.ts +51 -0
- package/dist/sdk/refine/host.js +76 -0
- package/dist/sdk/refine/ledger.d.ts +48 -0
- package/dist/sdk/refine/ledger.js +76 -0
- package/dist/sdk/refine/library.d.ts +55 -0
- package/dist/sdk/refine/library.js +177 -0
- package/dist/sdk/refine/pi.d.ts +72 -0
- package/dist/sdk/refine/pi.js +248 -0
- package/dist/sdk/refine/prompts.d.ts +45 -0
- package/dist/sdk/refine/prompts.js +390 -0
- package/dist/sdk/refine/recursion.d.ts +113 -0
- package/dist/sdk/refine/recursion.js +258 -0
- package/dist/sdk/refine/runner.d.ts +81 -0
- package/dist/sdk/refine/runner.js +150 -0
- package/dist/sdk/refine/selection.d.ts +57 -0
- package/dist/sdk/refine/selection.js +251 -0
- package/dist/sdk/refine/service.d.ts +84 -0
- package/dist/sdk/refine/service.js +165 -0
- package/dist/sdk/refine/suite.d.ts +67 -0
- package/dist/sdk/refine/suite.js +174 -0
- package/dist/sdk/refine/trajectory.d.ts +9 -0
- package/dist/sdk/refine/trajectory.js +68 -0
- package/dist/sdk/refine/types.d.ts +77 -0
- package/dist/sdk/refine/types.js +10 -0
- package/dist/sdk/server/methods.d.ts +3 -0
- package/dist/sdk/server/methods.js +370 -0
- package/dist/sdk/server/remote.d.ts +19 -0
- package/dist/sdk/server/remote.js +84 -0
- package/dist/sdk/server/rpc.d.ts +20 -0
- package/dist/sdk/server/rpc.js +102 -0
- package/dist/sdk/server/serve.d.ts +10 -0
- package/dist/sdk/server/serve.js +18 -0
- package/dist/sdk/sessions/approvals.d.ts +47 -0
- package/dist/sdk/sessions/approvals.js +119 -0
- package/dist/sdk/sessions/continuation.d.ts +48 -0
- package/dist/sdk/sessions/continuation.js +85 -0
- package/dist/sdk/sessions/contracts.d.ts +534 -0
- package/dist/sdk/sessions/contracts.js +299 -0
- package/dist/sdk/sessions/extensionUi.d.ts +19 -0
- package/dist/sdk/sessions/extensionUi.js +64 -0
- package/dist/sdk/sessions/history.d.ts +16 -0
- package/dist/sdk/sessions/history.js +68 -0
- package/dist/sdk/sessions/lineageJoin.d.ts +2 -0
- package/dist/sdk/sessions/lineageJoin.js +61 -0
- package/dist/sdk/sessions/planMode.d.ts +36 -0
- package/dist/sdk/sessions/planMode.js +84 -0
- package/dist/sdk/sessions/policy.d.ts +43 -0
- package/dist/sdk/sessions/policy.js +1 -0
- package/dist/sdk/sessions/pricing.d.ts +34 -0
- package/dist/sdk/sessions/pricing.js +40 -0
- package/dist/sdk/sessions/reasoningSummaries.d.ts +11 -0
- package/dist/sdk/sessions/reasoningSummaries.js +61 -0
- package/dist/sdk/sessions/registry.d.ts +102 -0
- package/dist/sdk/sessions/registry.js +383 -0
- package/dist/sdk/sessions/runIndex.d.ts +21 -0
- package/dist/sdk/sessions/runIndex.js +65 -0
- package/dist/sdk/sessions/runStore.d.ts +66 -0
- package/dist/sdk/sessions/runStore.js +335 -0
- package/dist/sdk/sessions/runner.d.ts +213 -0
- package/dist/sdk/sessions/runner.js +589 -0
- package/dist/sdk/sessions/sessions.d.ts +140 -0
- package/dist/sdk/sessions/sessions.js +394 -0
- package/dist/sdk/sessions/subagentService.d.ts +41 -0
- package/dist/sdk/sessions/subagentService.js +247 -0
- package/dist/sdk/sessions/subagents.d.ts +50 -0
- package/dist/sdk/sessions/subagents.js +68 -0
- package/dist/sdk/terminal/accountsMenu.d.ts +46 -0
- package/dist/sdk/terminal/accountsMenu.js +392 -0
- package/dist/sdk/terminal/cli.d.ts +20 -0
- package/dist/sdk/terminal/cli.js +121 -0
- package/dist/sdk/terminal/client.d.ts +15 -0
- package/dist/sdk/terminal/client.js +126 -0
- package/dist/sdk/terminal/control.d.ts +4 -0
- package/dist/sdk/terminal/control.js +25 -0
- package/dist/sdk/terminal/controller.d.ts +72 -0
- package/dist/sdk/terminal/controller.js +351 -0
- package/dist/sdk/terminal/hubMenu.d.ts +10 -0
- package/dist/sdk/terminal/hubMenu.js +34 -0
- package/dist/sdk/terminal/launch.d.ts +5 -0
- package/dist/sdk/terminal/launch.js +27 -0
- package/dist/sdk/terminal/logs.d.ts +14 -0
- package/dist/sdk/terminal/logs.js +59 -0
- package/dist/sdk/terminal/piExtension.d.ts +6 -0
- package/dist/sdk/terminal/piExtension.js +392 -0
- package/dist/sdk/terminal/pluginMenu.d.ts +34 -0
- package/dist/sdk/terminal/pluginMenu.js +101 -0
- package/dist/sdk/terminal/socket.d.ts +70 -0
- package/dist/sdk/terminal/socket.js +256 -0
- package/dist/sdk/terminal/standalone.d.ts +18 -0
- package/dist/sdk/terminal/standalone.js +146 -0
- package/dist/sdk/terminal/terminals.d.ts +176 -0
- package/dist/sdk/terminal/terminals.js +767 -0
- package/dist/sdk/terminal/trajectory.d.ts +10 -0
- package/dist/sdk/terminal/trajectory.js +30 -0
- package/dist/sdk/terminal/tui/chrome.d.ts +64 -0
- package/dist/sdk/terminal/tui/chrome.js +169 -0
- package/dist/sdk/terminal/tui/clipboardFiles.d.ts +11 -0
- package/dist/sdk/terminal/tui/clipboardFiles.js +74 -0
- package/dist/sdk/terminal/tui/commandPages.d.ts +7 -0
- package/dist/sdk/terminal/tui/commandPages.js +63 -0
- package/dist/sdk/terminal/tui/commands.d.ts +42 -0
- package/dist/sdk/terminal/tui/commands.js +276 -0
- package/dist/sdk/terminal/tui/dialogGeometry.d.ts +27 -0
- package/dist/sdk/terminal/tui/dialogGeometry.js +50 -0
- package/dist/sdk/terminal/tui/dialogs.d.ts +75 -0
- package/dist/sdk/terminal/tui/dialogs.js +363 -0
- package/dist/sdk/terminal/tui/keybindings.d.ts +11 -0
- package/dist/sdk/terminal/tui/keybindings.js +49 -0
- package/dist/sdk/terminal/tui/list.d.ts +73 -0
- package/dist/sdk/terminal/tui/list.js +196 -0
- package/dist/sdk/terminal/tui/modeChoices.d.ts +42 -0
- package/dist/sdk/terminal/tui/modeChoices.js +70 -0
- package/dist/sdk/terminal/tui/modelPicker.d.ts +55 -0
- package/dist/sdk/terminal/tui/modelPicker.js +155 -0
- package/dist/sdk/terminal/tui/mouse.d.ts +3 -0
- package/dist/sdk/terminal/tui/mouse.js +3 -0
- package/dist/sdk/terminal/tui/page.d.ts +14 -0
- package/dist/sdk/terminal/tui/page.js +1 -0
- package/dist/sdk/terminal/tui/pageOverlay.d.ts +27 -0
- package/dist/sdk/terminal/tui/pageOverlay.js +145 -0
- package/dist/sdk/terminal/tui/pages/accountsPage.d.ts +54 -0
- package/dist/sdk/terminal/tui/pages/accountsPage.js +181 -0
- package/dist/sdk/terminal/tui/pages/componentsPage.d.ts +47 -0
- package/dist/sdk/terminal/tui/pages/componentsPage.js +119 -0
- package/dist/sdk/terminal/tui/pages/doctorPage.d.ts +37 -0
- package/dist/sdk/terminal/tui/pages/doctorPage.js +145 -0
- package/dist/sdk/terminal/tui/pages/hubPage.d.ts +39 -0
- package/dist/sdk/terminal/tui/pages/hubPage.js +128 -0
- package/dist/sdk/terminal/tui/pages/logPage.d.ts +38 -0
- package/dist/sdk/terminal/tui/pages/logPage.js +189 -0
- package/dist/sdk/terminal/tui/pages/permissionsPage.d.ts +44 -0
- package/dist/sdk/terminal/tui/pages/permissionsPage.js +124 -0
- package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +57 -0
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +243 -0
- package/dist/sdk/terminal/tui/pages/settingsPage.d.ts +55 -0
- package/dist/sdk/terminal/tui/pages/settingsPage.js +88 -0
- package/dist/sdk/terminal/tui/pages/trajectoryPage.d.ts +58 -0
- package/dist/sdk/terminal/tui/pages/trajectoryPage.js +377 -0
- package/dist/sdk/terminal/tui/piAdapter.d.ts +18 -0
- package/dist/sdk/terminal/tui/piAdapter.js +48 -0
- package/dist/sdk/terminal/tui/planMode.d.ts +2 -0
- package/dist/sdk/terminal/tui/planMode.js +2 -0
- package/dist/sdk/terminal/tui/questionPicker.d.ts +20 -0
- package/dist/sdk/terminal/tui/questionPicker.js +150 -0
- package/dist/sdk/terminal/tui/questions.d.ts +19 -0
- package/dist/sdk/terminal/tui/questions.js +42 -0
- package/dist/sdk/terminal/tui/runtime.d.ts +114 -0
- package/dist/sdk/terminal/tui/runtime.js +441 -0
- package/dist/sdk/terminal/tui/search.d.ts +22 -0
- package/dist/sdk/terminal/tui/search.js +60 -0
- package/dist/sdk/terminal/tui/selection.d.ts +16 -0
- package/dist/sdk/terminal/tui/selection.js +16 -0
- package/dist/sdk/terminal/tui/sessionUi.d.ts +3 -0
- package/dist/sdk/terminal/tui/sessionUi.js +30 -0
- package/dist/sdk/terminal/tui/settingsModel.d.ts +12 -0
- package/dist/sdk/terminal/tui/settingsModel.js +84 -0
- package/dist/sdk/terminal/tui/sideQuestion.d.ts +53 -0
- package/dist/sdk/terminal/tui/sideQuestion.js +197 -0
- package/dist/sdk/terminal/tui/skillInvocation.d.ts +9 -0
- package/dist/sdk/terminal/tui/skillInvocation.js +43 -0
- package/dist/sdk/terminal/tui/status.d.ts +18 -0
- package/dist/sdk/terminal/tui/status.js +67 -0
- package/dist/sdk/terminal/tui/theme.d.ts +45 -0
- package/dist/sdk/terminal/tui/theme.js +176 -0
- package/dist/sdk/terminal/tui/treeModel.d.ts +79 -0
- package/dist/sdk/terminal/tui/treeModel.js +483 -0
- package/dist/sdk/terminal/tui/treePicker.d.ts +49 -0
- package/dist/sdk/terminal/tui/treePicker.js +251 -0
- package/dist/sdk/terminal/tui/ui.d.ts +47 -0
- package/dist/sdk/terminal/tui/ui.js +148 -0
- package/dist/sdk/terminal/tui/workingStatus.d.ts +9 -0
- package/dist/sdk/terminal/tui/workingStatus.js +229 -0
- package/dist/sdk/tools/askUser.d.ts +6 -0
- package/dist/sdk/tools/askUser.js +74 -0
- package/dist/sdk/tools/checklist.d.ts +9 -0
- package/dist/sdk/tools/checklist.js +54 -0
- package/dist/sdk/tools/definitions.d.ts +30 -0
- package/dist/sdk/tools/definitions.js +117 -0
- package/dist/sdk/tools/dispatch.d.ts +16 -0
- package/dist/sdk/tools/dispatch.js +67 -0
- package/dist/sdk/tools/harnessTools.d.ts +6 -0
- package/dist/sdk/tools/harnessTools.js +20 -0
- package/dist/sdk/tools/sessionName.d.ts +7 -0
- package/dist/sdk/tools/sessionName.js +31 -0
- package/dist/sdk/tools/spawnSubagent.d.ts +4 -0
- package/dist/sdk/tools/spawnSubagent.js +49 -0
- package/dist/sdk/tools/types.d.ts +48 -0
- package/dist/sdk/tools/types.js +1 -0
- package/dist/sdk/variant/components.d.ts +34 -0
- package/dist/sdk/variant/components.js +230 -0
- package/dist/sdk/variant/materialise.d.ts +66 -0
- package/dist/sdk/variant/materialise.js +81 -0
- package/dist/sdk/variant/migrate.d.ts +4 -0
- package/dist/sdk/variant/migrate.js +41 -0
- package/dist/sdk/variant/seed.d.ts +20 -0
- package/dist/sdk/variant/seed.js +116 -0
- package/dist/sdk/variant/store.d.ts +82 -0
- package/dist/sdk/variant/store.js +151 -0
- package/dist/sdk/variant/surface.d.ts +56 -0
- package/dist/sdk/variant/surface.js +81 -0
- package/dist/sdk/variant/trust.d.ts +27 -0
- package/dist/sdk/variant/trust.js +64 -0
- package/dist/sdk/variant/variant.d.ts +71 -0
- package/dist/sdk/variant/variant.js +331 -0
- package/dist/sdk/web/markdown.d.ts +10 -0
- package/dist/sdk/web/markdown.js +66 -0
- package/dist/sdk/web/safeRequest.d.ts +34 -0
- package/dist/sdk/web/safeRequest.js +175 -0
- package/dist/sdk/web/search.d.ts +53 -0
- package/dist/sdk/web/search.js +168 -0
- package/dist/sdk/web/text.d.ts +2 -0
- package/dist/sdk/web/text.js +7 -0
- package/dist/sdk/web/tools.d.ts +22 -0
- package/dist/sdk/web/tools.js +195 -0
- package/dist/sdk/web/turndown-plugin-gfm.d.ts +4 -0
- package/dist/sdk/wiki/notes.d.ts +68 -0
- package/dist/sdk/wiki/notes.js +297 -0
- package/dist/sdk/world/actions.d.ts +23 -0
- package/dist/sdk/world/actions.js +31 -0
- package/dist/sdk/world/cli.d.ts +12 -0
- package/dist/sdk/world/cli.js +166 -0
- package/dist/sdk/world/images.d.ts +20 -0
- package/dist/sdk/world/images.js +48 -0
- package/dist/sdk/world/untrusted.d.ts +12 -0
- package/dist/sdk/world/untrusted.js +20 -0
- package/dist/sdk/world/world.d.ts +65 -0
- package/dist/sdk/world/world.js +80 -0
- package/dist/terminal-opentui.js +13612 -0
- package/dist/terminal-pi.js +1170 -62
- package/docs/README.md +34 -0
- package/docs/architecture/00-overview.md +159 -0
- package/docs/architecture/01-extension-points.md +78 -0
- package/docs/architecture/02-sessions-and-runs.md +146 -0
- package/docs/architecture/03-variants.md +95 -0
- package/docs/architecture/04-prompt.md +102 -0
- package/docs/architecture/05-refinement.md +107 -0
- package/docs/architecture/06-plugins-and-capabilities.md +93 -0
- package/docs/architecture/07-tools-and-gate.md +88 -0
- package/docs/architecture/08-memory-and-history.md +94 -0
- package/docs/architecture/09-accounts-and-connectors.md +90 -0
- package/docs/architecture/10-server-protocol.md +84 -0
- package/docs/architecture/11-terminal.md +81 -0
- package/docs/architecture/12-storage.md +61 -0
- package/docs/architecture/13-world-tools.md +23 -0
- package/docs/architecture/README.md +114 -0
- package/docs/architecture/proposals/PROP-0001-reporting-section.md +106 -0
- package/docs/architecture/proposals/PROP-0002-retain-rollout-workspaces.md +104 -0
- package/docs/architecture/proposals/PROP-0003-executor-settings.md +81 -0
- package/docs/architecture/proposals/PROP-0004-flexible-reporting-guidance.md +71 -0
- package/docs/architecture/proposals/PROP-0005-xharness-cli-name.md +78 -0
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +68 -0
- package/docs/architecture/proposals/PROP-0007-run-continuation.md +80 -0
- package/docs/architecture/proposals/PROP-0008-lessons-from-runs.md +80 -0
- package/docs/architecture/proposals/PROP-0009-foundation-and-variants.md +78 -0
- package/docs/architecture/proposals/PROP-0010-session-names.md +73 -0
- package/docs/architecture/proposals/PROP-0011-doctor.md +63 -0
- package/docs/architecture/proposals/PROP-0012-release-baseline.md +56 -0
- package/docs/architecture/proposals/README.md +76 -0
- package/docs/decisions/ADR-0001-owned-terminal.md +87 -0
- package/docs/decisions/ADR-0002-native-subagent-lifecycle.md +49 -0
- package/docs/decisions/ADR-0003-shared-asynchronous-subagents.md +70 -0
- package/docs/decisions/ADR-0004-fullscreen-terminal-layout.md +50 -0
- package/docs/decisions/ADR-0005-app-provided-oauth-clients.md +54 -0
- package/docs/decisions/ADR-0006-opentui-only-terminal.md +47 -0
- package/docs/decisions/ADR-0007-reporting-section.md +33 -0
- package/docs/decisions/ADR-0008-flexible-reporting-guidance.md +38 -0
- package/docs/decisions/ADR-0009-xharness-command.md +38 -0
- package/docs/decisions/ADR-0010-lessons-in-the-wiki.md +31 -0
- package/docs/decisions/ADR-0011-foundation-and-variants.md +28 -0
- package/docs/decisions/ADR-0012-native-session-names.md +34 -0
- package/docs/decisions/ADR-0013-doctor.md +27 -0
- package/docs/decisions/ADR-0014-public-release-baseline.md +21 -0
- package/docs/doctor.md +74 -0
- package/docs/experiments/EXP-0001-terminal-input-latency/plan.md +81 -0
- package/docs/experiments/EXP-0001-terminal-input-latency/results.md +46 -0
- package/docs/experiments/EXP-0002-opentui-typing-latency/plan.md +70 -0
- package/docs/experiments/EXP-0002-opentui-typing-latency/results.md +92 -0
- package/docs/features/README.md +20 -0
- package/docs/features/automations/README.md +41 -0
- package/docs/features/cores/README.md +39 -0
- package/docs/features/google-harness2/README.md +52 -0
- package/docs/features/integrations/README.md +40 -0
- package/docs/features/interfaces/README.md +29 -0
- package/docs/features/memory/README.md +28 -0
- package/docs/releases/1.0.0.md +40 -0
- package/docs/releases/README.md +36 -0
- package/docs/study/00-index.md +78 -0
- package/docs/study/arms/00-matrix.md +20 -0
- package/docs/study/arms/01-system-prompt.md +211 -0
- package/docs/study/arms/02-tools.md +79 -0
- package/docs/study/arms/03-subagents.md +90 -0
- package/docs/study/arms/04-skills.md +75 -0
- package/docs/study/arms/05-context-management.md +90 -0
- package/docs/study/arms/06-memory.md +90 -0
- package/docs/study/arms/07-governance.md +71 -0
- package/docs/study/arms/08-execution.md +69 -0
- package/docs/study/arms/09-observability.md +60 -0
- package/docs/study/arms/10-refinement.md +110 -0
- package/docs/study/btw-comparison.md +37 -0
- package/docs/study/feature-gap-review.md +42 -0
- package/docs/study/insights.md +123 -0
- package/docs/study/roadmap.md +72 -0
- package/docs/study/sources/claude-code.md +44 -0
- package/docs/study/sources/deepseek-harness.md +102 -0
- package/docs/study/sources/harness2.md +50 -0
- package/docs/study/sources/kimi-cli.md +42 -0
- package/docs/study/sources/omp.md +93 -0
- package/docs/study/sources/opencode.md +43 -0
- package/docs/study/sources/openhands.md +46 -0
- package/docs/study/sources/smolagents.md +40 -0
- package/docs/study/sources/swe-agent.md +34 -0
- package/docs/system-prompt-design.md +196 -0
- package/docs/terminal-tui.md +671 -0
- package/package.json +48 -12
- package/src/accounts/accounts.test.ts +0 -223
- package/src/accounts/accounts.ts +0 -522
- package/src/accounts/env.test.ts +0 -95
- package/src/accounts/env.ts +0 -338
- package/src/accounts/secrets.ts +0 -50
- package/src/accounts/store.ts +0 -338
- package/src/capabilities/api.ts +0 -75
- package/src/capabilities/runSnapshot.ts +0 -59
- package/src/capabilities/types.ts +0 -34
- package/src/cli.ts +0 -114
- package/src/core/bus.ts +0 -34
- package/src/core/changes.ts +0 -33
- package/src/core/env.ts +0 -27
- package/src/core/errors.ts +0 -32
- package/src/core/frontmatter.ts +0 -47
- package/src/core/fs.test.ts +0 -38
- package/src/core/fs.ts +0 -92
- package/src/core/index.ts +0 -193
- package/src/core/path.test.ts +0 -14
- package/src/core/path.ts +0 -79
- package/src/core/projects.ts +0 -90
- package/src/core/text.ts +0 -39
- package/src/harness.ts +0 -299
- package/src/index.ts +0 -70
- package/src/mcp/connections.ts +0 -614
- package/src/mcp/manager.ts +0 -130
- package/src/mcp/oauth.ts +0 -195
- package/src/mcp/scopeMigration.test.ts +0 -79
- package/src/mcp/scopeMigration.ts +0 -204
- package/src/mcp/service.test.ts +0 -376
- package/src/mcp/service.ts +0 -549
- package/src/mcp/types.ts +0 -108
- package/src/memory/api.test.ts +0 -82
- package/src/memory/api.ts +0 -289
- package/src/memory/blocks.test.ts +0 -78
- package/src/memory/blocks.ts +0 -406
- package/src/memory/caller.ts +0 -57
- package/src/memory/dispatch.ts +0 -59
- package/src/memory/facts.test.ts +0 -41
- package/src/memory/facts.ts +0 -248
- package/src/memory/graph.ts +0 -103
- package/src/memory/health.test.ts +0 -130
- package/src/memory/health.ts +0 -74
- package/src/memory/maintainer.ts +0 -272
- package/src/memory/maintenance.test.ts +0 -176
- package/src/memory/maintenance.ts +0 -330
- package/src/memory/migrate.ts +0 -79
- package/src/memory/operations.ts +0 -138
- package/src/memory/proposals.test.ts +0 -168
- package/src/memory/proposals.ts +0 -576
- package/src/memory/recall.test.ts +0 -104
- package/src/memory/recall.ts +0 -271
- package/src/memory/references.ts +0 -77
- package/src/memory/runContext.ts +0 -93
- package/src/memory/secrets.ts +0 -6
- package/src/memory/store.test.ts +0 -66
- package/src/memory/store.ts +0 -385
- package/src/memory/wiki.test.ts +0 -47
- package/src/memory/wiki.ts +0 -422
- package/src/memory/wikiIndex.test.ts +0 -88
- package/src/memory/wikiIndex.ts +0 -116
- package/src/plugins/api.ts +0 -181
- package/src/plugins/attachments.test.ts +0 -84
- package/src/plugins/attachments.ts +0 -141
- package/src/plugins/builtin.ts +0 -82
- package/src/plugins/catalogue.test.ts +0 -254
- package/src/plugins/catalogue.ts +0 -251
- package/src/plugins/cli.ts +0 -184
- package/src/plugins/discover.ts +0 -151
- package/src/plugins/firstParty.test.ts +0 -143
- package/src/plugins/firstParty.ts +0 -129
- package/src/plugins/hooks.test.ts +0 -250
- package/src/plugins/hooks.ts +0 -239
- package/src/plugins/identity.ts +0 -37
- package/src/plugins/install.test.ts +0 -107
- package/src/plugins/install.ts +0 -106
- package/src/plugins/inventory.ts +0 -123
- package/src/plugins/manifest.ts +0 -76
- package/src/plugins/plugins.test.ts +0 -205
- package/src/plugins/plugins.ts +0 -189
- package/src/plugins/proposal.ts +0 -121
- package/src/plugins/runtime.test.ts +0 -40
- package/src/plugins/runtime.ts +0 -82
- package/src/plugins/trust.ts +0 -142
- package/src/plugins/types.ts +0 -149
- package/src/prompt/compile.test.ts +0 -261
- package/src/prompt/compile.ts +0 -732
- package/src/server/methods.ts +0 -314
- package/src/server/remote.ts +0 -77
- package/src/server/rpc.ts +0 -116
- package/src/server/serve.ts +0 -28
- package/src/server/server.test.ts +0 -174
- package/src/sessions/approvals.ts +0 -118
- package/src/sessions/contracts.ts +0 -310
- package/src/sessions/conversation.test.ts +0 -150
- package/src/sessions/extensionSession.test.ts +0 -74
- package/src/sessions/extensionUi.test.ts +0 -65
- package/src/sessions/extensionUi.ts +0 -102
- package/src/sessions/history.ts +0 -51
- package/src/sessions/lineageJoin.ts +0 -75
- package/src/sessions/policy.ts +0 -32
- package/src/sessions/pricing.ts +0 -60
- package/src/sessions/reasoningSummaries.ts +0 -59
- package/src/sessions/registry.ts +0 -294
- package/src/sessions/runIndex.ts +0 -99
- package/src/sessions/runStore.test.ts +0 -152
- package/src/sessions/runStore.ts +0 -332
- package/src/sessions/runner.ts +0 -569
- package/src/sessions/runtimeResources.test.ts +0 -61
- package/src/sessions/sessions.test.ts +0 -455
- package/src/sessions/sessions.ts +0 -295
- package/src/skills/registry.test.ts +0 -104
- package/src/skills/registry.ts +0 -407
- package/src/terminal/cli.ts +0 -149
- package/src/terminal/client.ts +0 -74
- package/src/terminal/controller.ts +0 -227
- package/src/terminal/launch.test.ts +0 -34
- package/src/terminal/launch.ts +0 -88
- package/src/terminal/piExtension.ts +0 -210
- package/src/terminal/pluginControl.test.ts +0 -146
- package/src/terminal/pluginMenu.ts +0 -112
- package/src/terminal/socket.test.ts +0 -71
- package/src/terminal/socket.ts +0 -221
- package/src/terminal/standalone.test.ts +0 -130
- package/src/terminal/standalone.ts +0 -144
- package/src/terminal/terminal.test.ts +0 -330
- package/src/terminal/terminals.ts +0 -443
- package/src/tools/askUser.ts +0 -45
- package/src/tools/definitions.ts +0 -187
- package/src/tools/memoryTools.ts +0 -22
- package/src/tools/pluginPropose.ts +0 -41
- package/src/tools/types.ts +0 -39
- package/src/web/safeRequest.ts +0 -171
- package/src/web/text.ts +0 -53
- package/src/web/tools.ts +0 -134
- package/src/web/web.test.ts +0 -141
|
@@ -1,130 +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
|
-
|
|
15
|
-
const scratch: string[] = []
|
|
16
|
-
const harnesses: Harness[] = []
|
|
17
|
-
|
|
18
|
-
afterEach(async () => {
|
|
19
|
-
for (const harness of harnesses.splice(0)) await harness.close()
|
|
20
|
-
for (const path of scratch.splice(0)) rmSync(path, { recursive: true, force: true })
|
|
21
|
-
vi.restoreAllMocks()
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
function temporaryDirectory(prefix: string): string {
|
|
25
|
-
const path = mkdtempSync(join(tmpdir(), prefix))
|
|
26
|
-
scratch.push(path)
|
|
27
|
-
return path
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function fakeSpawn(onSpawn: (program: string, args: string[], options: { cwd?: string; env?: NodeJS.ProcessEnv; stdio?: string }) => void, outcome: { code?: number; error?: Error } = {}): typeof nodeSpawn {
|
|
31
|
-
return ((program: string, args: string[], options: { cwd?: string; env?: NodeJS.ProcessEnv; stdio?: string }) => {
|
|
32
|
-
onSpawn(program, args, options)
|
|
33
|
-
const child = new EventEmitter() as ChildProcess
|
|
34
|
-
child.kill = vi.fn(() => true)
|
|
35
|
-
queueMicrotask(() => {
|
|
36
|
-
if (outcome.error) child.emit('error', outcome.error)
|
|
37
|
-
else child.emit('exit', outcome.code ?? 0, null)
|
|
38
|
-
})
|
|
39
|
-
return child
|
|
40
|
-
}) as unknown as typeof nodeSpawn
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function harnessFactory(options: HarnessOptions): Harness {
|
|
44
|
-
const harness = createHarness({ ...options, secrets: memorySecretStore(), piProgram: () => '/opt/pi/bin/pi' })
|
|
45
|
-
harnesses.push(harness)
|
|
46
|
-
return harness
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
describe('standalone terminal startup', () => {
|
|
50
|
-
it('routes the no-command and terminal invocations to Pi, leaving host commands explicit', () => {
|
|
51
|
-
expect(standaloneArguments([])).toEqual([])
|
|
52
|
-
expect(standaloneArguments(['--data', '/tmp/data'])).toEqual(['--data', '/tmp/data'])
|
|
53
|
-
expect(standaloneArguments(['terminal', '--state', '/tmp/state'])).toEqual(['--state', '/tmp/state'])
|
|
54
|
-
expect(standaloneArguments(['serve', '--state', '/tmp/state'])).toBeNull()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('uses a dedicated default data and state root and accepts path overrides', () => {
|
|
58
|
-
expect(parseStandaloneOptions([], '/home/test', '/work/project')).toEqual({
|
|
59
|
-
dataRoot: '/home/test/.xingular-harness/data',
|
|
60
|
-
stateDir: '/home/test/.xingular-harness/state'
|
|
61
|
-
})
|
|
62
|
-
expect(parseStandaloneOptions(['--data', './knowledge', '--state', './device'], '/home/test', '/work/project')).toEqual({
|
|
63
|
-
dataRoot: '/work/project/knowledge',
|
|
64
|
-
stateDir: '/work/project/device'
|
|
65
|
-
})
|
|
66
|
-
expect(() => parseStandaloneOptions(['--data'])).toThrow('--data needs a directory path.')
|
|
67
|
-
expect(() => parseStandaloneOptions(['--model', 'unknown'])).toThrow('Unknown standalone option: --model')
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('refuses default folders under a home that is not a folder path, and accepts explicit absolute folders', () => {
|
|
71
|
-
expect(() => parseStandaloneOptions([], 'HOME', '/work/project')).toThrow('is not an absolute path')
|
|
72
|
-
expect(parseStandaloneOptions(['--data', '/data', '--state', '/state'], 'HOME', '/work/project')).toEqual({ dataRoot: '/data', stateDir: '/state' })
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('starts Pi in the current directory, creates the first isolated account, and cleans up on exit', async () => {
|
|
76
|
-
const home = temporaryDirectory('harness-standalone-')
|
|
77
|
-
const cwd = temporaryDirectory('harness-cwd-')
|
|
78
|
-
const paths = parseStandaloneOptions([], home)
|
|
79
|
-
let invocation: { program: string; args: string[]; options: { cwd?: string; env?: NodeJS.ProcessEnv; stdio?: string } } | undefined
|
|
80
|
-
const spawn = fakeSpawn((program, args, options) => {
|
|
81
|
-
invocation = { program, args, options }
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
expect(await runStandaloneTerminal([], { home, cwd, stdinIsTTY: true, stdoutIsTTY: true, createSessionId: () => 'standalone', createHarness: harnessFactory, spawn })).toBe(0)
|
|
85
|
-
|
|
86
|
-
const harness = harnesses.shift()!
|
|
87
|
-
expect(harness.accounts.overview().accounts).toMatchObject([{ label: 'Default', isDefault: true }])
|
|
88
|
-
expect(invocation?.program).toBe('/opt/pi/bin/pi')
|
|
89
|
-
expect(invocation?.options).toMatchObject({ cwd, stdio: 'inherit' })
|
|
90
|
-
expect(invocation?.args).toContain('--extension')
|
|
91
|
-
expect(invocation?.args).toContain(defaultAssets().piExtension)
|
|
92
|
-
expect(invocation?.args).toContain(join(paths.dataRoot, 'sessions', 'terminal', createHash('sha256').update(resolve(cwd)).digest('hex'), 'pi'))
|
|
93
|
-
expect(invocation?.options.env?.PI_CODING_AGENT_DIR).toContain(join(paths.stateDir, 'accounts'))
|
|
94
|
-
expect(invocation?.options.env?.XINGULAR_HARNESS_SOCKET).toBeTruthy()
|
|
95
|
-
expect(invocation?.options.env?.XINGULAR_HARNESS_TOKEN).toBeTruthy()
|
|
96
|
-
expect(existsSync(join(paths.stateDir, 'accounts', harness.accounts.overview().accounts[0].id, 'home', 'pi', 'auth.json'))).toBe(false)
|
|
97
|
-
expect(existsSync(socketPath(paths.stateDir))).toBe(false)
|
|
98
|
-
expect(harness.terminals.has('standalone')).toBe(false)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('requires an interactive TTY before creating Harness data', async () => {
|
|
102
|
-
const create = vi.fn(harnessFactory)
|
|
103
|
-
await expect(runStandaloneTerminal([], { stdinIsTTY: false, stdoutIsTTY: true, createHarness: create })).rejects.toThrow('needs a TTY')
|
|
104
|
-
expect(create).not.toHaveBeenCalled()
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('cleans up the prepared terminal when Pi cannot be spawned', async () => {
|
|
108
|
-
const home = temporaryDirectory('harness-standalone-fail-')
|
|
109
|
-
const cwd = temporaryDirectory('harness-cwd-fail-')
|
|
110
|
-
const spawn = fakeSpawn(() => undefined, { error: new Error('spawn failed') })
|
|
111
|
-
|
|
112
|
-
await expect(runStandaloneTerminal([], { home, cwd, stdinIsTTY: true, stdoutIsTTY: true, createSessionId: () => 'failed', createHarness: harnessFactory, spawn })).rejects.toThrow('spawn failed')
|
|
113
|
-
|
|
114
|
-
const harness = harnesses.shift()!
|
|
115
|
-
const account = harness.accounts.overview().accounts[0]
|
|
116
|
-
expect(existsSync(join(harness.accounts.dir, account.id, 'home', 'pi', 'auth.json'))).toBe(false)
|
|
117
|
-
expect(existsSync(socketPath(harness.stateDir))).toBe(false)
|
|
118
|
-
expect(harness.terminals.has('failed')).toBe(false)
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('explains a missing Pi executable in terms of the terminal launch', async () => {
|
|
122
|
-
const home = temporaryDirectory('harness-standalone-enoent-')
|
|
123
|
-
const cwd = temporaryDirectory('harness-cwd-enoent-')
|
|
124
|
-
const error = Object.assign(new Error('spawn /opt/pi/bin/pi ENOENT'), { code: 'ENOENT' })
|
|
125
|
-
const spawn = fakeSpawn(() => undefined, { error })
|
|
126
|
-
|
|
127
|
-
await expect(runStandaloneTerminal([], { home, cwd, stdinIsTTY: true, stdoutIsTTY: true, createSessionId: () => 'missing-pi', createHarness: harnessFactory, spawn }))
|
|
128
|
-
.rejects.toThrow(/Could not start Pi terminal.*Pi executable.*\/opt\/pi\/bin\/pi/)
|
|
129
|
-
})
|
|
130
|
-
})
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
// The standalone entry point for people using the harness directly. Pi owns the interface;
|
|
2
|
-
// the harness prepares the isolated account, context, extension, and session socket.
|
|
3
|
-
import { spawn as nodeSpawn } from 'node:child_process'
|
|
4
|
-
import { createHash, randomUUID } from 'node:crypto'
|
|
5
|
-
import { homedir } from 'node:os'
|
|
6
|
-
import { isAbsolute, join, resolve } from 'node:path'
|
|
7
|
-
|
|
8
|
-
import { createHarness, type Harness, type HarnessOptions } from '../harness'
|
|
9
|
-
import type { TerminalLaunch } from './terminals'
|
|
10
|
-
|
|
11
|
-
export interface StandalonePaths {
|
|
12
|
-
dataRoot: string
|
|
13
|
-
stateDir: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface StandaloneDependencies {
|
|
17
|
-
home?: string
|
|
18
|
-
cwd?: string
|
|
19
|
-
stdinIsTTY?: boolean
|
|
20
|
-
stdoutIsTTY?: boolean
|
|
21
|
-
createSessionId?: () => string
|
|
22
|
-
createHarness?: (options: HarnessOptions) => Harness
|
|
23
|
-
spawn?: typeof nodeSpawn
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function standaloneArguments(argv: string[]): string[] | null {
|
|
27
|
-
const command = argv[0]
|
|
28
|
-
if (!command || command.startsWith('--') || command === '-h') return argv
|
|
29
|
-
return command === 'terminal' ? argv.slice(1) : null
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function parseStandaloneOptions(argv: string[], home = homedir(), cwd = process.cwd()): StandalonePaths {
|
|
33
|
-
const root = join(home, '.xingular-harness')
|
|
34
|
-
const paths: StandalonePaths = {
|
|
35
|
-
dataRoot: join(root, 'data'),
|
|
36
|
-
stateDir: join(root, 'state')
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const given = new Set<string>()
|
|
40
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
41
|
-
const argument = argv[index]
|
|
42
|
-
if (argument !== '--data' && argument !== '--state') throw new Error(`Unknown standalone option: ${argument}`)
|
|
43
|
-
const value = argv[index + 1]
|
|
44
|
-
if (!value || value.startsWith('--')) throw new Error(`${argument} needs a directory path.`)
|
|
45
|
-
paths[argument === '--data' ? 'dataRoot' : 'stateDir'] = resolve(cwd, value)
|
|
46
|
-
given.add(argument)
|
|
47
|
-
index += 1
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// A home that is not a folder path (for example HOME set to the word "HOME") would put the
|
|
51
|
-
// defaults, and the session socket, in whatever folder the command was started from.
|
|
52
|
-
if (!isAbsolute(home) && !(given.has('--data') && given.has('--state'))) {
|
|
53
|
-
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.`)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return paths
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function processEnvironment(overrides: Record<string, string | null>): NodeJS.ProcessEnv {
|
|
60
|
-
const environment = { ...process.env }
|
|
61
|
-
for (const [name, value] of Object.entries(overrides)) {
|
|
62
|
-
if (value === null) delete environment[name]
|
|
63
|
-
else environment[name] = value
|
|
64
|
-
}
|
|
65
|
-
return environment
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function exitCode(code: number | null, signal: NodeJS.Signals | null): number {
|
|
69
|
-
if (code !== null) return code
|
|
70
|
-
if (signal === 'SIGINT') return 130
|
|
71
|
-
if (signal === 'SIGTERM') return 143
|
|
72
|
-
if (signal === 'SIGHUP') return 129
|
|
73
|
-
return 1
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function spawnError(error: Error, launch: TerminalLaunch): Error {
|
|
77
|
-
const code = (error as NodeJS.ErrnoException).code
|
|
78
|
-
if (code !== 'ENOENT' && code !== 'EACCES' && code !== 'EPERM') return error
|
|
79
|
-
if (launch.program === process.execPath || launch.env.XINGULAR_HARNESS_NODE === launch.program) {
|
|
80
|
-
return new Error(`Could not start the Pi terminal because Node could not run "${launch.args[0] ?? 'the bundled Pi CLI'}" (${code}). Verify Node and the bundled Pi package are installed.`, { cause: error })
|
|
81
|
-
}
|
|
82
|
-
const problem = code === 'ENOENT' ? 'was not found' : `could not start (${code})`
|
|
83
|
-
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 })
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function spawnPi(launch: TerminalLaunch, cwd: string, spawn = nodeSpawn): Promise<number> {
|
|
87
|
-
return new Promise((resolveExit, reject) => {
|
|
88
|
-
const child = spawn(launch.program, launch.args, {
|
|
89
|
-
cwd,
|
|
90
|
-
env: processEnvironment(launch.env),
|
|
91
|
-
stdio: 'inherit'
|
|
92
|
-
})
|
|
93
|
-
let settled = false
|
|
94
|
-
const cleanup = (): void => {
|
|
95
|
-
process.off('SIGINT', onInterrupt)
|
|
96
|
-
process.off('SIGTERM', onTerminate)
|
|
97
|
-
process.off('SIGHUP', onHangup)
|
|
98
|
-
}
|
|
99
|
-
const finish = (action: () => void): void => {
|
|
100
|
-
if (settled) return
|
|
101
|
-
settled = true
|
|
102
|
-
cleanup()
|
|
103
|
-
action()
|
|
104
|
-
}
|
|
105
|
-
// Ctrl+C is delivered to both processes in the foreground process group. Keep this host
|
|
106
|
-
// alive while Pi handles it so the account credentials and harness socket are cleaned up.
|
|
107
|
-
const onInterrupt = (): void => undefined
|
|
108
|
-
const onTerminate = (): void => {
|
|
109
|
-
child.kill('SIGTERM')
|
|
110
|
-
}
|
|
111
|
-
const onHangup = (): void => undefined
|
|
112
|
-
|
|
113
|
-
process.on('SIGINT', onInterrupt)
|
|
114
|
-
process.on('SIGTERM', onTerminate)
|
|
115
|
-
process.on('SIGHUP', onHangup)
|
|
116
|
-
child.once('error', (error) => finish(() => reject(spawnError(error, launch))))
|
|
117
|
-
child.once('exit', (code, signal) => finish(() => resolveExit(exitCode(code, signal))))
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export async function runStandaloneTerminal(argv: string[], dependencies: StandaloneDependencies = {}): Promise<number> {
|
|
122
|
-
if (!(dependencies.stdinIsTTY ?? process.stdin.isTTY) || !(dependencies.stdoutIsTTY ?? process.stdout.isTTY)) {
|
|
123
|
-
throw new Error('The interactive Harness terminal needs a TTY. Run it from a terminal, or use `xingular-harness serve` for a host process.')
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const cwd = resolve(dependencies.cwd ?? process.cwd())
|
|
127
|
-
const paths = parseStandaloneOptions(argv, dependencies.home, cwd)
|
|
128
|
-
const makeHarness = dependencies.createHarness ?? createHarness
|
|
129
|
-
const harness = makeHarness({ dataRoot: paths.dataRoot, stateDir: paths.stateDir, productName: 'Xingular Harness' })
|
|
130
|
-
const sessionId = (dependencies.createSessionId ?? randomUUID)()
|
|
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
|
-
const launch = await harness.terminals.prepare({ sessionId, cwd, sessionFolder })
|
|
139
|
-
return await spawnPi(launch, cwd, dependencies.spawn)
|
|
140
|
-
} finally {
|
|
141
|
-
await harness.terminals.dispose(sessionId)
|
|
142
|
-
await harness.close()
|
|
143
|
-
}
|
|
144
|
-
}
|
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
-
import { spawnSync } from 'node:child_process'
|
|
3
|
-
import { connect } from 'node:net'
|
|
4
|
-
import { tmpdir } from 'node:os'
|
|
5
|
-
import { join } from 'node:path'
|
|
6
|
-
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
7
|
-
|
|
8
|
-
import { memorySecretStore } from '../accounts/secrets'
|
|
9
|
-
import { createHarness, type Harness } from '../harness'
|
|
10
|
-
import type { RunEvent } from '../sessions/contracts'
|
|
11
|
-
import type { ToolPolicy } from '../sessions/policy'
|
|
12
|
-
import { loadRun } from '../sessions/runStore'
|
|
13
|
-
import type { Found } from '../plugins/discover'
|
|
14
|
-
import { rootWithProject, writeFile } from '../../tests/helpers'
|
|
15
|
-
import { parseCli } from './cli'
|
|
16
|
-
import { SOCKET_ENV, TOKEN_ENV, socketCall, type SocketConnection } from './client'
|
|
17
|
-
import { SessionSocket } from './socket'
|
|
18
|
-
|
|
19
|
-
const open: Harness[] = []
|
|
20
|
-
afterEach(async () => {
|
|
21
|
-
vi.useRealTimers()
|
|
22
|
-
for (const harness of open.splice(0)) await harness.close()
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
async function setup(policy: ToolPolicy = {}) {
|
|
26
|
-
const root = rootWithProject()
|
|
27
|
-
const harness = createHarness({ dataRoot: root, stateDir: mkdtempSync(join(tmpdir(), 'hs-')), secrets: memorySecretStore(), policy, piProgram: () => '/opt/pi/bin/pi', assets: { node: '/opt/node', cli: '/opt/harness/cli.js', piExtension: '/opt/harness/terminal-pi.js' } })
|
|
28
|
-
const legacyBin = join(harness.stateDir, 'run', 'bin')
|
|
29
|
-
mkdirSync(legacyBin, { recursive: true })
|
|
30
|
-
writeFileSync(join(legacyBin, 'agentx-wiki'), 'old wrapper')
|
|
31
|
-
writeFileSync(join(legacyBin, 'agentx-mcp'), 'old wrapper')
|
|
32
|
-
open.push(harness)
|
|
33
|
-
const account = harness.accounts.create('Main', 'apiKey').accounts[0]
|
|
34
|
-
const events: RunEvent[] = []
|
|
35
|
-
const notices: Record<string, unknown>[] = []
|
|
36
|
-
harness.runs.listen((event) => void events.push(event))
|
|
37
|
-
harness.bus.on((notice) => void notices.push(notice))
|
|
38
|
-
const sessionFolder = join(root, 'sessions', 'p1', 'term-1')
|
|
39
|
-
const launch = await harness.terminals.prepare({ sessionId: 'term-1', accountId: account.id, projectId: 'p1', cwd: root, sessionFolder, model: 'chosen-model', launch: { connections: 'Use mcp_list_tools.' } })
|
|
40
|
-
const connection: SocketConnection = { socket: String(launch.env[SOCKET_ENV]), token: String(launch.env[TOKEN_ENV]) }
|
|
41
|
-
/** One telemetry line, as the native extension sends it. */
|
|
42
|
-
const tell = (event: string, data: Record<string, unknown> = {}) =>
|
|
43
|
-
new Promise<any>((resolve, reject) => {
|
|
44
|
-
const stream = connect(connection.socket)
|
|
45
|
-
let buffer = ''
|
|
46
|
-
stream.on('connect', () => stream.write(`${JSON.stringify({ token: connection.token, event, data })}\n`))
|
|
47
|
-
stream.on('data', (chunk) => {
|
|
48
|
-
buffer += chunk.toString()
|
|
49
|
-
if (!buffer.includes('\n')) return
|
|
50
|
-
const reply = JSON.parse(buffer.slice(0, buffer.indexOf('\n')))
|
|
51
|
-
stream.end()
|
|
52
|
-
if (reply.error) reject(new Error(reply.error.message))
|
|
53
|
-
else resolve(reply.result)
|
|
54
|
-
})
|
|
55
|
-
stream.on('error', reject)
|
|
56
|
-
})
|
|
57
|
-
return { harness, root, account, events, notices, launch, connection, tell, sessionFolder }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
describe('a pi terminal session', () => {
|
|
61
|
-
it('launches on the pinned account with compiled instructions, the extension, and the session socket', async () => {
|
|
62
|
-
const { launch, account, harness, sessionFolder, root } = await setup()
|
|
63
|
-
expect(launch.program).toBe('/opt/pi/bin/pi')
|
|
64
|
-
expect(launch.accountId).toBe(account.id)
|
|
65
|
-
expect(launch.args[0]).toBe('--append-system-prompt')
|
|
66
|
-
expect(launch.args[1]).toContain('# Xingular Harness context')
|
|
67
|
-
expect(launch.args[1]).toContain('xagent-wiki')
|
|
68
|
-
// Pi's own discovery is off; its command's built-in extensions are named, and the harness's comes last.
|
|
69
|
-
expect(launch.args.slice(2)).toEqual([
|
|
70
|
-
'--model', 'chosen-model', '--no-extensions', '--no-skills', '--no-prompt-templates', '--no-themes',
|
|
71
|
-
'--extension', 'builtin:llama.cpp', '--extension', 'builtin:codemode', '--extension', 'builtin:tool-search', '--extension', 'builtin:mcp',
|
|
72
|
-
'--extension', '/opt/harness/terminal-pi.js', '--session-dir', join(sessionFolder, 'pi')
|
|
73
|
-
])
|
|
74
|
-
expect(launch.env.PI_CODING_AGENT_DIR).toBe(join(harness.accounts.dir, account.id, 'home', 'pi'))
|
|
75
|
-
expect(String(launch.env.PATH).split(':')[0]).toBe(join(harness.stateDir, 'run', 'bin'))
|
|
76
|
-
expect(readFileSync(join(harness.stateDir, 'run', 'bin', 'xagent-wiki'), 'utf8')).toBe("#!/bin/sh\nexec '/opt/node' '/opt/harness/cli.js' wiki \"$@\"\n")
|
|
77
|
-
expect(readFileSync(join(harness.stateDir, 'run', 'bin', 'xagent-mcp'), 'utf8')).toContain("'/opt/harness/cli.js' mcp")
|
|
78
|
-
expect(existsSync(join(harness.stateDir, 'run', 'bin', 'agentx-wiki'))).toBe(false)
|
|
79
|
-
expect(existsSync(join(harness.stateDir, 'run', 'bin', 'agentx-mcp'))).toBe(false)
|
|
80
|
-
// The session keeps the pack it started with.
|
|
81
|
-
expect(JSON.parse(readFileSync(join(sessionFolder, 'context.json'), 'utf8')).profile).toBe('session')
|
|
82
|
-
expect(root).toBeTruthy()
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it.skipIf(process.platform !== 'win32')('starts the resolved Pi CLI through Node without a model or interactive terminal', async () => {
|
|
86
|
-
const root = rootWithProject()
|
|
87
|
-
const stateDir = mkdtempSync(join(tmpdir(), 'hs-pi-smoke-'))
|
|
88
|
-
const harness = createHarness({ dataRoot: root, stateDir, secrets: memorySecretStore() })
|
|
89
|
-
open.push(harness)
|
|
90
|
-
harness.accounts.create('Main', 'subscription')
|
|
91
|
-
const launch = await harness.terminals.prepare({ sessionId: 'pi-smoke', cwd: root, sessionFolder: join(root, 'sessions', 'pi-smoke') })
|
|
92
|
-
const cli = launch.args[0]
|
|
93
|
-
const pathKeys = Object.keys(launch.env).filter((name) => name.toLowerCase() === 'path')
|
|
94
|
-
|
|
95
|
-
expect(launch.program).toBe(process.execPath)
|
|
96
|
-
expect(pathKeys).toHaveLength(1)
|
|
97
|
-
expect(launch.env[pathKeys[0]]).toContain(';')
|
|
98
|
-
expect(launch.env.XINGULAR_HARNESS_NODE).toBe(process.execPath)
|
|
99
|
-
expect(readFileSync(join(stateDir, 'run', 'bin', 'xagent-wiki.cmd'), 'utf8')).toContain(' wiki %*\r\n')
|
|
100
|
-
const result = spawnSync(launch.program, [cli, '--help'], { cwd: root, encoding: 'utf8', timeout: 20_000 })
|
|
101
|
-
expect(result.error).toBeUndefined()
|
|
102
|
-
expect(result.status).toBe(0)
|
|
103
|
-
expect(`${result.stdout}${result.stderr}`).toMatch(/usage|options|pi coding agent/i)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('passes what the harness found as paths, the enabled registry items with them, and its own extension last', async () => {
|
|
107
|
-
const root = rootWithProject()
|
|
108
|
-
writeFile(join(root, 'registry/skills/release/SKILL.md'), '# Release\n')
|
|
109
|
-
writeFile(join(root, 'registry/prompts/review.md'), 'Review this.\n')
|
|
110
|
-
writeFile(join(root, 'config/registry-enabled.json'), JSON.stringify({ p1: ['skills/release', 'prompts/review'] }))
|
|
111
|
-
writeFile(join(root, '.pi/extensions/local.ts'), 'export default function () {}\n')
|
|
112
|
-
const item = (kind: Found['kind'], path: string, origin: Found['origin'], enabled = true): Found => ({ kind, path, origin, project: origin === 'project', packageRoot: null, source: null, enabled })
|
|
113
|
-
const found = [
|
|
114
|
-
item('extension', join(root, '.pi/extensions/local.ts'), 'project'),
|
|
115
|
-
item('extension', '/shared/extensions/mine.ts', 'shared'),
|
|
116
|
-
item('extension', '/shared/extensions/off.ts', 'shared', false),
|
|
117
|
-
item('extension', 'builtin:mcp', 'builtin'),
|
|
118
|
-
item('skill', '/shared/skills/alpha/SKILL.md', 'shared'),
|
|
119
|
-
item('prompt', '/shared/prompts/p.md', 'shared'),
|
|
120
|
-
item('theme', '/shared/themes/dark.json', 'shared')
|
|
121
|
-
]
|
|
122
|
-
const harness = createHarness({ dataRoot: root, stateDir: mkdtempSync(join(tmpdir(), 'hs-')), secrets: memorySecretStore(), piProgram: () => '/opt/pi/bin/pi', assets: { node: '/opt/node', cli: '/opt/harness/cli.js', piExtension: '/opt/harness/terminal-pi.js' }, resolver: async () => found })
|
|
123
|
-
open.push(harness)
|
|
124
|
-
const account = harness.accounts.create('Main', 'apiKey').accounts[0]
|
|
125
|
-
const prepare = async () => (await harness.terminals.prepare({ sessionId: 'term-1', accountId: account.id, projectId: 'p1', cwd: root, sessionFolder: join(root, 'sessions', 'p1', 'term-1') })).args.slice(2)
|
|
126
|
-
|
|
127
|
-
const loaded = [
|
|
128
|
-
'--extension', '/shared/extensions/mine.ts', '--extension', 'builtin:mcp',
|
|
129
|
-
'--skill', join(root, 'registry/skills/release/SKILL.md'), '--skill', '/shared/skills/alpha/SKILL.md',
|
|
130
|
-
'--prompt-template', join(root, 'registry/prompts/review.md'), '--prompt-template', '/shared/prompts/p.md',
|
|
131
|
-
'--theme', '/shared/themes/dark.json',
|
|
132
|
-
'--extension', '/opt/harness/terminal-pi.js', '--session-dir', join(root, 'sessions', 'p1', 'term-1', 'pi')
|
|
133
|
-
]
|
|
134
|
-
const flags = ['--no-extensions', '--no-skills', '--no-prompt-templates', '--no-themes']
|
|
135
|
-
expect(await prepare()).toEqual([...flags, ...loaded])
|
|
136
|
-
|
|
137
|
-
// The working folder's own code joins only once a person trusted it.
|
|
138
|
-
const code = (await harness.plugins.loading({ cwd: root, agentDir: join(harness.accounts.dir, account.id, 'home', 'pi') })).projectCode!
|
|
139
|
-
harness.plugins.trust.grant(code, root)
|
|
140
|
-
expect(await prepare()).toEqual([...flags, '--extension', join(root, '.pi/extensions/local.ts'), ...loaded])
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
it('refuses to launch without the pi command, and never on another account', async () => {
|
|
144
|
-
const harness = createHarness({ dataRoot: rootWithProject(), stateDir: mkdtempSync(join(tmpdir(), 'hs-')), secrets: memorySecretStore(), piProgram: () => null })
|
|
145
|
-
open.push(harness)
|
|
146
|
-
harness.accounts.create('Main', 'apiKey')
|
|
147
|
-
await expect(harness.terminals.prepare({ sessionId: 's', cwd: '/tmp', sessionFolder: join(harness.env.root, 'sessions', 's') })).rejects.toMatchObject({ code: 'harness_unavailable' })
|
|
148
|
-
const other = createHarness({ dataRoot: rootWithProject(), stateDir: mkdtempSync(join(tmpdir(), 'hs-')), secrets: memorySecretStore(), piProgram: () => '/opt/pi' })
|
|
149
|
-
open.push(other)
|
|
150
|
-
other.accounts.create('Main', 'apiKey')
|
|
151
|
-
await expect(other.terminals.prepare({ sessionId: 's', accountId: 'missing-account', cwd: '/tmp', sessionFolder: join(other.env.root, 'sessions', 's') })).rejects.toThrow()
|
|
152
|
-
expect(other.terminals.has('s')).toBe(false)
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
it('records what the person typed as a run and follows it to the end', async () => {
|
|
156
|
-
const { tell, events, harness, root, notices } = await setup()
|
|
157
|
-
await tell('startup', { phase: 'Pi extension loaded' })
|
|
158
|
-
await tell('ready', { nativeSessionId: '/history/native.jsonl' })
|
|
159
|
-
expect(notices).toContainEqual({ type: 'terminal.native', sessionId: 'term-1', nativeSessionId: '/history/native.jsonl' })
|
|
160
|
-
await tell('begin', { text: 'Fix the build' })
|
|
161
|
-
const runId = events[0].runId
|
|
162
|
-
expect(loadRun(root, runId)).toMatchObject({ mode: 'code', harness: 'pi', sessionType: 'terminal', sessionId: 'term-1', projectId: 'p1', goal: 'Fix the build', nativeSessionId: '/history/native.jsonl' })
|
|
163
|
-
await tell('tool', { phase: 'start', id: 't1', name: 'read', summary: '{"path":"a"}' })
|
|
164
|
-
await tell('tool', { phase: 'end', id: 't1', name: 'read', ok: true, durationMs: 4, resultChars: 12 })
|
|
165
|
-
await tell('message', { text: 'Done.' })
|
|
166
|
-
await tell('usage', { inputTokens: 5, outputTokens: 2, cacheReadTokens: 0, cacheWriteTokens: 0, requests: 1 })
|
|
167
|
-
await tell('settled', {})
|
|
168
|
-
expect(events.map((event) => event.type)).toEqual(['started', 'status.changed', 'message.completed', 'tool.started', 'tool.completed', 'message.completed', 'usage.updated', 'completed'])
|
|
169
|
-
expect(loadRun(root, runId).status).toBe('completed')
|
|
170
|
-
expect(harness.terminals.state('term-1')).toEqual({ ready: true, dirty: false, active: false })
|
|
171
|
-
// The run has the pack its session started with.
|
|
172
|
-
expect(JSON.parse(readFileSync(join(root, 'runs', runId, 'context.json'), 'utf8')).runId).toBe(runId)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
it('delivers an instruction through the extension and reserves its run first', async () => {
|
|
176
|
-
const { tell, harness, root, events } = await setup()
|
|
177
|
-
await tell('ready', {})
|
|
178
|
-
const sent = harness.terminals.send({ sessionId: 'term-1', runId: 'reserved', text: 'Run the tests' })
|
|
179
|
-
await vi.waitFor(async () => expect(await tell('poll')).toEqual({ command: { type: 'send', text: 'Run the tests' } }))
|
|
180
|
-
expect(loadRun(root, 'reserved')).toMatchObject({ status: 'queued', goal: 'Run the tests' })
|
|
181
|
-
await tell('begin', { text: 'Run the tests' })
|
|
182
|
-
expect(await sent).toEqual({ runId: 'reserved' })
|
|
183
|
-
expect(events[0]).toMatchObject({ runId: 'reserved', type: 'started' })
|
|
184
|
-
await expect(harness.terminals.send({ sessionId: 'term-1', text: 'Another' })).rejects.toMatchObject({ code: 'session_busy' })
|
|
185
|
-
await harness.runs.interrupt('reserved')
|
|
186
|
-
expect(await tell('poll')).toEqual({ command: { type: 'interrupt' } })
|
|
187
|
-
expect(loadRun(root, 'reserved').status).toBe('interrupted')
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
it('sends a memory block that changed since the session started along with the next instruction', async () => {
|
|
191
|
-
const { tell, harness, connection } = await setup()
|
|
192
|
-
await tell('ready', {})
|
|
193
|
-
await socketCall(connection, 'memory.append', { block: 'project', text: 'Prefers pnpm.' })
|
|
194
|
-
const first = harness.terminals.send({ sessionId: 'term-1', text: 'Continue' })
|
|
195
|
-
let delivered = ''
|
|
196
|
-
await vi.waitFor(async () => {
|
|
197
|
-
const { command } = await tell('poll')
|
|
198
|
-
expect(command?.type).toBe('send')
|
|
199
|
-
delivered = command.text
|
|
200
|
-
})
|
|
201
|
-
await tell('begin', { text: delivered })
|
|
202
|
-
await first
|
|
203
|
-
expect(delivered.startsWith('[Memory updated since this session')).toBe(true)
|
|
204
|
-
expect(delivered).toContain('Prefers pnpm.')
|
|
205
|
-
expect(delivered.endsWith('\n\nContinue')).toBe(true)
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
it('will not send over a draft the person is typing, and fails a delivery the native session never acknowledges', async () => {
|
|
209
|
-
const { tell, harness, root } = await setup()
|
|
210
|
-
await tell('ready', {})
|
|
211
|
-
harness.terminals.input('term-1', 'half a thought')
|
|
212
|
-
await expect(harness.terminals.send({ sessionId: 'term-1', runId: 'draft', text: 'Task' })).rejects.toMatchObject({ code: 'terminal_has_draft' })
|
|
213
|
-
expect(loadRun(root, 'draft').status).toBe('failed')
|
|
214
|
-
harness.terminals.input('term-1', '\u0015')
|
|
215
|
-
vi.useFakeTimers()
|
|
216
|
-
const send = harness.terminals.send({ sessionId: 'term-1', runId: 'lost', text: 'Task' })
|
|
217
|
-
const rejected = expect(send).rejects.toMatchObject({ code: 'delivery_uncertain' })
|
|
218
|
-
await vi.advanceTimersByTimeAsync(25_001)
|
|
219
|
-
await rejected
|
|
220
|
-
vi.useRealTimers()
|
|
221
|
-
expect(loadRun(root, 'lost').status).toBe('failed')
|
|
222
|
-
// A late acknowledgement does not start a run for an instruction reported as lost.
|
|
223
|
-
await expect(tell('begin', { text: 'Task' })).rejects.toThrow('will not be resent')
|
|
224
|
-
expect(await tell('poll')).toEqual({ command: { type: 'interrupt' } })
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
it('asks before a native action unless the policy allows it, and reads never ask', async () => {
|
|
228
|
-
const { tell, events, harness } = await setup()
|
|
229
|
-
await tell('ready', {})
|
|
230
|
-
await tell('begin', { text: 'Edit' })
|
|
231
|
-
expect(await tell('policy', { tool: 'read', args: { path: 'a' } })).toEqual({ allowed: true })
|
|
232
|
-
expect(await tell('policy', { tool: 'bash', args: { command: 'rm -rf x' } })).toMatchObject({ allowed: false, requiresApproval: true })
|
|
233
|
-
const decision = tell('approval', { requestId: 'call-1', target: 'bash', action: '{"command":"rm -rf x"}', tool: 'bash', args: { command: 'rm -rf x' } })
|
|
234
|
-
await vi.waitFor(() => expect(events.some((event) => event.type === 'approval.requested')).toBe(true))
|
|
235
|
-
const request = events.find((event) => event.type === 'approval.requested')!
|
|
236
|
-
harness.runs.resolveApproval(request.runId, (request.payload as { approvalId: string }).approvalId, 'approved')
|
|
237
|
-
expect(await decision).toEqual({ decision: 'approved' })
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
it('applies the application policy to native tool calls', async () => {
|
|
241
|
-
const seen: string[] = []
|
|
242
|
-
const { tell } = await setup({
|
|
243
|
-
scope: async (call) => {
|
|
244
|
-
seen.push(`${call.run.sessionId}:${call.tool}`)
|
|
245
|
-
return call.tool === 'bash' ? { allowed: false, reason: 'Outside the task.' } : { allowed: true }
|
|
246
|
-
},
|
|
247
|
-
authorize: async (call) => {
|
|
248
|
-
if (call.tool === 'write') throw new Error('Switched off.')
|
|
249
|
-
}
|
|
250
|
-
})
|
|
251
|
-
await tell('ready', {})
|
|
252
|
-
await tell('begin', { text: 'Go' })
|
|
253
|
-
expect(await tell('policy', { tool: 'bash', args: {} })).toEqual({ allowed: false, reason: 'Outside the task.' })
|
|
254
|
-
expect(await tell('policy', { tool: 'write', args: {} })).toEqual({ allowed: false, reason: 'Switched off.' })
|
|
255
|
-
expect(await tell('policy', { tool: 'edit', args: {} })).toMatchObject({ allowed: true })
|
|
256
|
-
expect(seen).toEqual(['term-1:bash', 'term-1:write', 'term-1:edit'])
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
it('fails the turn and releases its decisions when the native process hangs up mid-question', async () => {
|
|
260
|
-
const { tell, events, harness, connection } = await setup()
|
|
261
|
-
await tell('ready', {})
|
|
262
|
-
await tell('begin', { text: 'task' })
|
|
263
|
-
const socket = connect(connection.socket)
|
|
264
|
-
await new Promise<void>((resolve, reject) => {
|
|
265
|
-
socket.once('connect', resolve)
|
|
266
|
-
socket.once('error', reject)
|
|
267
|
-
})
|
|
268
|
-
socket.write(`${JSON.stringify({ token: connection.token, event: 'question', data: { requestId: 'native-one', question: 'Continue?' } })}\n`)
|
|
269
|
-
await vi.waitFor(() => expect(events.some((event) => event.type === 'question.requested')).toBe(true))
|
|
270
|
-
socket.destroy()
|
|
271
|
-
await vi.waitFor(() => expect(events.some((event) => event.type === 'failed')).toBe(true))
|
|
272
|
-
expect((events.find((event) => event.type === 'failed')!.payload as { message: string }).message).toContain('callback disconnected')
|
|
273
|
-
expect(harness.runs.activeRunIds()).toEqual([])
|
|
274
|
-
expect(harness.terminals.state('term-1').ready).toBe(false)
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
it('serves memory and connected apps to the session over the socket, until the terminal ends', async () => {
|
|
278
|
-
const { connection, harness, tell } = await setup()
|
|
279
|
-
await tell('ready', {})
|
|
280
|
-
expect(await socketCall<string>(connection, 'wiki.entrypoint')).toContain('# Xingular Harness context')
|
|
281
|
-
expect(await socketCall<string>(connection, 'wiki.list')).toContain('library')
|
|
282
|
-
const listed = await socketCall<{ tools: { name: string; parameters: { type: string } }[] }>(connection, 'tools.list')
|
|
283
|
-
expect(listed.tools.map((tool) => tool.name)).toEqual(['mcp_list_tools', 'mcp_call_tool'])
|
|
284
|
-
expect(listed.tools[0].parameters.type).toBe('object')
|
|
285
|
-
await expect(socketCall(connection, 'accounts.list')).rejects.toMatchObject({ code: 'unknown_method' })
|
|
286
|
-
await expect(socketCall({ ...connection, token: 'guess' }, 'wiki.list')).rejects.toMatchObject({ code: 'unauthorized' })
|
|
287
|
-
await harness.terminals.dispose('term-1')
|
|
288
|
-
await expect(socketCall(connection, 'wiki.list')).rejects.toMatchObject({ code: 'unauthorized' })
|
|
289
|
-
expect(harness.terminals.has('term-1')).toBe(false)
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
it('writes the account credentials for the pi command only while a terminal runs', async () => {
|
|
293
|
-
const { harness, account } = await setup()
|
|
294
|
-
const auth = join(harness.accounts.dir, account.id, 'home', 'pi', 'auth.json')
|
|
295
|
-
expect(JSON.parse(readFileSync(auth, 'utf8'))).toEqual({})
|
|
296
|
-
await harness.terminals.dispose('term-1')
|
|
297
|
-
expect(() => readFileSync(auth, 'utf8')).toThrow()
|
|
298
|
-
})
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
describe('the session socket', () => {
|
|
302
|
-
it('answers only calls it knows, for tokens it issued', async () => {
|
|
303
|
-
const socket = new SessionSocket(join(mkdtempSync(join(tmpdir(), 'sock-')), 's.sock'), { memory: (caller, call) => `${caller.runId}:${call}`, apps: async () => 'apps' })
|
|
304
|
-
const token = socket.issue({ sessionId: 's1', caller: () => ({ runId: 'r1' }), broker: null })
|
|
305
|
-
expect(await socket.handle(JSON.stringify({ token, call: 'wiki.search', params: { query: 'x' } }))).toEqual({ result: 'r1:wiki.search' })
|
|
306
|
-
expect(await socket.handle(JSON.stringify({ token, call: 'agents.create' }))).toMatchObject({ error: { code: 'unknown_method' } })
|
|
307
|
-
expect(await socket.handle(JSON.stringify({ token, call: 'mcp.list' }))).toMatchObject({ error: { code: 'mcp_unauthorized' } })
|
|
308
|
-
expect(await socket.handle(JSON.stringify({ token: 'guess', call: 'wiki.search' }))).toMatchObject({ error: { code: 'unauthorized' } })
|
|
309
|
-
expect(await socket.handle('not json')).toMatchObject({ error: { code: 'invalid_request' } })
|
|
310
|
-
socket.revokeSession('s1')
|
|
311
|
-
expect(await socket.handle(JSON.stringify({ token, call: 'wiki.search' }))).toMatchObject({ error: { code: 'unauthorized' } })
|
|
312
|
-
})
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
describe('xagent-wiki command', () => {
|
|
316
|
-
it('maps commands onto memory and Wiki calls', () => {
|
|
317
|
-
expect(parseCli(['search', 'deploy', 'steps', '--kind', 'page'])).toEqual({ kind: 'call', call: 'wiki.search', params: { query: 'deploy steps', kinds: ['page'] } })
|
|
318
|
-
expect(parseCli(['read', '/projects/app/arch'])).toEqual({ kind: 'call', call: 'wiki.read', params: { id: '/projects/app/arch' } })
|
|
319
|
-
expect(parseCli(['list'])).toEqual({ kind: 'call', call: 'wiki.list', params: {} })
|
|
320
|
-
expect(parseCli(['skill', 'review'])).toEqual({ kind: 'call', call: 'wiki.skill', params: { name: 'review' } })
|
|
321
|
-
expect(parseCli(['memory', '--namespace', '/projects/app'])).toEqual({ kind: 'call', call: 'wiki.memory', params: { namespace: '/projects/app' } })
|
|
322
|
-
expect(parseCli(['recall', 'the', 'outage', '--scope', 'all'])).toEqual({ kind: 'call', call: 'recall.search', params: { query: 'the outage', scope: 'all' } })
|
|
323
|
-
expect(parseCli(['propose', '{"kind":"memory","title":"t","body":"b"}'])).toMatchObject({ kind: 'call', call: 'wiki.propose', params: { kind: 'memory' } })
|
|
324
|
-
expect(parseCli(['propose', '-'])).toEqual({ kind: 'stdin-propose' })
|
|
325
|
-
expect(parseCli(['block', 'append', 'project', 'Uses', 'pnpm.'])).toEqual({ kind: 'call', call: 'memory.append', params: { block: 'project', text: 'Uses pnpm.' } })
|
|
326
|
-
expect(parseCli(['block', 'replace', 'project', '--old', 'npm', '--new', 'pnpm'])).toEqual({ kind: 'call', call: 'memory.replace', params: { block: 'project', old: 'npm', new: 'pnpm' } })
|
|
327
|
-
expect(parseCli(['search'])).toEqual({ kind: 'help' })
|
|
328
|
-
expect(parseCli(['nonsense'])).toEqual({ kind: 'help' })
|
|
329
|
-
})
|
|
330
|
-
})
|