@xingular/harness 1.0.0 → 1.2.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 +46 -5
- package/README.md +75 -21
- package/dist/cli.js +17421 -8711
- package/dist/sdk/accounts/accounts.d.ts +27 -3
- package/dist/sdk/accounts/accounts.js +78 -24
- package/dist/sdk/accounts/secrets.d.ts +12 -0
- package/dist/sdk/accounts/secrets.js +114 -7
- package/dist/sdk/accounts/store.d.ts +26 -1
- package/dist/sdk/accounts/store.js +1 -1
- package/dist/sdk/agents/builtin.d.ts +2 -0
- package/dist/sdk/agents/builtin.js +48 -0
- package/dist/sdk/agents/narrow.d.ts +13 -0
- package/dist/sdk/agents/narrow.js +29 -0
- package/dist/sdk/agents/planResearch.d.ts +5 -0
- package/dist/sdk/agents/planResearch.js +19 -0
- package/dist/sdk/agents/profiles.d.ts +81 -0
- package/dist/sdk/agents/profiles.js +226 -0
- package/dist/sdk/annotations/bridge.d.ts +42 -0
- package/dist/sdk/annotations/bridge.js +264 -0
- package/dist/sdk/annotations/capture.d.ts +116 -0
- package/dist/sdk/annotations/capture.js +308 -0
- package/dist/sdk/annotations/delivery.d.ts +72 -0
- package/dist/sdk/annotations/delivery.js +130 -0
- package/dist/sdk/annotations/inspect.d.ts +10 -0
- package/dist/sdk/annotations/inspect.js +33 -0
- package/dist/sdk/annotations/isolated.d.ts +6 -0
- package/dist/sdk/annotations/isolated.js +51 -0
- package/dist/sdk/annotations/pageScript.d.ts +28 -0
- package/dist/sdk/annotations/pageScript.js +871 -0
- package/dist/sdk/annotations/part.d.ts +10 -0
- package/dist/sdk/annotations/part.js +155 -0
- package/dist/sdk/annotations/queue.d.ts +4 -0
- package/dist/sdk/annotations/queue.js +40 -0
- package/dist/sdk/annotations/serverMethods.d.ts +3 -0
- package/dist/sdk/annotations/serverMethods.js +62 -0
- package/dist/sdk/annotations/store.d.ts +167 -0
- package/dist/sdk/annotations/store.js +394 -0
- package/dist/sdk/browser/backend.d.ts +16 -0
- package/dist/sdk/browser/backend.js +17 -0
- package/dist/sdk/browser/contracts.d.ts +19 -0
- package/dist/sdk/browser/contracts.js +116 -0
- package/dist/sdk/browser/index.d.ts +14 -3
- package/dist/sdk/browser/index.js +11 -4
- package/dist/sdk/browser/serverMethods.d.ts +3 -0
- package/dist/sdk/browser/serverMethods.js +7 -0
- package/dist/sdk/browser/session.d.ts +1 -0
- package/dist/sdk/browser/session.js +45 -9
- package/dist/sdk/browser/tools.d.ts +1 -4
- package/dist/sdk/browser/tools.js +14 -91
- package/dist/sdk/cli.js +24 -15
- package/dist/sdk/commands/list.d.ts +3 -0
- package/dist/sdk/commands/list.js +75 -0
- package/dist/sdk/commands/registry.d.ts +250 -0
- package/dist/sdk/commands/registry.js +47 -0
- package/dist/sdk/commands/routes.d.ts +16 -0
- package/dist/sdk/commands/routes.js +33 -0
- package/dist/sdk/core/bus.js +1 -1
- package/dist/sdk/core/commandBudget.d.ts +23 -0
- package/dist/sdk/core/commandBudget.js +57 -0
- package/dist/sdk/core/env.d.ts +1 -1
- package/dist/sdk/core/env.js +1 -1
- package/dist/sdk/core/index.js +17 -1
- package/dist/sdk/core/interactiveProfile.d.ts +46 -0
- package/dist/sdk/core/interactiveProfile.js +143 -0
- package/dist/sdk/core/workspace.d.ts +13 -0
- package/dist/sdk/core/workspace.js +88 -0
- package/dist/sdk/core/workspaceBashTool.d.ts +4 -0
- package/dist/sdk/core/workspaceBashTool.js +36 -0
- package/dist/sdk/core/workspaceSandbox.d.ts +31 -0
- package/dist/sdk/core/workspaceSandbox.js +320 -0
- package/dist/sdk/core/workspaceWriteOperations.d.ts +22 -0
- package/dist/sdk/core/workspaceWriteOperations.js +206 -0
- package/dist/sdk/doctor/serverMethods.d.ts +3 -0
- package/dist/sdk/doctor/serverMethods.js +8 -0
- package/dist/sdk/doctor/types.d.ts +2 -2
- package/dist/sdk/harness.d.ts +22 -0
- package/dist/sdk/harness.js +84 -5
- package/dist/sdk/history/recall.js +8 -8
- package/dist/sdk/history/serverMethods.d.ts +3 -0
- package/dist/sdk/history/serverMethods.js +8 -0
- package/dist/sdk/hub/cli.d.ts +1 -1
- package/dist/sdk/hub/cli.js +1 -1
- package/dist/sdk/hub/schema.d.ts +1 -1
- package/dist/sdk/hub/serverMethods.d.ts +3 -0
- package/dist/sdk/hub/serverMethods.js +11 -0
- package/dist/sdk/index.d.ts +16 -1
- package/dist/sdk/index.js +10 -0
- package/dist/sdk/mcp/connections.js +14 -7
- package/dist/sdk/mcp/serverMethods.d.ts +3 -0
- package/dist/sdk/mcp/serverMethods.js +22 -0
- package/dist/sdk/pi/runtime.d.ts +2 -1
- package/dist/sdk/pi/runtime.js +2 -2
- package/dist/sdk/plugins/builtin.d.ts +1 -0
- package/dist/sdk/plugins/builtin.js +145 -33
- package/dist/sdk/plugins/builtinRegistry.d.ts +60 -0
- package/dist/sdk/plugins/builtinRegistry.js +71 -0
- package/dist/sdk/plugins/catalogue.js +2 -1
- package/dist/sdk/plugins/cli.d.ts +1 -1
- package/dist/sdk/plugins/cli.js +4 -3
- package/dist/sdk/plugins/commandPrefix.d.ts +8 -0
- package/dist/sdk/plugins/commandPrefix.js +21 -0
- package/dist/sdk/plugins/firstParty.d.ts +1 -1
- package/dist/sdk/plugins/firstParty.js +8 -2
- package/dist/sdk/plugins/inventory.js +4 -3
- package/dist/sdk/plugins/manifest.d.ts +2 -0
- package/dist/sdk/plugins/manifest.js +2 -1
- package/dist/sdk/plugins/surfaces.d.ts +5 -0
- package/dist/sdk/plugins/surfaces.js +11 -0
- package/dist/sdk/plugins/types.d.ts +15 -0
- package/dist/sdk/prompt/compile.d.ts +19 -2
- package/dist/sdk/prompt/compile.js +79 -26
- package/dist/sdk/prompt/standard.d.ts +17 -0
- package/dist/sdk/prompt/standard.js +21 -0
- package/dist/sdk/refine/cli.d.ts +1 -1
- package/dist/sdk/refine/cli.js +2 -2
- package/dist/sdk/refine/serverMethods.d.ts +3 -0
- package/dist/sdk/refine/serverMethods.js +20 -0
- package/dist/sdk/server/methods.js +27 -86
- package/dist/sdk/server/params.d.ts +4 -0
- package/dist/sdk/server/params.js +11 -0
- package/dist/sdk/server/remote.js +36 -11
- package/dist/sdk/sessions/compactionCheckpoint.d.ts +11 -0
- package/dist/sdk/sessions/compactionCheckpoint.js +79 -0
- package/dist/sdk/sessions/continuation.d.ts +3 -0
- package/dist/sdk/sessions/continuation.js +5 -1
- package/dist/sdk/sessions/contracts.d.ts +185 -0
- package/dist/sdk/sessions/contracts.js +101 -4
- package/dist/sdk/sessions/control.d.ts +72 -0
- package/dist/sdk/sessions/control.js +158 -0
- package/dist/sdk/sessions/initialBash.d.ts +7 -0
- package/dist/sdk/sessions/initialBash.js +7 -0
- package/dist/sdk/sessions/planMode.d.ts +18 -4
- package/dist/sdk/sessions/planMode.js +82 -16
- package/dist/sdk/sessions/reasoningSummaries.d.ts +2 -2
- package/dist/sdk/sessions/reasoningSummaries.js +3 -3
- package/dist/sdk/sessions/registry.d.ts +2 -1
- package/dist/sdk/sessions/registry.js +6 -3
- package/dist/sdk/sessions/runIndex.js +7 -7
- package/dist/sdk/sessions/runStore.d.ts +14 -0
- package/dist/sdk/sessions/runStore.js +38 -1
- package/dist/sdk/sessions/runner.d.ts +40 -2
- package/dist/sdk/sessions/runner.js +163 -15
- package/dist/sdk/sessions/sessions.d.ts +40 -8
- package/dist/sdk/sessions/sessions.js +279 -27
- package/dist/sdk/sessions/sideQuestionServerMethods.d.ts +3 -0
- package/dist/sdk/sessions/sideQuestionServerMethods.js +19 -0
- package/dist/sdk/sessions/sideQuestions.d.ts +66 -0
- package/dist/sdk/sessions/sideQuestions.js +290 -0
- package/dist/sdk/sessions/subagentServerMethods.d.ts +3 -0
- package/dist/sdk/sessions/subagentServerMethods.js +41 -0
- package/dist/sdk/sessions/subagentService.d.ts +22 -2
- package/dist/sdk/sessions/subagentService.js +93 -16
- package/dist/sdk/sessions/subagents.d.ts +9 -1
- package/dist/sdk/sessions/subagents.js +19 -0
- package/dist/sdk/sessions/workSlice.d.ts +103 -0
- package/dist/sdk/sessions/workSlice.js +297 -0
- package/dist/sdk/sessions/workspaceBoundary.d.ts +15 -0
- package/dist/sdk/sessions/workspaceBoundary.js +46 -0
- package/dist/sdk/terminal/browser.d.ts +62 -0
- package/dist/sdk/terminal/browser.js +41 -0
- package/dist/sdk/terminal/browserControl.d.ts +29 -0
- package/dist/sdk/terminal/browserControl.js +187 -0
- package/dist/sdk/terminal/commandHelp.d.ts +17 -0
- package/dist/sdk/terminal/commandHelp.js +20 -0
- package/dist/sdk/terminal/controller.d.ts +15 -1
- package/dist/sdk/terminal/controller.js +67 -8
- package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +28 -0
- package/dist/sdk/terminal/opentui/subagentsWidget.js +52 -0
- package/dist/sdk/terminal/opentui/theme.d.ts +43 -0
- package/dist/sdk/terminal/opentui/theme.js +87 -0
- package/dist/sdk/terminal/piExtension.d.ts +6 -1
- package/dist/sdk/terminal/piExtension.js +169 -84
- package/dist/sdk/terminal/socket.d.ts +6 -3
- package/dist/sdk/terminal/socket.js +40 -9
- package/dist/sdk/terminal/standalone.d.ts +9 -0
- package/dist/sdk/terminal/standalone.js +118 -19
- package/dist/sdk/terminal/subagentMonitor.d.ts +47 -0
- package/dist/sdk/terminal/subagentMonitor.js +152 -0
- package/dist/sdk/terminal/terminals.d.ts +60 -7
- package/dist/sdk/terminal/terminals.js +598 -47
- package/dist/sdk/terminal/trajectory.js +2 -2
- package/dist/sdk/terminal/tui/annotateCommand.d.ts +3 -0
- package/dist/sdk/terminal/tui/annotateCommand.js +22 -0
- package/dist/sdk/terminal/tui/commandPages.d.ts +7 -1
- package/dist/sdk/terminal/tui/commandPages.js +57 -6
- package/dist/sdk/terminal/tui/commands.d.ts +10 -4
- package/dist/sdk/terminal/tui/commands.js +269 -65
- package/dist/sdk/terminal/tui/dialogGeometry.d.ts +2 -2
- package/dist/sdk/terminal/tui/dialogGeometry.js +4 -2
- package/dist/sdk/terminal/tui/dialogs.d.ts +6 -1
- package/dist/sdk/terminal/tui/dialogs.js +9 -2
- package/dist/sdk/terminal/tui/list.d.ts +14 -1
- package/dist/sdk/terminal/tui/list.js +96 -14
- package/dist/sdk/terminal/tui/modelPicker.js +1 -6
- package/dist/sdk/terminal/tui/page.d.ts +3 -0
- package/dist/sdk/terminal/tui/pageOverlay.js +27 -7
- package/dist/sdk/terminal/tui/pages/accountsPage.d.ts +26 -4
- package/dist/sdk/terminal/tui/pages/accountsPage.js +264 -36
- package/dist/sdk/terminal/tui/pages/agentsPage.d.ts +74 -0
- package/dist/sdk/terminal/tui/pages/agentsPage.js +190 -0
- package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +94 -0
- package/dist/sdk/terminal/tui/pages/annotatePage.js +341 -0
- package/dist/sdk/terminal/tui/pages/browserPage.d.ts +87 -0
- package/dist/sdk/terminal/tui/pages/browserPage.js +738 -0
- package/dist/sdk/terminal/tui/pages/doctorPage.d.ts +2 -0
- package/dist/sdk/terminal/tui/pages/doctorPage.js +103 -15
- package/dist/sdk/terminal/tui/pages/notesPage.d.ts +75 -0
- package/dist/sdk/terminal/tui/pages/notesPage.js +201 -0
- package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +0 -5
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +9 -3
- package/dist/sdk/terminal/tui/pages/settingsPage.d.ts +8 -0
- package/dist/sdk/terminal/tui/pages/settingsPage.js +16 -1
- package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +24 -0
- package/dist/sdk/terminal/tui/pages/subagentsPage.js +152 -0
- package/dist/sdk/terminal/tui/pages/trajectoryPage.d.ts +12 -5
- package/dist/sdk/terminal/tui/pages/trajectoryPage.js +156 -83
- package/dist/sdk/terminal/tui/pages/usagePage.d.ts +43 -0
- package/dist/sdk/terminal/tui/pages/usagePage.js +370 -0
- package/dist/sdk/terminal/tui/pages/worktreesPage.d.ts +72 -0
- package/dist/sdk/terminal/tui/pages/worktreesPage.js +203 -0
- package/dist/sdk/terminal/tui/piAdapter.d.ts +1 -2
- package/dist/sdk/terminal/tui/piAdapter.js +3 -5
- package/dist/sdk/terminal/tui/planMode.d.ts +1 -1
- package/dist/sdk/terminal/tui/planMode.js +1 -1
- package/dist/sdk/terminal/tui/pluginCommands.d.ts +16 -0
- package/dist/sdk/terminal/tui/pluginCommands.js +31 -0
- package/dist/sdk/terminal/tui/questions.d.ts +2 -0
- package/dist/sdk/terminal/tui/runtime.js +27 -5
- package/dist/sdk/terminal/tui/sessionPicker.d.ts +40 -0
- package/dist/sdk/terminal/tui/sessionPicker.js +141 -0
- package/dist/sdk/terminal/tui/settingsModel.d.ts +2 -0
- package/dist/sdk/terminal/tui/settingsModel.js +32 -2
- package/dist/sdk/terminal/tui/sideQuestion.d.ts +26 -26
- package/dist/sdk/terminal/tui/sideQuestion.js +131 -98
- package/dist/sdk/terminal/tui/ui.d.ts +2 -0
- package/dist/sdk/terminal/tui/ui.js +1 -0
- package/dist/sdk/tools/askUser.js +26 -8
- package/dist/sdk/tools/checklist.js +3 -2
- package/dist/sdk/tools/checkpoint.d.ts +27 -0
- package/dist/sdk/tools/checkpoint.js +154 -0
- package/dist/sdk/tools/getCapabilities.d.ts +31 -0
- package/dist/sdk/tools/getCapabilities.js +34 -0
- package/dist/sdk/tools/harnessTools.d.ts +2 -0
- package/dist/sdk/tools/harnessTools.js +1 -1
- package/dist/sdk/tools/spawnSubagent.js +14 -4
- package/dist/sdk/usage/analytics.d.ts +20 -0
- package/dist/sdk/usage/analytics.js +151 -0
- package/dist/sdk/usage/ledger.d.ts +38 -0
- package/dist/sdk/usage/ledger.js +142 -0
- package/dist/sdk/usage/limits/adapters.d.ts +11 -0
- package/dist/sdk/usage/limits/adapters.js +349 -0
- package/dist/sdk/usage/limits/capture.d.ts +8 -0
- package/dist/sdk/usage/limits/capture.js +47 -0
- package/dist/sdk/usage/limits/http.d.ts +12 -0
- package/dist/sdk/usage/limits/http.js +51 -0
- package/dist/sdk/usage/limits/normalize.d.ts +15 -0
- package/dist/sdk/usage/limits/normalize.js +211 -0
- package/dist/sdk/usage/limits/service.d.ts +30 -0
- package/dist/sdk/usage/limits/service.js +176 -0
- package/dist/sdk/usage/limits/types.d.ts +65 -0
- package/dist/sdk/usage/limits/types.js +1 -0
- package/dist/sdk/usage/serverMethods.d.ts +3 -0
- package/dist/sdk/usage/serverMethods.js +22 -0
- package/dist/sdk/usage/service.d.ts +32 -0
- package/dist/sdk/usage/service.js +66 -0
- package/dist/sdk/usage/settings.d.ts +7 -0
- package/dist/sdk/usage/settings.js +28 -0
- package/dist/sdk/usage/types.d.ts +60 -0
- package/dist/sdk/usage/types.js +4 -0
- package/dist/sdk/web/part.d.ts +10 -0
- package/dist/sdk/web/part.js +6 -0
- package/dist/sdk/web/serverMethods.d.ts +3 -0
- package/dist/sdk/web/serverMethods.js +7 -0
- package/dist/sdk/wiki/serverMethods.d.ts +3 -0
- package/dist/sdk/wiki/serverMethods.js +26 -0
- package/dist/sdk/worktrees/cli.d.ts +11 -0
- package/dist/sdk/worktrees/cli.js +146 -0
- package/dist/sdk/worktrees/copy.d.ts +9 -0
- package/dist/sdk/worktrees/copy.js +111 -0
- package/dist/sdk/worktrees/git.d.ts +16 -0
- package/dist/sdk/worktrees/git.js +81 -0
- package/dist/sdk/worktrees/serverMethods.d.ts +3 -0
- package/dist/sdk/worktrees/serverMethods.js +30 -0
- package/dist/sdk/worktrees/service.d.ts +73 -0
- package/dist/sdk/worktrees/service.js +483 -0
- package/dist/sdk/worktrees/setup.d.ts +23 -0
- package/dist/sdk/worktrees/setup.js +65 -0
- package/dist/sdk/worktrees/types.d.ts +91 -0
- package/dist/sdk/worktrees/types.js +1 -0
- package/dist/sdk/world/cli.d.ts +2 -2
- package/dist/sdk/world/cli.js +6 -6
- package/dist/sdk/world/world.d.ts +3 -1
- package/dist/sdk/world/world.js +8 -9
- package/dist/terminal-opentui.js +16456 -5966
- package/dist/terminal-pi.js +1579 -573
- package/docs/README.md +22 -2
- package/docs/architecture/00-overview.md +28 -16
- package/docs/architecture/02-sessions-and-runs.md +184 -25
- package/docs/architecture/03-variants.md +10 -2
- package/docs/architecture/04-prompt.md +65 -7
- package/docs/architecture/06-plugins-and-capabilities.md +57 -4
- package/docs/architecture/07-tools-and-gate.md +112 -17
- package/docs/architecture/08-memory-and-history.md +4 -2
- package/docs/architecture/09-accounts-and-connectors.md +39 -1
- package/docs/architecture/10-server-protocol.md +168 -4
- package/docs/architecture/11-terminal.md +251 -8
- package/docs/architecture/12-storage.md +45 -3
- package/docs/architecture/13-world-tools.md +49 -6
- package/docs/architecture/14-surfaces-and-parity.md +136 -0
- package/docs/architecture/README.md +17 -4
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +4 -2
- package/docs/architecture/proposals/PROP-0013-resource-browser.md +58 -0
- package/docs/architecture/proposals/PROP-0014-fullscreen-browser-tabs.md +58 -0
- package/docs/architecture/proposals/PROP-0015-one-capability-surface.md +88 -0
- package/docs/architecture/proposals/PROP-0016-managed-worktrees.md +156 -0
- package/docs/architecture/proposals/PROP-0017-page-annotations.md +131 -0
- package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +179 -0
- package/docs/architecture/proposals/PROP-0019-contextual-side-questions.md +122 -0
- package/docs/architecture/proposals/PROP-0020-builtin-feature-plugins.md +105 -0
- package/docs/architecture/proposals/PROP-0021-command-namespaces-and-duplicates.md +99 -0
- package/docs/architecture/proposals/PROP-0022-plan-only-questions.md +102 -0
- package/docs/architecture/proposals/PROP-0023-xagent-cli-name.md +61 -0
- package/docs/architecture/proposals/PROP-0024-question-sketch-previews.md +130 -0
- package/docs/architecture/proposals/PROP-0025-contextual-question-wireframes.md +49 -0
- package/docs/architecture/proposals/PROP-0026-connected-sketch-display.md +94 -0
- package/docs/architecture/proposals/PROP-0027-xingular-agent-product-identity.md +60 -0
- package/docs/architecture/proposals/PROP-0028-normalize-question-wireframes.md +55 -0
- package/docs/architecture/proposals/PROP-0029-agent-profiles.md +124 -0
- package/docs/architecture/proposals/PROP-0030-subagent-title.md +86 -0
- package/docs/architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md +54 -0
- package/docs/architecture/proposals/PROP-0032-planning-quality.md +196 -0
- package/docs/architecture/proposals/PROP-0033-compaction-plan-checkpoints.md +75 -0
- package/docs/architecture/proposals/PROP-0034-conversation-fast-mode.md +120 -0
- package/docs/architecture/proposals/PROP-0035-retain-subagent-status.md +47 -0
- package/docs/architecture/proposals/PROP-0036-first-class-harness-commands.md +59 -0
- package/docs/architecture/proposals/PROP-0037-workspace-write-boundary.md +63 -0
- package/docs/architecture/proposals/PROP-0038-annotate-open-shadow-roots.md +73 -0
- package/docs/architecture/proposals/PROP-0039-workspace-boundary-hardening.md +139 -0
- package/docs/architecture/proposals/PROP-0040-one-launch-path.md +87 -0
- package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +247 -0
- package/docs/architecture/proposals/PROP-0042-usage-and-provider-limits.md +127 -0
- package/docs/architecture/proposals/PROP-0043-read-only-plan-bash.md +74 -0
- package/docs/architecture/proposals/README.md +39 -0
- package/docs/decisions/ADR-0001-owned-terminal.md +1 -1
- package/docs/decisions/ADR-0009-xharness-command.md +6 -5
- package/docs/decisions/ADR-0014-public-release-baseline.md +3 -2
- package/docs/decisions/ADR-0015-resource-browser.md +28 -0
- package/docs/decisions/ADR-0016-fullscreen-browser-tabs.md +31 -0
- package/docs/decisions/ADR-0017-one-capability-surface.md +34 -0
- package/docs/decisions/ADR-0018-builtin-feature-plugins.md +35 -0
- package/docs/decisions/ADR-0019-managed-git-worktrees.md +31 -0
- package/docs/decisions/ADR-0020-page-annotations.md +37 -0
- package/docs/decisions/ADR-0021-required-command-namespaces.md +40 -0
- package/docs/decisions/ADR-0022-plan-only-question-tools.md +34 -0
- package/docs/decisions/ADR-0023-contextual-side-question-runs.md +35 -0
- package/docs/decisions/ADR-0024-xagent-command.md +40 -0
- package/docs/decisions/ADR-0025-question-choice-sketches.md +49 -0
- package/docs/decisions/ADR-0026-contextual-question-wireframes.md +58 -0
- package/docs/decisions/ADR-0027-connected-sketch-display.md +60 -0
- package/docs/decisions/ADR-0028-xingular-agent-product-identity.md +56 -0
- package/docs/decisions/ADR-0029-normalized-question-wireframe-borders.md +33 -0
- package/docs/decisions/ADR-0030-agent-profiles.md +43 -0
- package/docs/decisions/ADR-0031-plugin-command-owner-namespaces.md +37 -0
- package/docs/decisions/ADR-0032-compaction-run-checkpoints.md +33 -0
- package/docs/decisions/ADR-0033-plan-mode-research-delegation.md +47 -0
- package/docs/decisions/ADR-0034-first-class-harness-terminal-commands.md +25 -0
- package/docs/decisions/ADR-0035-one-launch-path.md +40 -0
- package/docs/decisions/ADR-0036-workspace-write-operation-boundary.md +54 -0
- package/docs/decisions/ADR-0037-usage-ledger-and-provider-limits.md +44 -0
- package/docs/decisions/ADR-0038-plan-bash-os-read-only.md +29 -0
- package/docs/doctor.md +10 -4
- package/docs/experiments/EXP-0003-performance-audit/data/session-runtime-replication.json +420 -0
- package/docs/experiments/EXP-0003-performance-audit/data/session-runtime.json +420 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-compact.json +376 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-history.json +422 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-question.json +95 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-wide.json +1036 -0
- package/docs/experiments/EXP-0003-performance-audit/plan.md +109 -0
- package/docs/experiments/EXP-0003-performance-audit/results.md +151 -0
- package/docs/features/README.md +26 -0
- package/docs/features/interfaces/README.md +1 -1
- package/docs/future-work.md +48 -0
- package/docs/handoff-2026-10-25-app-parity-and-solo-tabs.md +202 -0
- package/docs/parity.md +134 -0
- package/docs/releases/1.0.0.md +2 -2
- package/docs/releases/1.1.1.md +28 -0
- package/docs/releases/1.2.0.md +49 -0
- package/docs/releases/4.0.0.md +47 -0
- package/docs/releases/README.md +37 -27
- package/docs/study/00-index.md +8 -0
- package/docs/study/STUDY-0003-long-horizon-runs.md +210 -0
- package/docs/study/autonomous-engineering-sources.md +277 -0
- package/docs/study/autonomy-sources.md +147 -0
- package/docs/study/proactive-autonomy.md +408 -0
- package/docs/study/tui-resource-browser.md +78 -0
- package/docs/terminal-tui.md +386 -113
- package/package.json +6 -5
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AgentSession, type ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
2
|
export interface TerminalPlanRunState {
|
|
3
3
|
active: boolean;
|
|
4
4
|
allowedTools: ReadonlySet<string>;
|
|
5
|
+
connectors?: string[];
|
|
6
|
+
subagentProfiles?: {
|
|
7
|
+
normal: string;
|
|
8
|
+
plan: string;
|
|
9
|
+
};
|
|
5
10
|
}
|
|
6
11
|
export default function (pi: ExtensionAPI, planRun?: TerminalPlanRunState, getSession?: () => AgentSession): Promise<void>;
|
|
@@ -1,14 +1,29 @@
|
|
|
1
|
+
// Loaded by the `pi` command in a terminal session (`--extension`). It reports what the native
|
|
2
|
+
// session does to the harness, asks before each tool call, takes instructions the harness
|
|
3
|
+
// queues for it, and gives the session the harness's own tools. It runs in the `pi` process,
|
|
4
|
+
// so everything it knows about the harness comes through the session socket.
|
|
5
|
+
import { createBashTool, createEditTool, createWriteTool } from '@earendil-works/pi-coding-agent';
|
|
1
6
|
import { Type } from 'typebox';
|
|
2
7
|
import { connectionFromEnv, socketCall, terminalCall } from "./client.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { claimTerminalControl, controlCall } from "./control.js";
|
|
7
|
-
import { SUBAGENT_OWNER, SUBAGENT_RECEIPT, SUBAGENT_RESULTS, subagentHistory } from "../sessions/subagents.js";
|
|
8
|
+
import { controlCall } from "./control.js";
|
|
9
|
+
import { SPAWN_SUBAGENT, SUBAGENT_OWNER, SUBAGENT_RECEIPT, SUBAGENT_RESULTS, subagentHistory } from "../sessions/subagents.js";
|
|
10
|
+
import { PLAN_RESEARCH_TOOLS } from "../agents/planResearch.js";
|
|
8
11
|
import { askUserTool } from "../tools/askUser.js";
|
|
12
|
+
import { checklistTool } from "../tools/checklist.js";
|
|
13
|
+
import { checkpointTool } from "../tools/checkpoint.js";
|
|
14
|
+
import { createSliceState, parseWorkSlice, workSliceGuard } from "../sessions/workSlice.js";
|
|
15
|
+
import { DEFAULT_COMMAND_TIMEOUT_SECONDS, withCommandBudget } from "../core/commandBudget.js";
|
|
9
16
|
import { sessionNameTool } from "../tools/sessionName.js";
|
|
17
|
+
import { CAPABILITY_DISCOVERY, describeCapabilities } from "../tools/getCapabilities.js";
|
|
10
18
|
import { PLAN_INSTRUCTIONS, planRefusal } from "../sessions/planMode.js";
|
|
19
|
+
import { compactionCheckpoint } from "../sessions/compactionCheckpoint.js";
|
|
11
20
|
import { pruneToolImages } from "../world/images.js";
|
|
21
|
+
import { parseInitialBashCommand } from "../sessions/initialBash.js";
|
|
22
|
+
import { inspectWorkspaceWrite } from "../sessions/workspaceBoundary.js";
|
|
23
|
+
import { withWorkspaceWriteScope } from "../core/workspaceBashTool.js";
|
|
24
|
+
import { createWorkspaceBashOperations, runWithWorkspaceWriteScope } from "../core/workspaceSandbox.js";
|
|
25
|
+
import { withWorkspaceWriteAuthorization, workspaceEditOperations, workspaceWriteOperations } from "../core/workspaceWriteOperations.js";
|
|
26
|
+
import { BUDGET_GUARD, OUTPUT_TRIM, budgetGuard, outputTrim } from "../plugins/firstParty.js";
|
|
12
27
|
/** How much text a tool handed back to the model. */
|
|
13
28
|
function resultSize(result) {
|
|
14
29
|
const content = result?.content;
|
|
@@ -28,8 +43,6 @@ async function withParentCancellation(signal, work) {
|
|
|
28
43
|
signal?.removeEventListener('abort', stop);
|
|
29
44
|
}
|
|
30
45
|
}
|
|
31
|
-
/** /hub, /plugins and /accounts are pages: the owned terminal fills the screen, and Pi's own dialogs ignore the field. */
|
|
32
|
-
const PAGE = { page: true };
|
|
33
46
|
export default async function (pi, planRun, getSession) {
|
|
34
47
|
await terminalCall('startup', { phase: 'Pi extension loaded' });
|
|
35
48
|
let context = null;
|
|
@@ -45,50 +58,13 @@ export default async function (pi, planRun, getSession) {
|
|
|
45
58
|
let requestStartedAt = null;
|
|
46
59
|
const toolStartedAt = new Map();
|
|
47
60
|
const fileTools = new Map();
|
|
61
|
+
const writeAuthorizations = new Map();
|
|
62
|
+
let workspaceRoot = null;
|
|
48
63
|
// The harness's own tools for this session (connected apps and what the application offers).
|
|
49
64
|
let stage = 'Tool discovery';
|
|
50
65
|
try {
|
|
51
66
|
await terminalCall('startup', { phase: stage });
|
|
52
67
|
const connection = connectionFromEnv();
|
|
53
|
-
// Claimed here, before the model has run anything, and kept in this process's memory: a
|
|
54
|
-
// command the model runs can read the session token from its environment, never this key.
|
|
55
|
-
const control = await claimTerminalControl(connection).catch(() => null);
|
|
56
|
-
pi.registerCommand('hub', {
|
|
57
|
-
description: 'Browse and install reviewed skills, plugins, and reusable documents',
|
|
58
|
-
handler: async (_args, ctx) => {
|
|
59
|
-
if (!control)
|
|
60
|
-
return ctx.ui.notify('This session cannot install hub items.', 'warning');
|
|
61
|
-
await runHubMenu({ select: (title, options) => ctx.ui.select(title, options, PAGE), confirm: (title, message) => ctx.ui.confirm(title, message), notify: (message, level) => ctx.ui.notify(message, level) }, (method, params) => socketCall(connection, method, params ?? {}, 300_000, control));
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
pi.registerCommand('plugins', {
|
|
65
|
-
description: 'See what this session loads, and switch a plugin on or off or trust its code',
|
|
66
|
-
handler: async (_args, ctx) => {
|
|
67
|
-
if (!control)
|
|
68
|
-
return ctx.ui.notify('This session cannot change plugins.', 'warning');
|
|
69
|
-
const { projectId } = await socketCall(connection, 'plugins.session', {}, 15_000, control).catch(() => ({ projectId: null }));
|
|
70
|
-
await runPluginsMenu({ select: (title, options) => ctx.ui.select(title, options, PAGE), confirm: (title, message) => ctx.ui.confirm(title, message), notify: (message, level) => ctx.ui.notify(message, level) }, (method, params) => socketCall(connection, method, params ?? {}, 60_000, control), projectId);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
pi.registerCommand('accounts', {
|
|
74
|
-
description: 'Add, sign in to, check, switch, or remove the Pi accounts this harness uses',
|
|
75
|
-
handler: async (_args, ctx) => {
|
|
76
|
-
if (!control)
|
|
77
|
-
return ctx.ui.notify('This session cannot manage accounts.', 'warning');
|
|
78
|
-
const session = await socketCall(connection, 'accounts.session', {}, 15_000, control).catch(() => null);
|
|
79
|
-
if (!session)
|
|
80
|
-
return ctx.ui.notify('This session has no account to manage.', 'warning');
|
|
81
|
-
const outcome = await runAccountsMenu({
|
|
82
|
-
select: (title, options) => ctx.ui.select(title, options, PAGE),
|
|
83
|
-
confirm: (title, message) => ctx.ui.confirm(title, message),
|
|
84
|
-
input: (title, placeholder) => ctx.ui.input(title, placeholder),
|
|
85
|
-
notify: (message, level) => ctx.ui.notify(message, level)
|
|
86
|
-
}, (method, params) => socketCall(connection, method, params ?? {}, 120_000, control), session);
|
|
87
|
-
// The session restarts on the account it chose: Pi ends here, and the launcher starts it again.
|
|
88
|
-
if (outcome === 'relaunch')
|
|
89
|
-
ctx.shutdown();
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
68
|
const { tools } = await socketCall(connection, 'tools.list', {}, 15_000);
|
|
93
69
|
stage = 'Tool registration';
|
|
94
70
|
for (const tool of tools) {
|
|
@@ -104,8 +80,19 @@ export default async function (pi, planRun, getSession) {
|
|
|
104
80
|
}
|
|
105
81
|
});
|
|
106
82
|
}
|
|
83
|
+
// The harness's own extensions a person switched on, as a structured session loads them inline.
|
|
84
|
+
const own = await socketCall(connection, 'firstParty.list', {}, 15_000)
|
|
85
|
+
.then((reply) => reply?.plugins ?? [], () => []);
|
|
86
|
+
for (const plugin of own) {
|
|
87
|
+
if (plugin.id === OUTPUT_TRIM)
|
|
88
|
+
outputTrim(plugin.settings)(pi);
|
|
89
|
+
if (plugin.id === BUDGET_GUARD)
|
|
90
|
+
budgetGuard(plugin.settings)(pi);
|
|
91
|
+
}
|
|
107
92
|
const native = await controlCall('terminal.subagents.tools');
|
|
108
93
|
nativeSubagents = native.tools.length > 0;
|
|
94
|
+
if (planRun && native.profiles)
|
|
95
|
+
planRun.subagentProfiles = native.profiles;
|
|
109
96
|
for (const tool of native.tools)
|
|
110
97
|
pi.registerTool({
|
|
111
98
|
name: tool.name, label: tool.label, description: tool.description, parameters: Type.Unsafe(tool.parameters),
|
|
@@ -119,6 +106,46 @@ export default async function (pi, planRun, getSession) {
|
|
|
119
106
|
await terminalCall('startupFailed', { reason: `${stage} failed: ${error instanceof Error ? error.message : 'Unavailable'}` }).catch(() => { });
|
|
120
107
|
throw error;
|
|
121
108
|
}
|
|
109
|
+
pi.registerTool({
|
|
110
|
+
...CAPABILITY_DISCOVERY,
|
|
111
|
+
parameters: Type.Unsafe(CAPABILITY_DISCOVERY.parameters),
|
|
112
|
+
async execute(_id, input) {
|
|
113
|
+
const active = pi.getActiveTools();
|
|
114
|
+
const tools = pi.getAllTools();
|
|
115
|
+
const selected = planRun?.active ? [...planRun.allowedTools] : active;
|
|
116
|
+
const name = input && typeof input === 'object' && typeof input.tool === 'string'
|
|
117
|
+
? input.tool
|
|
118
|
+
: undefined;
|
|
119
|
+
const text = JSON.stringify(describeCapabilities({
|
|
120
|
+
capabilities: { version: 1, tools: selected, connectors: planRun?.connectors ?? [], contributions: [] },
|
|
121
|
+
active,
|
|
122
|
+
registered: tools
|
|
123
|
+
}, name));
|
|
124
|
+
return { content: [{ type: 'text', text }], details: undefined };
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
const workspaceBash = createWorkspaceBashOperations(() => workspaceRoot, {
|
|
128
|
+
bypassWorkspaceBoundary: async (request) => {
|
|
129
|
+
const result = await controlCall('terminal.workspaceBoundaryBypass', { tool: 'bash', args: { command: request.command, cwd: request.cwd, workspaceRoot: request.workspaceRoot } });
|
|
130
|
+
return result.bypass;
|
|
131
|
+
},
|
|
132
|
+
requestWorkspaceWriteScope: async (request) => {
|
|
133
|
+
const result = await controlCall('terminal.workspaceEscape', { tool: 'bash', args: { command: request.command, workspaceWriteScope: 'outside' }, reason: request.reason });
|
|
134
|
+
return result.approved;
|
|
135
|
+
},
|
|
136
|
+
workspaceWriteScope: () => planRun?.active ? 'read-only' : undefined
|
|
137
|
+
});
|
|
138
|
+
pi.registerTool(withWorkspaceWriteScope(createBashTool(process.cwd(), { operations: workspaceBash })));
|
|
139
|
+
pi.registerTool(withWorkspaceWriteAuthorization(createWriteTool(process.cwd(), { operations: workspaceWriteOperations }), writeAuthorizations));
|
|
140
|
+
pi.registerTool(withWorkspaceWriteAuthorization(createEditTool(process.cwd(), { operations: workspaceEditOperations }), writeAuthorizations));
|
|
141
|
+
compactionCheckpoint({ planInstructions: () => planRun?.active ? PLAN_INSTRUCTIONS : null })(pi);
|
|
142
|
+
// A terminal reply is bounded like any other (PROP-0041): a checkpoint at the boundary, then the
|
|
143
|
+
// person decides whether to continue.
|
|
144
|
+
const slicePolicy = parseWorkSlice(undefined);
|
|
145
|
+
const sliceState = createSliceState(slicePolicy);
|
|
146
|
+
// `XINGULAR_WORK_SLICE=off` at launch leaves terminal replies unbounded, as before PROP-0041.
|
|
147
|
+
if (slicePolicy.enabled)
|
|
148
|
+
workSliceGuard({ policy: slicePolicy, state: sliceState })(pi);
|
|
122
149
|
// Only the latest screenshots stay in what the model sees (world/images.ts).
|
|
123
150
|
pi.on('context', (event) => {
|
|
124
151
|
const messages = pruneToolImages(event.messages);
|
|
@@ -162,9 +189,14 @@ export default async function (pi, planRun, getSession) {
|
|
|
162
189
|
costReported = false;
|
|
163
190
|
// A run a plugin starts with a custom message has no user prompt to record; it still begins a turn.
|
|
164
191
|
const begun = await terminalCall('begin', { text: event.messageType === 'user' ? event.prompt : '' });
|
|
192
|
+
workspaceRoot = typeof begun.workspaceRoot === 'string' ? begun.workspaceRoot : null;
|
|
193
|
+
if (!workspaceRoot)
|
|
194
|
+
throw new Error('The active project workspace could not be resolved; the turn was not started.');
|
|
165
195
|
if (nativeSubagents) {
|
|
166
196
|
pi.appendEntry(SUBAGENT_OWNER, { runId: begun.runId });
|
|
167
|
-
await controlCall('terminal.subagents.register', { tools: pi.getActiveTools(), model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null, effort: pi.getThinkingLevel(), history: subagentHistory(ctx.sessionManager.getBranch()) });
|
|
197
|
+
const snapshot = await controlCall('terminal.subagents.register', { tools: pi.getActiveTools(), model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null, effort: pi.getThinkingLevel(), plan: planRun?.active === true, history: subagentHistory(ctx.sessionManager.getBranch()) });
|
|
198
|
+
if (planRun && Array.isArray(snapshot.connectors))
|
|
199
|
+
planRun.connectors = snapshot.connectors;
|
|
168
200
|
subagentParent = true;
|
|
169
201
|
}
|
|
170
202
|
if (planRun && event.messageType === 'user') {
|
|
@@ -172,24 +204,31 @@ export default async function (pi, planRun, getSession) {
|
|
|
172
204
|
event.systemPromptOptions.sections.plan = PLAN_INSTRUCTIONS;
|
|
173
205
|
else
|
|
174
206
|
delete event.systemPromptOptions.sections.plan;
|
|
207
|
+
if (planRun.subagentProfiles)
|
|
208
|
+
event.systemPromptOptions.sections.subagents = planRun.active ? planRun.subagentProfiles.plan : planRun.subagentProfiles.normal;
|
|
175
209
|
}
|
|
176
210
|
const bang = event.messageType === 'user' ? /^\s*!\s*([\s\S]+)$/u.exec(event.prompt) : null;
|
|
177
211
|
if (!bang)
|
|
178
212
|
return;
|
|
179
|
-
const
|
|
213
|
+
const initial = parseInitialBashCommand(bang[1]);
|
|
214
|
+
const command = initial.command;
|
|
215
|
+
const args = { command, ...(initial.workspaceWriteScope === 'outside' ? { workspaceWriteScope: 'outside' } : {}) };
|
|
180
216
|
const id = `initial-bash:${begun.runId}`;
|
|
181
217
|
const started = Date.now();
|
|
182
|
-
await terminalCall('tool', { phase: 'start', id, name: 'bash', summary: command.slice(0, 200), input: JSON.stringify(
|
|
218
|
+
await terminalCall('tool', { phase: 'start', id, name: 'bash', summary: command.slice(0, 200), input: JSON.stringify(args) });
|
|
183
219
|
let refusal;
|
|
184
220
|
let result;
|
|
185
|
-
if (
|
|
221
|
+
if (!command) {
|
|
222
|
+
refusal = 'Enter a Bash command after !outside.';
|
|
223
|
+
}
|
|
224
|
+
else if (planRun?.active) {
|
|
186
225
|
refusal = 'Bash commands are disabled while a plan request is running.';
|
|
187
226
|
}
|
|
188
227
|
else {
|
|
189
228
|
try {
|
|
190
|
-
const policy = await terminalCall('policy', { tool: 'bash', args
|
|
229
|
+
const policy = await terminalCall('policy', { tool: 'bash', args });
|
|
191
230
|
if (policy.requiresApproval) {
|
|
192
|
-
const approval = await terminalCall('approval', { requestId: id, tool: 'bash', args
|
|
231
|
+
const approval = await terminalCall('approval', { requestId: id, tool: 'bash', args });
|
|
193
232
|
if (approval.decision !== 'approved')
|
|
194
233
|
refusal = 'The user denied this Bash command.';
|
|
195
234
|
}
|
|
@@ -201,7 +240,7 @@ export default async function (pi, planRun, getSession) {
|
|
|
201
240
|
if (!session)
|
|
202
241
|
refusal = 'The native session is unavailable.';
|
|
203
242
|
else
|
|
204
|
-
result = await session.executeBash(command, undefined, { id });
|
|
243
|
+
result = await runWithWorkspaceWriteScope(initial.workspaceWriteScope, () => withCommandBudget(() => session.executeBash(command, undefined, { id, operations: workspaceBash }), () => session.abortBash(), DEFAULT_COMMAND_TIMEOUT_SECONDS));
|
|
205
244
|
}
|
|
206
245
|
}
|
|
207
246
|
catch (error) {
|
|
@@ -282,21 +321,61 @@ export default async function (pi, planRun, getSession) {
|
|
|
282
321
|
pi.on('turn_start', async () => {
|
|
283
322
|
requestStartedAt = Date.now();
|
|
284
323
|
});
|
|
285
|
-
pi.on('tool_call', async (event) => {
|
|
324
|
+
pi.on('tool_call', async (event, ctx) => {
|
|
286
325
|
if (planRun?.active && !planRun.allowedTools.has(event.toolName)) {
|
|
287
|
-
const reason = planRefusal(event.toolName, new Set()) ?? `Plan mode is research-only: ${event.toolName} is not available in this session.`;
|
|
326
|
+
const reason = planRefusal(event.toolName, new Set(), event.input) ?? `Plan mode is research-only: ${event.toolName} is not available in this session.`;
|
|
288
327
|
return { block: true, reason };
|
|
289
328
|
}
|
|
329
|
+
if (planRun?.active && event.toolName === SPAWN_SUBAGENT) {
|
|
330
|
+
try {
|
|
331
|
+
const eligibility = await controlCall('terminal.subagents.planSafe', { input: event.input });
|
|
332
|
+
if (!eligibility.allowed)
|
|
333
|
+
return { block: true, reason: eligibility.reason ?? 'This profile is not eligible for plan-mode research.' };
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
return { block: true, reason: `Plan-mode research eligibility could not be verified: ${error instanceof Error ? error.message : 'controller unavailable'}` };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else if (planRun?.active) {
|
|
340
|
+
const reason = planRefusal(event.toolName, new Set(PLAN_RESEARCH_TOOLS), event.input);
|
|
341
|
+
if (reason)
|
|
342
|
+
return { block: true, reason };
|
|
343
|
+
}
|
|
344
|
+
let authorization;
|
|
345
|
+
if ((event.toolName === 'edit' || event.toolName === 'write') && typeof event.input.path === 'string') {
|
|
346
|
+
try {
|
|
347
|
+
if (!workspaceRoot)
|
|
348
|
+
throw new Error('The active project workspace is unavailable.');
|
|
349
|
+
const inspection = inspectWorkspaceWrite(workspaceRoot, ctx.cwd, event.toolName, event.input);
|
|
350
|
+
Object.assign(event.input, inspection.input);
|
|
351
|
+
if (!inspection.path)
|
|
352
|
+
throw new Error('The native file-write target could not be verified.');
|
|
353
|
+
authorization = {
|
|
354
|
+
workspaceRoot,
|
|
355
|
+
cwd: ctx.cwd,
|
|
356
|
+
path: inspection.path,
|
|
357
|
+
outside: inspection.outside,
|
|
358
|
+
authorized: false
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
return { block: true, reason: error instanceof Error ? error.message : 'The active project workspace could not be verified.' };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
290
365
|
try {
|
|
291
366
|
const policy = await controlCall('terminal.policy', { tool: event.toolName, args: event.input });
|
|
292
367
|
if (policy.requiresApproval) {
|
|
293
368
|
const approval = await controlCall('terminal.approval', { requestId: event.toolCallId, tool: event.toolName, args: event.input });
|
|
294
|
-
if (approval.decision
|
|
295
|
-
return;
|
|
296
|
-
return { block: true, reason: 'The user denied this action. Ask how they would like to proceed.' };
|
|
369
|
+
if (approval.decision !== 'approved')
|
|
370
|
+
return { block: true, reason: 'The user denied this action. Ask how they would like to proceed.' };
|
|
297
371
|
}
|
|
298
|
-
if (!policy.allowed)
|
|
372
|
+
else if (!policy.allowed) {
|
|
299
373
|
return { block: true, reason: policy.reason ?? 'Outside what this session may do.' };
|
|
374
|
+
}
|
|
375
|
+
if (authorization) {
|
|
376
|
+
authorization.authorized = true;
|
|
377
|
+
writeAuthorizations.set(event.toolCallId, authorization);
|
|
378
|
+
}
|
|
300
379
|
}
|
|
301
380
|
catch (error) {
|
|
302
381
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -329,7 +408,10 @@ export default async function (pi, planRun, getSession) {
|
|
|
329
408
|
}
|
|
330
409
|
}
|
|
331
410
|
subagentParent = false;
|
|
332
|
-
|
|
411
|
+
// A reply that stopped at its work-slice boundary is not a completed task: the record says so,
|
|
412
|
+
// so a host can offer the continuation instead of treating it as done (PROP-0041).
|
|
413
|
+
const reason = sliceState.endedBy ? sliceState.endedBy === 'stuck' ? 'stalled' : 'slice_complete' : undefined;
|
|
414
|
+
await terminalCall('settled', { error: failure, aborted: event.aborted, ...(reason ? { reason } : {}) });
|
|
333
415
|
});
|
|
334
416
|
pi.on('user_bash', async () => {
|
|
335
417
|
throw new Error('Use the ! command composer so this action can be checked.');
|
|
@@ -355,6 +437,32 @@ export default async function (pi, planRun, getSession) {
|
|
|
355
437
|
return { content: [{ type: 'text', text: typeof result === 'string' ? result : result.text }], details: undefined };
|
|
356
438
|
}
|
|
357
439
|
});
|
|
440
|
+
// The checklist is recorded on the harness run; the shared tool validates and the socket carries the event.
|
|
441
|
+
let recorded = Promise.resolve();
|
|
442
|
+
const checklist = checklistTool({ emit: (_type, payload) => { recorded = terminalCall('checklist', payload); } });
|
|
443
|
+
pi.registerTool({
|
|
444
|
+
name: checklist.name, label: checklist.label, description: checklist.description,
|
|
445
|
+
parameters: Type.Unsafe(checklist.parameters(Type)),
|
|
446
|
+
async execute(_id, input) {
|
|
447
|
+
recorded = Promise.resolve();
|
|
448
|
+
const result = await checklist.execute(input);
|
|
449
|
+
await recorded;
|
|
450
|
+
return { content: [{ type: 'text', text: typeof result === 'string' ? result : result.text }], details: undefined };
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
// A bounded reply records its handoff here; the terminal reply is its own slice.
|
|
454
|
+
let checkpointRecorded = Promise.resolve();
|
|
455
|
+
const checkpoint = checkpointTool({ emit: (_type, payload) => { checkpointRecorded = terminalCall('checkpoint', payload); } }, 1);
|
|
456
|
+
pi.registerTool({
|
|
457
|
+
name: checkpoint.name, label: checkpoint.label, description: checkpoint.description,
|
|
458
|
+
parameters: Type.Unsafe(checkpoint.parameters(Type)),
|
|
459
|
+
async execute(_id, input) {
|
|
460
|
+
checkpointRecorded = Promise.resolve();
|
|
461
|
+
const result = await checkpoint.execute(input);
|
|
462
|
+
await checkpointRecorded;
|
|
463
|
+
return { content: [{ type: 'text', text: typeof result === 'string' ? result : result.text }], details: undefined };
|
|
464
|
+
}
|
|
465
|
+
});
|
|
358
466
|
const ask = askUserTool({ requestAnswers: questions => terminalCall('questions', { questions }, 1_230_000) });
|
|
359
467
|
pi.registerTool({
|
|
360
468
|
name: ask.name, label: ask.label, description: ask.description,
|
|
@@ -364,29 +472,6 @@ export default async function (pi, planRun, getSession) {
|
|
|
364
472
|
return { content: [{ type: 'text', text: typeof result === 'string' ? result : result.text }], details: undefined };
|
|
365
473
|
}
|
|
366
474
|
});
|
|
367
|
-
|
|
368
|
-
name: 'ask_user_question',
|
|
369
|
-
label: 'Ask a question',
|
|
370
|
-
description: 'Ask the user a question and wait for their answer. Every question must include at least one choice and at least one choice must be recommended. Recommendations are visual only and never preselect answers; submitted choices keep their labels. Mark at most one choice recommended for single-choice questions or several when multiSelect is true.',
|
|
371
|
-
parameters: Type.Object({
|
|
372
|
-
question: Type.String(),
|
|
373
|
-
options: Type.Array(Type.Object({ label: Type.String(), description: Type.Optional(Type.String()), recommended: Type.Optional(Type.Boolean()) }), {
|
|
374
|
-
minItems: 1,
|
|
375
|
-
contains: Type.Object({ recommended: Type.Literal(true) })
|
|
376
|
-
}),
|
|
377
|
-
multiSelect: Type.Optional(Type.Boolean())
|
|
378
|
-
}),
|
|
379
|
-
async execute(id, input) {
|
|
380
|
-
if (!Array.isArray(input.options) || input.options.length === 0)
|
|
381
|
-
throw new Error('Every question needs at least one choice.');
|
|
382
|
-
const recommendedCount = input.options.filter(option => option.recommended).length;
|
|
383
|
-
if (recommendedCount === 0)
|
|
384
|
-
throw new Error('Every question needs at least one recommended choice.');
|
|
385
|
-
if (!input.multiSelect && recommendedCount > 1)
|
|
386
|
-
throw new Error('A single-choice question may recommend at most one option.');
|
|
387
|
-
const answer = await terminalCall('question', { requestId: id, ...input });
|
|
388
|
-
return { content: [{ type: 'text', text: JSON.stringify(answer) }], details: undefined };
|
|
389
|
-
}
|
|
390
|
-
});
|
|
475
|
+
// One question tool, as in every structured session: the terminal has no ask_user_question alias (PROP-0039).
|
|
391
476
|
await terminalCall('startup', { phase: 'Pi hooks registered' });
|
|
392
477
|
}
|
|
@@ -28,9 +28,12 @@ export interface SocketHandlers {
|
|
|
28
28
|
accounts?(grant: SocketGrant, call: string, params: Record<string, unknown>, control: boolean): Promise<unknown>;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* Windows has no socket files for Node's `net`: it
|
|
32
|
-
* machine-wide namespace
|
|
33
|
-
*
|
|
31
|
+
* Each Harness process listens on its own path. Windows has no socket files for Node's `net`: it
|
|
32
|
+
* listens on a named pipe in a machine-wide namespace, and a random name keeps another local process
|
|
33
|
+
* from guessing it. On Unix the file is named for this process, because several Harness processes
|
|
34
|
+
* may share one state folder: closing a server removes its socket file, so a shared name would let
|
|
35
|
+
* one process delete, and the next one take over, the path another's sessions still call. The token
|
|
36
|
+
* still decides what a caller may do.
|
|
34
37
|
*/
|
|
35
38
|
export declare function socketPath(stateDir: string): string;
|
|
36
39
|
export declare class SessionSocket {
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// reply leaves it open for the next request. Each terminal session holds a random token bound to
|
|
6
6
|
// its caller, its connected-apps grant, and its controller; the token stops working when the
|
|
7
7
|
// terminal ends.
|
|
8
|
-
import {
|
|
9
|
-
import { chmodSync, mkdirSync, rmSync, statSync } from 'node:fs';
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
9
|
+
import { chmodSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
10
10
|
import { createServer } from 'node:net';
|
|
11
11
|
import { tmpdir } from 'node:os';
|
|
12
12
|
import { dirname, join } from 'node:path';
|
|
@@ -17,21 +17,50 @@ const MAX_REQUEST_BYTES = 1024 * 1024;
|
|
|
17
17
|
/** macOS refuses a socket path longer than 103 bytes. */
|
|
18
18
|
const MAX_SOCKET_PATH = 100;
|
|
19
19
|
/**
|
|
20
|
-
* Windows has no socket files for Node's `net`: it
|
|
21
|
-
* machine-wide namespace
|
|
22
|
-
*
|
|
20
|
+
* Each Harness process listens on its own path. Windows has no socket files for Node's `net`: it
|
|
21
|
+
* listens on a named pipe in a machine-wide namespace, and a random name keeps another local process
|
|
22
|
+
* from guessing it. On Unix the file is named for this process, because several Harness processes
|
|
23
|
+
* may share one state folder: closing a server removes its socket file, so a shared name would let
|
|
24
|
+
* one process delete, and the next one take over, the path another's sessions still call. The token
|
|
25
|
+
* still decides what a caller may do.
|
|
23
26
|
*/
|
|
24
27
|
export function socketPath(stateDir) {
|
|
25
28
|
if (process.platform === 'win32')
|
|
26
29
|
return `\\\\.\\pipe\\xingular-harness-${randomBytes(12).toString('hex')}`;
|
|
27
|
-
const path = join(stateDir, 'run',
|
|
30
|
+
const path = join(stateDir, 'run', `harness-${process.pid}-${randomBytes(8).toString('hex')}.sock`);
|
|
28
31
|
if (Buffer.byteLength(path) <= MAX_SOCKET_PATH)
|
|
29
32
|
return path;
|
|
30
|
-
return join(tmpdir(), `xh-${
|
|
33
|
+
return join(tmpdir(), `xh-${process.pid}-${randomBytes(8).toString('hex')}.sock`);
|
|
34
|
+
}
|
|
35
|
+
const OWN_SOCKET = /^harness-(\d+)-[0-9a-f]+\.sock$/;
|
|
36
|
+
/** Removes socket files left in `folder` by Harness processes that are no longer running. */
|
|
37
|
+
function pruneDeadSockets(folder) {
|
|
38
|
+
let names;
|
|
39
|
+
try {
|
|
40
|
+
names = readdirSync(folder);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const name of names) {
|
|
46
|
+
const pid = Number(OWN_SOCKET.exec(name)?.[1]);
|
|
47
|
+
if (!pid || pid === process.pid || isRunning(pid))
|
|
48
|
+
continue;
|
|
49
|
+
rmSync(join(folder, name), { force: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function isRunning(pid) {
|
|
53
|
+
try {
|
|
54
|
+
process.kill(pid, 0);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return error.code === 'EPERM';
|
|
59
|
+
}
|
|
31
60
|
}
|
|
32
61
|
function alternateSocketPath(path) {
|
|
33
62
|
const candidate = join(dirname(path), `harness-${process.pid}-${randomBytes(8).toString('hex')}.sock`);
|
|
34
|
-
return Buffer.byteLength(candidate) <= MAX_SOCKET_PATH ? candidate : join(tmpdir(), `xh-${randomBytes(
|
|
63
|
+
return Buffer.byteLength(candidate) <= MAX_SOCKET_PATH ? candidate : join(tmpdir(), `xh-${process.pid}-${randomBytes(8).toString('hex')}.sock`);
|
|
35
64
|
}
|
|
36
65
|
/** A named pipe has no folder, file mode, or owner to check or clean up. */
|
|
37
66
|
const isPipePath = (path) => path.startsWith('\\\\.\\pipe\\');
|
|
@@ -216,8 +245,10 @@ export class SessionSocket {
|
|
|
216
245
|
}
|
|
217
246
|
/** Binds the usual path, or an instance-specific path if another Harness owns it. */
|
|
218
247
|
async bindAvailablePath() {
|
|
219
|
-
if (!isPipePath(this.path))
|
|
248
|
+
if (!isPipePath(this.path)) {
|
|
220
249
|
mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
250
|
+
pruneDeadSockets(dirname(this.path));
|
|
251
|
+
}
|
|
221
252
|
try {
|
|
222
253
|
await this.bind(this.path);
|
|
223
254
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
+
import type { SecretStore } from "../accounts/secrets.js";
|
|
3
|
+
import { type SecretBackendDescriptor } from "../core/interactiveProfile.js";
|
|
2
4
|
import { type Harness, type HarnessOptions } from "../harness.js";
|
|
3
5
|
export interface StandalonePaths {
|
|
4
6
|
dataRoot: string;
|
|
5
7
|
stateDir: string;
|
|
8
|
+
/** `--worktree [name]`: start in a new managed worktree (true) or the named one. */
|
|
9
|
+
worktree?: string | true | null;
|
|
10
|
+
/** `--variant <scope>`: run on that scope's head, as a host's agent of that scope does. */
|
|
11
|
+
variant?: string | null;
|
|
6
12
|
}
|
|
7
13
|
export interface StandaloneDependencies {
|
|
8
14
|
home?: string;
|
|
@@ -11,6 +17,9 @@ export interface StandaloneDependencies {
|
|
|
11
17
|
stdoutIsTTY?: boolean;
|
|
12
18
|
createSessionId?: () => string;
|
|
13
19
|
createHarness?: (options: HarnessOptions) => Harness;
|
|
20
|
+
hostSecretStore?: (descriptor: Extract<SecretBackendDescriptor, {
|
|
21
|
+
kind: 'host';
|
|
22
|
+
}>) => SecretStore | Promise<SecretStore>;
|
|
14
23
|
spawn?: typeof nodeSpawn;
|
|
15
24
|
}
|
|
16
25
|
export declare function standaloneArguments(argv: string[]): string[] | null;
|