@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
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { constants, closeSync, fstatSync, lstatSync, openSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
2
|
+
import { describePlugin } from "../plugins/surfaces.js";
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
5
|
+
import { invalid } from "../core/errors.js";
|
|
6
|
+
import { connectionInputSchema } from "../mcp/types.js";
|
|
7
|
+
import * as library from "../variant/components.js";
|
|
8
|
+
import { PI_SURFACE, componentOf } from "../variant/surface.js";
|
|
9
|
+
import { headVariant } from "../variant/store.js";
|
|
10
|
+
const MAX_CONTENT = 256 * 1024;
|
|
11
|
+
export function isWithinBoundary(boundary, file, pathApi = path) {
|
|
12
|
+
const within = pathApi.relative(boundary, file);
|
|
13
|
+
return within !== '..' && !within.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(within);
|
|
14
|
+
}
|
|
15
|
+
function readText(path) {
|
|
16
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
17
|
+
try {
|
|
18
|
+
const stat = fstatSync(fd);
|
|
19
|
+
if (!stat.isFile() || stat.size > MAX_CONTENT)
|
|
20
|
+
throw invalid('Preview requires a regular text file smaller than 256 KiB.');
|
|
21
|
+
return readFileSync(fd, 'utf8');
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
closeSync(fd);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Only the protected terminal control route calls this adapter. It never loads discovered code. */
|
|
28
|
+
export class BrowserControl {
|
|
29
|
+
options;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.options = options;
|
|
32
|
+
}
|
|
33
|
+
async snapshot(refresh = false) {
|
|
34
|
+
const { root, pinned, apps } = this.options;
|
|
35
|
+
const plugins = await this.options.plugins();
|
|
36
|
+
const scope = pinned?.scope ?? null;
|
|
37
|
+
const latest = scope ? headVariant(root, scope) : null;
|
|
38
|
+
const items = [];
|
|
39
|
+
const add = (item) => items.push(item);
|
|
40
|
+
for (const [id, name, description] of [
|
|
41
|
+
['runtime', 'Runtime and recording', 'The foundation runs sessions and records their events. Changes require a release.'],
|
|
42
|
+
['prompt', 'Prompt compiler', 'Assembles the stable prompt and context updates. Variant c1–c4 supply configurable instructions.'],
|
|
43
|
+
['loader', 'Resource loader', 'Selects plugins and their contributions. Discovered code waits for exact-code trust.'],
|
|
44
|
+
['gate', 'Tool approval gate', 'Checks capabilities, policy and approvals for every tool call. Availability never grants authorization.'],
|
|
45
|
+
['accounts', 'Accounts and secrets', 'Keeps account identities isolated and credentials in the secret store.'],
|
|
46
|
+
['surface', 'Variant surface', 'Defines c1–c8, validation and immutable versions. Only a validated variant version may change.']
|
|
47
|
+
])
|
|
48
|
+
add({ id: `foundation:${id}`, section: 'foundation', name: name, description: description, owner: 'Foundation', origin: 'Built-in', status: 'Read-only' });
|
|
49
|
+
for (const plugin of plugins) {
|
|
50
|
+
add({ id: `plugin:${plugin.id}`, section: 'plugins', name: plugin.title, description: describePlugin(plugin),
|
|
51
|
+
owner: plugin.title, origin: plugin.source, status: plugin.required ? 'Required' : plugin.trust === 'untrusted' ? 'Needs trust' : 'Saved configuration',
|
|
52
|
+
pluginId: plugin.id, path: plugin.path ?? undefined, required: plugin.required });
|
|
53
|
+
for (const entry of plugin.contributions) {
|
|
54
|
+
if (!['skill', 'tool', 'command', 'instruction'].includes(entry.kind))
|
|
55
|
+
continue;
|
|
56
|
+
if ((entry.kind === 'command' || entry.kind === 'instruction') && !entry.path)
|
|
57
|
+
continue;
|
|
58
|
+
const section = entry.kind === 'skill' ? 'skills' : entry.kind === 'tool' ? 'tools' : 'library';
|
|
59
|
+
add({ id: `contribution:${entry.id}`, section, name: entry.name, description: entry.description,
|
|
60
|
+
owner: plugin.title, origin: plugin.source, pluginId: plugin.id, contributionId: entry.id,
|
|
61
|
+
path: entry.path ?? undefined, scope: entry.scope, status: entry.state === 'off' ? 'Disabled' : entry.state === 'untrusted' ? 'Needs trust' : 'Enabled for future sessions',
|
|
62
|
+
read: Boolean(entry.path && ['skill', 'command', 'instruction'].includes(entry.kind)) });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const item of library.list(root)) {
|
|
66
|
+
const added = Boolean(latest && library.isInVariant(root, latest.variant, item.id));
|
|
67
|
+
const existing = items.find(existing => existing.path === item.path);
|
|
68
|
+
if (existing) {
|
|
69
|
+
existing.componentId = item.id;
|
|
70
|
+
existing.added = added;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
add({ id: `library:${item.id}`, section: item.kind === 'skills' ? 'skills' : 'library', name: item.name,
|
|
74
|
+
description: item.description, owner: 'Reusable library', origin: item.builtin ? 'Built-in library' : 'Local library', path: item.path,
|
|
75
|
+
componentId: item.id, added, status: added ? 'Saved in latest variant' : 'Library only', read: true });
|
|
76
|
+
}
|
|
77
|
+
const paths = new Set([...Object.keys(pinned?.files ?? {}), ...Object.keys(latest?.variant ?? {})]);
|
|
78
|
+
for (const path of [...paths].sort()) {
|
|
79
|
+
const component = PI_SURFACE.components.find(entry => entry.id === componentOf(PI_SURFACE, path));
|
|
80
|
+
if (!component)
|
|
81
|
+
continue;
|
|
82
|
+
const current = pinned?.files[path];
|
|
83
|
+
const future = latest?.variant[path];
|
|
84
|
+
const status = !latest || current === future ? 'Pinned version' : current === undefined ? 'Added in latest' : future === undefined ? 'Removed in latest' : 'Changed in latest';
|
|
85
|
+
add({ id: `variant:${path}`, section: 'variant', name: path, description: `c${component.index} · ${component.title}: ${component.description}`,
|
|
86
|
+
owner: 'Variant', origin: 'Variant', status, read: true });
|
|
87
|
+
if (/^skills\/[^/]+\/SKILL\.md$/.test(path) && current !== undefined) {
|
|
88
|
+
const name = path.split('/')[1];
|
|
89
|
+
const fullPath = join(pinned.dir, path);
|
|
90
|
+
if (!items.some(item => item.section === 'skills' && item.path === fullPath))
|
|
91
|
+
add({ id: `variant-skill:${path}`, section: 'skills', name,
|
|
92
|
+
description: /^description:\s*(.+)$/m.exec(current)?.[1] ?? '', owner: 'Variant', origin: 'Variant', status: 'Prepared for this session',
|
|
93
|
+
path: fullPath, read: true });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const connections = apps.connections.list().map(connection => ({ ...connection,
|
|
97
|
+
error: connection.error ? String(apps.connections.scrub(connection.error, connection.id)) : null,
|
|
98
|
+
tools: apps.connections.scrub(connection.tools, connection.id) }));
|
|
99
|
+
for (const connection of connections)
|
|
100
|
+
add({ id: `connection:${connection.id}`, section: 'connections', name: connection.name || connection.id,
|
|
101
|
+
description: `${connection.kind === 'remote' ? 'Remote HTTP' : 'Local stdio'} MCP · ${connection.tools.length} tools${connection.checkedAt ? ` · last checked ${connection.checkedAt}` : ' · never tested'}`,
|
|
102
|
+
owner: 'Connected apps', origin: connection.kind === 'remote' ? 'Remote' : 'Local', connectionId: connection.id,
|
|
103
|
+
scope: connection.global ? 'Everywhere' : connection.projectIds.join(', ') || 'No project scope',
|
|
104
|
+
status: !connection.enabled ? 'Disabled' : connection.auth === 'oauth' && connection.authState !== 'signed_in' ? 'Sign in required' : connection.error ? 'Last check failed' : connection.checkedAt ? 'Last check passed' : 'Not tested' });
|
|
105
|
+
return { items, plugins, connections, variant: { scope, pinned: pinned?.fingerprint ?? null, latest: latest?.fingerprint ?? null,
|
|
106
|
+
writable: this.options.writable && Boolean(scope && latest) }, hub: this.options.hub ? await this.options.hub.list(refresh) : null };
|
|
107
|
+
}
|
|
108
|
+
async read(id, latestVersion = false) {
|
|
109
|
+
const { root, pinned } = this.options;
|
|
110
|
+
const snapshot = await this.snapshot();
|
|
111
|
+
const item = snapshot.items.find(entry => entry.id === id);
|
|
112
|
+
if (!item?.read)
|
|
113
|
+
throw invalid('This resource has no inspectable content.');
|
|
114
|
+
if (id.startsWith('variant:') || id.startsWith('variant-skill:')) {
|
|
115
|
+
const path = id.slice(id.indexOf(':') + 1);
|
|
116
|
+
const files = latestVersion && pinned?.scope ? headVariant(root, pinned.scope)?.variant : pinned?.files;
|
|
117
|
+
const content = files?.[path] ?? '(File absent in this version.)';
|
|
118
|
+
return { content: content.length > MAX_CONTENT ? content.slice(0, MAX_CONTENT) + '\n[Preview truncated.]' : content };
|
|
119
|
+
}
|
|
120
|
+
if (!item.path)
|
|
121
|
+
throw invalid('This resource has no file.');
|
|
122
|
+
const plugin = snapshot.plugins.find(plugin => plugin.id === item.pluginId);
|
|
123
|
+
const registryItem = item.componentId ? library.list(root).find(entry => entry.id === item.componentId) : undefined;
|
|
124
|
+
const boundary = realpathSync(registryItem && !registryItem.builtin ? join(root, 'registry') : plugin?.path ?? dirname(item.path));
|
|
125
|
+
const file = realpathSync(item.path);
|
|
126
|
+
if (!isWithinBoundary(boundary, file))
|
|
127
|
+
throw invalid('Resource is outside its owner folder.');
|
|
128
|
+
return { content: readText(file) };
|
|
129
|
+
}
|
|
130
|
+
importSkill(input) {
|
|
131
|
+
const start = resolve(this.options.cwd, input);
|
|
132
|
+
const folder = lstatSync(start).isDirectory() ? start : dirname(start);
|
|
133
|
+
if (start !== folder && basename(start) !== 'SKILL.md')
|
|
134
|
+
throw invalid('Choose a skill folder or its SKILL.md.');
|
|
135
|
+
const files = [];
|
|
136
|
+
let bytes = 0;
|
|
137
|
+
let directories = 0;
|
|
138
|
+
const visit = (directory, depth = 0) => {
|
|
139
|
+
if (++directories > 100 || depth > 16)
|
|
140
|
+
throw invalid('Skill import exceeds its directory limit.');
|
|
141
|
+
for (const name of readdirSync(directory)) {
|
|
142
|
+
if (name.startsWith('.'))
|
|
143
|
+
continue;
|
|
144
|
+
const path = join(directory, name);
|
|
145
|
+
const stat = lstatSync(path);
|
|
146
|
+
if (stat.isSymbolicLink())
|
|
147
|
+
throw invalid('Skill imports cannot contain symbolic links.');
|
|
148
|
+
if (stat.isDirectory()) {
|
|
149
|
+
visit(path, depth + 1);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (!stat.isFile() || files.length >= 100 || stat.size > 10 * 1024 * 1024)
|
|
153
|
+
throw invalid('Skill import exceeds its file limit.');
|
|
154
|
+
bytes += stat.size;
|
|
155
|
+
if (bytes > 10 * 1024 * 1024)
|
|
156
|
+
throw invalid('Skill import exceeds 10 MiB.');
|
|
157
|
+
files.push({ path: relative(folder, path), content: readFileSync(path).toString('base64'), encoding: 'base64' });
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
if (lstatSync(folder).isSymbolicLink())
|
|
161
|
+
throw invalid('Choose a regular skill folder.');
|
|
162
|
+
visit(folder);
|
|
163
|
+
library.importItems(this.options.root, 'skills', [{ name: basename(folder), files }]);
|
|
164
|
+
}
|
|
165
|
+
async connection(action, params) {
|
|
166
|
+
const apps = this.options.apps;
|
|
167
|
+
if (action === 'appClients')
|
|
168
|
+
return apps.appClients();
|
|
169
|
+
if (action === 'save')
|
|
170
|
+
return apps.save(connectionInputSchema.parse(params.input));
|
|
171
|
+
const id = typeof params.id === 'string' ? params.id : '';
|
|
172
|
+
if (!apps.connections.list().some(connection => connection.id === id))
|
|
173
|
+
throw invalid('Unknown connection.');
|
|
174
|
+
if (action === 'test') {
|
|
175
|
+
const result = await apps.test(id);
|
|
176
|
+
return { ...result, error: result.error ? String(apps.connections.scrub(result.error, id)) : null,
|
|
177
|
+
tools: apps.connections.scrub(result.tools, id) };
|
|
178
|
+
}
|
|
179
|
+
if (action === 'signIn')
|
|
180
|
+
return apps.signIn(id);
|
|
181
|
+
if (action === 'signOut')
|
|
182
|
+
return apps.signOut(id);
|
|
183
|
+
if (action === 'remove')
|
|
184
|
+
return apps.remove(id);
|
|
185
|
+
throw invalid('Unknown connection action.');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface TerminalCommandCompletion {
|
|
2
|
+
name: string;
|
|
3
|
+
aliases?: string[];
|
|
4
|
+
description?: string;
|
|
5
|
+
group: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TerminalCommandHelpEntry {
|
|
8
|
+
name: string;
|
|
9
|
+
aliases?: string[];
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function formatTerminalCommandLabel(name: string, aliases?: readonly string[]): string;
|
|
13
|
+
export interface TerminalCommandHelpGroup {
|
|
14
|
+
name: string;
|
|
15
|
+
commands: TerminalCommandHelpEntry[];
|
|
16
|
+
}
|
|
17
|
+
export declare function groupTerminalCommands(commands: readonly TerminalCommandCompletion[]): TerminalCommandHelpGroup[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function formatTerminalCommandLabel(name, aliases) {
|
|
2
|
+
return aliases?.length ? `${name} (${aliases.join(', ')})` : name;
|
|
3
|
+
}
|
|
4
|
+
const GROUP_ORDER = ['Core', 'Run', 'Model', 'Session', 'Inspect', 'Harness', 'Plugins', 'Extensions', 'Prompts'];
|
|
5
|
+
export function groupTerminalCommands(commands) {
|
|
6
|
+
const groups = new Map();
|
|
7
|
+
for (const command of commands) {
|
|
8
|
+
const group = groups.get(command.group) ?? [];
|
|
9
|
+
group.push({
|
|
10
|
+
name: command.name,
|
|
11
|
+
...(command.aliases?.length ? { aliases: command.aliases } : {}),
|
|
12
|
+
description: command.description
|
|
13
|
+
});
|
|
14
|
+
groups.set(command.group, group);
|
|
15
|
+
}
|
|
16
|
+
const order = new Map(GROUP_ORDER.map((name, index) => [name, index]));
|
|
17
|
+
return [...groups]
|
|
18
|
+
.sort(([left], [right]) => (order.get(left) ?? GROUP_ORDER.length) - (order.get(right) ?? GROUP_ORDER.length))
|
|
19
|
+
.map(([name, entries]) => ({ name, commands: entries }));
|
|
20
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type RunEvent, type RunRecord } from "../sessions/contracts.js";
|
|
2
2
|
import type { ActiveRun, ApprovalDecision, QuestionAnswer } from "../sessions/registry.js";
|
|
3
|
+
import { type TerminalSubagentActivity, type TerminalSubagentProfile, type TerminalSubagentRun } from "./subagentMonitor.js";
|
|
3
4
|
export interface PolicyReply {
|
|
4
5
|
allowed: boolean;
|
|
5
6
|
reason?: string | null;
|
|
7
|
+
workspaceRoot?: string;
|
|
6
8
|
requiresApproval?: boolean;
|
|
7
9
|
rememberable?: boolean;
|
|
8
10
|
standing?: 'full' | 'switch' | 'none';
|
|
@@ -18,6 +20,12 @@ export interface ControllerHooks {
|
|
|
18
20
|
}): ActiveRun;
|
|
19
21
|
/** Whether a native tool call may run. */
|
|
20
22
|
policy(run: ActiveRun, tool: string, args: unknown): Promise<PolicyReply>;
|
|
23
|
+
/** The workspace root pinned on the run record. */
|
|
24
|
+
workspaceRoot(run: ActiveRun): string;
|
|
25
|
+
/** One-shot approval before an explicitly broad Bash command runs. */
|
|
26
|
+
workspaceEscape(run: ActiveRun, tool: string, args: unknown, reason: string): Promise<boolean>;
|
|
27
|
+
/** Whether Full Permission may bypass the filesystem boundary for this Bash command. */
|
|
28
|
+
workspaceBoundaryBypass(run: ActiveRun, tool: string, args: unknown): Promise<boolean>;
|
|
21
29
|
/** Saves a user-selected tool grant in the shared local approval store. */
|
|
22
30
|
allowToolGlobally(target: string): void;
|
|
23
31
|
/** The native conversation identity changed. */
|
|
@@ -35,6 +43,8 @@ export declare class TerminalController {
|
|
|
35
43
|
private readonly hooks;
|
|
36
44
|
private run;
|
|
37
45
|
private readonly pending;
|
|
46
|
+
private readonly subagents;
|
|
47
|
+
private subagentParentRunId;
|
|
38
48
|
private ready;
|
|
39
49
|
private disposed;
|
|
40
50
|
private dirty;
|
|
@@ -64,6 +74,10 @@ export declare class TerminalController {
|
|
|
64
74
|
disconnected(): void;
|
|
65
75
|
/** Project registry-owned questions, including grouped requests, into the terminal inbox. */
|
|
66
76
|
observe(event: RunEvent, descendant?: boolean): void;
|
|
77
|
+
/** A child event that belongs to the active terminal parent run. */
|
|
78
|
+
observeSubagent(parentRunId: string, run: RunRecord, event: RunEvent, profile?: TerminalSubagentProfile | null, runtime?: Pick<TerminalSubagentRun, 'accountLabel' | 'model' | 'effort'>): void;
|
|
79
|
+
/** Read-only child activity for the latest parent run, retained until another run begins. */
|
|
80
|
+
subagentSnapshot(): TerminalSubagentActivity[];
|
|
67
81
|
/** The socket grants this interface only to the process that owns terminal input. */
|
|
68
82
|
control(call: string, data: Record<string, any>): Promise<unknown>;
|
|
69
83
|
private requestNativeApproval;
|
|
@@ -4,12 +4,17 @@
|
|
|
4
4
|
// extension's questions about policy, approvals, and what to do next.
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
6
|
import { HarnessError } from "../core/errors.js";
|
|
7
|
+
import { runStatusSchema } from "../sessions/contracts.js";
|
|
8
|
+
import { briefTitle } from "../sessions/subagents.js";
|
|
9
|
+
import { SubagentMonitor } from "./subagentMonitor.js";
|
|
7
10
|
const READY_TIMEOUT_MS = 90_000;
|
|
8
11
|
const ACK_TIMEOUT_MS = 25_000;
|
|
9
12
|
export class TerminalController {
|
|
10
13
|
hooks;
|
|
11
14
|
run = null;
|
|
12
15
|
pending = new Map();
|
|
16
|
+
subagents = new SubagentMonitor();
|
|
17
|
+
subagentParentRunId = null;
|
|
13
18
|
ready = false;
|
|
14
19
|
disposed = false;
|
|
15
20
|
dirty = false;
|
|
@@ -48,6 +53,8 @@ export class TerminalController {
|
|
|
48
53
|
throw new Error('The native prompt did not match the reserved delivery. Inspect the terminal before continuing.');
|
|
49
54
|
const run = this.hooks.begin({ reservedRunId: delivery?.runId ?? null, nativeTurnId, text });
|
|
50
55
|
this.run = run;
|
|
56
|
+
this.subagentParentRunId = run.runId;
|
|
57
|
+
this.subagents.begin(run.runId);
|
|
51
58
|
run.onInterrupt(() => void this.commands.push({ type: 'interrupt' }));
|
|
52
59
|
if (text)
|
|
53
60
|
run.emit('message.completed', { messageId: nativeTurnId, role: 'user', text });
|
|
@@ -55,8 +62,8 @@ export class TerminalController {
|
|
|
55
62
|
this.delivery = null;
|
|
56
63
|
return run;
|
|
57
64
|
}
|
|
58
|
-
finish(outcome, message) {
|
|
59
|
-
this.run?.finish(outcome, message ? { code: 'terminal_attention', message } : undefined);
|
|
65
|
+
finish(outcome, message, reason) {
|
|
66
|
+
this.run?.finish(outcome, message ? { code: 'terminal_attention', message } : undefined, reason);
|
|
60
67
|
this.run = null;
|
|
61
68
|
this.seen.clear();
|
|
62
69
|
this.pending.clear();
|
|
@@ -126,10 +133,11 @@ export class TerminalController {
|
|
|
126
133
|
const payload = event.payload;
|
|
127
134
|
this.pending.set(String(payload.questionId), { ...payload, type: 'question', requestId: payload.questionId, runId: event.runId });
|
|
128
135
|
}
|
|
129
|
-
else if (
|
|
136
|
+
else if (event.type === 'approval.requested') {
|
|
130
137
|
const payload = event.payload;
|
|
131
|
-
if (!payload.automatic)
|
|
138
|
+
if (!payload.automatic && (descendant || payload.target === 'workspace-write')) {
|
|
132
139
|
this.pending.set(String(payload.approvalId), { ...payload, type: 'approval', requestId: payload.approvalId, runId: event.runId });
|
|
140
|
+
}
|
|
133
141
|
}
|
|
134
142
|
else if (event.type === 'approval.resolved') {
|
|
135
143
|
this.pending.delete(String(event.payload.approvalId));
|
|
@@ -138,6 +146,27 @@ export class TerminalController {
|
|
|
138
146
|
this.pending.delete(String(event.payload.questionId));
|
|
139
147
|
}
|
|
140
148
|
}
|
|
149
|
+
/** A child event that belongs to the active terminal parent run. */
|
|
150
|
+
observeSubagent(parentRunId, run, event, profile = null, runtime = {}) {
|
|
151
|
+
if (this.disposed || this.activeRun?.runId !== parentRunId || run.relation !== 'subagent')
|
|
152
|
+
return;
|
|
153
|
+
this.subagents.observe(parentRunId, {
|
|
154
|
+
id: run.id,
|
|
155
|
+
parentRunId: run.parentRunId,
|
|
156
|
+
relation: 'subagent',
|
|
157
|
+
title: run.title ?? briefTitle(run.goal),
|
|
158
|
+
goal: run.goal,
|
|
159
|
+
status: runStatusSchema.parse(run.status),
|
|
160
|
+
startedAt: run.startedAt,
|
|
161
|
+
endedAt: run.endedAt,
|
|
162
|
+
profile,
|
|
163
|
+
...runtime
|
|
164
|
+
}, event);
|
|
165
|
+
}
|
|
166
|
+
/** Read-only child activity for the latest parent run, retained until another run begins. */
|
|
167
|
+
subagentSnapshot() {
|
|
168
|
+
return this.subagentParentRunId ? this.subagents.snapshot(this.subagentParentRunId) : [];
|
|
169
|
+
}
|
|
141
170
|
/** The socket grants this interface only to the process that owns terminal input. */
|
|
142
171
|
async control(call, data) {
|
|
143
172
|
if (this.disposed)
|
|
@@ -157,6 +186,20 @@ export class TerminalController {
|
|
|
157
186
|
throw new HarnessError('invalid_request', 'An active run and tool name are required.');
|
|
158
187
|
return this.hooks.policy(run, data.tool, data.args ?? {});
|
|
159
188
|
}
|
|
189
|
+
if (call === 'terminal.workspaceBoundaryBypass') {
|
|
190
|
+
const run = this.activeRun;
|
|
191
|
+
if (!run || data.tool !== 'bash')
|
|
192
|
+
throw new HarnessError('invalid_request', 'An active run and Bash command are required.');
|
|
193
|
+
const args = data.args && typeof data.args === 'object' && !Array.isArray(data.args) ? data.args : {};
|
|
194
|
+
return { bypass: await this.hooks.workspaceBoundaryBypass(run, 'bash', args) };
|
|
195
|
+
}
|
|
196
|
+
if (call === 'terminal.workspaceEscape') {
|
|
197
|
+
const run = this.activeRun;
|
|
198
|
+
if (!run || data.tool !== 'bash' || typeof data.reason !== 'string' || !data.reason)
|
|
199
|
+
throw new HarnessError('invalid_request', 'An active run, Bash command, and outside-scope request are required.');
|
|
200
|
+
const args = data.args && typeof data.args === 'object' && !Array.isArray(data.args) ? data.args : {};
|
|
201
|
+
return { approved: await this.hooks.workspaceEscape(run, 'bash', args, data.reason) };
|
|
202
|
+
}
|
|
160
203
|
if (call === 'terminal.approval')
|
|
161
204
|
return this.requestNativeApproval(data);
|
|
162
205
|
if (call === 'terminal.answers' && this.hooks.answerQuestions) {
|
|
@@ -266,6 +309,10 @@ export class TerminalController {
|
|
|
266
309
|
}
|
|
267
310
|
this.ready = true;
|
|
268
311
|
if (typeof data.nativeSessionId === 'string' && data.nativeSessionId) {
|
|
312
|
+
if (this.nativeId !== data.nativeSessionId) {
|
|
313
|
+
this.subagents.clear();
|
|
314
|
+
this.subagentParentRunId = null;
|
|
315
|
+
}
|
|
269
316
|
this.nativeId = data.nativeSessionId;
|
|
270
317
|
this.hooks.native(data.nativeSessionId);
|
|
271
318
|
}
|
|
@@ -273,7 +320,7 @@ export class TerminalController {
|
|
|
273
320
|
}
|
|
274
321
|
if (event === 'begin') {
|
|
275
322
|
const run = this.begin(String(data.turnId ?? randomUUID()), String(data.text ?? ''));
|
|
276
|
-
return { runId: run.runId };
|
|
323
|
+
return { runId: run.runId, workspaceRoot: this.hooks.workspaceRoot(run) };
|
|
277
324
|
}
|
|
278
325
|
if (event === 'shutdown') {
|
|
279
326
|
this.ready = false;
|
|
@@ -281,7 +328,11 @@ export class TerminalController {
|
|
|
281
328
|
return {};
|
|
282
329
|
}
|
|
283
330
|
if (event === 'settled') {
|
|
284
|
-
|
|
331
|
+
// A reply that stopped at its work-slice boundary is interrupted, not completed: the record
|
|
332
|
+
// says `slice_complete` (or `stalled`) so a host can continue it (PROP-0041).
|
|
333
|
+
const reason = typeof data.reason === 'string' ? data.reason : undefined;
|
|
334
|
+
const outcome = data.error ? 'failed' : data.aborted || reason ? 'interrupted' : 'completed';
|
|
335
|
+
this.finish(outcome, data.error ? String(data.error) : undefined, reason);
|
|
285
336
|
return {};
|
|
286
337
|
}
|
|
287
338
|
const run = this.run;
|
|
@@ -301,8 +352,10 @@ export class TerminalController {
|
|
|
301
352
|
if (existing)
|
|
302
353
|
return event === 'approval' ? { decision: await existing } : existing;
|
|
303
354
|
if (event === 'question') {
|
|
304
|
-
const
|
|
305
|
-
|
|
355
|
+
const summary = typeof data.summary === 'string' && data.summary.trim() ? data.summary.trim() : undefined;
|
|
356
|
+
const sketchMode = data.uiSketch === undefined ? {} : { uiSketch: data.uiSketch };
|
|
357
|
+
const response = run.requestAnswer({ requestId: key, ...(summary ? { summary } : {}), ...sketchMode, question: String(data.question ?? ''), options: data.options ?? [], multiSelect: !!data.multiSelect, allowText: true });
|
|
358
|
+
this.pending.set(key, { type: 'question', requestId: key, ...(summary ? { summary } : {}), ...sketchMode, question: String(data.question ?? ''), options: data.options ?? [], multiSelect: !!data.multiSelect });
|
|
306
359
|
this.seen.set(key, response);
|
|
307
360
|
return response.finally(() => this.pending.delete(key));
|
|
308
361
|
}
|
|
@@ -324,6 +377,10 @@ export class TerminalController {
|
|
|
324
377
|
}
|
|
325
378
|
if (event === 'usage')
|
|
326
379
|
run.emit('usage.updated', data);
|
|
380
|
+
else if (event === 'checklist')
|
|
381
|
+
run.emit('checklist.updated', { items: data.items });
|
|
382
|
+
else if (event === 'checkpoint')
|
|
383
|
+
run.emit('checkpoint', data);
|
|
327
384
|
else if (event === 'file')
|
|
328
385
|
run.emit('file.changed', data);
|
|
329
386
|
else if (event === 'message')
|
|
@@ -347,5 +404,7 @@ export class TerminalController {
|
|
|
347
404
|
this.delivery?.reject(new Error('The terminal stopped before prompt acknowledgement.'));
|
|
348
405
|
this.delivery = null;
|
|
349
406
|
this.finish('interrupted');
|
|
407
|
+
this.subagents.clear();
|
|
408
|
+
this.subagentParentRunId = null;
|
|
350
409
|
}
|
|
351
410
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type TerminalSubagentActivity } from "../subagentMonitor.js";
|
|
2
|
+
export type SubagentTone = 'success' | 'warning' | 'error' | 'muted' | 'accent';
|
|
3
|
+
/** One roster row: status mark, profile, session name, checklist progress, in that order. */
|
|
4
|
+
export interface SubagentRowView {
|
|
5
|
+
id: string;
|
|
6
|
+
mark: string;
|
|
7
|
+
tone: SubagentTone;
|
|
8
|
+
/** The profile name, or `subagent` for a copy of the parent. */
|
|
9
|
+
profile: string;
|
|
10
|
+
/** The profile's colour name (PROP-0029); null for a copy of the parent. */
|
|
11
|
+
color: string | null;
|
|
12
|
+
title: string;
|
|
13
|
+
/** `n of m tasks`, or null when the child wrote no checklist. */
|
|
14
|
+
tasks: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface SubagentRosterView {
|
|
17
|
+
rows: SubagentRowView[];
|
|
18
|
+
/** `+N more · /subagents` when rows were left out. */
|
|
19
|
+
overflow: string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function subagentTasks(checklist: TerminalSubagentActivity['checklist']): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* The compact live roster shown under the status line: active children first, at most three
|
|
24
|
+
* rows. The session name is cut first when the terminal is narrow.
|
|
25
|
+
*/
|
|
26
|
+
export declare function subagentRoster(items: readonly TerminalSubagentActivity[], width: number, frame?: string): SubagentRosterView | null;
|
|
27
|
+
/** A row as plain text, the way it reads on screen. */
|
|
28
|
+
export declare function subagentRowText(row: SubagentRowView): string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { stripTerminalSequences, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
2
|
+
import { ACTIVE_SUBAGENT_STATUSES } from "../subagentMonitor.js";
|
|
3
|
+
import { getWorkingSpinnerFrames } from "../tui/workingStatus.js";
|
|
4
|
+
const MAX_VISIBLE_ROWS = 3;
|
|
5
|
+
const GAP = ' ';
|
|
6
|
+
/** Cuts plain text to a width; the cut adds no styling codes. */
|
|
7
|
+
function cut(text, width) {
|
|
8
|
+
return stripTerminalSequences(truncateToWidth(text, width, '…'));
|
|
9
|
+
}
|
|
10
|
+
function safe(text) {
|
|
11
|
+
return stripTerminalSequences(text).replace(/[\x00-\x1f\x7f]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
12
|
+
}
|
|
13
|
+
function mark(status, frame) {
|
|
14
|
+
switch (status) {
|
|
15
|
+
case 'completed': return { mark: '✓', tone: 'success' };
|
|
16
|
+
case 'failed': return { mark: '✕', tone: 'error' };
|
|
17
|
+
case 'interrupted': return { mark: '!', tone: 'muted' };
|
|
18
|
+
case 'queued': return { mark: '○', tone: 'muted' };
|
|
19
|
+
case 'waiting_for_approval':
|
|
20
|
+
case 'waiting_for_input': return { mark: '●', tone: 'accent' };
|
|
21
|
+
case 'running': return { mark: frame, tone: 'warning' };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function subagentTasks(checklist) {
|
|
25
|
+
return checklist && checklist.total > 0 ? `${checklist.done} of ${checklist.total} tasks` : null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The compact live roster shown under the status line: active children first, at most three
|
|
29
|
+
* rows. The session name is cut first when the terminal is narrow.
|
|
30
|
+
*/
|
|
31
|
+
export function subagentRoster(items, width, frame = getWorkingSpinnerFrames()[0] ?? '') {
|
|
32
|
+
if (!items.length || width < 1)
|
|
33
|
+
return null;
|
|
34
|
+
const ordered = [...items].sort((left, right) => {
|
|
35
|
+
const active = Number(ACTIVE_SUBAGENT_STATUSES.has(right.status)) - Number(ACTIVE_SUBAGENT_STATUSES.has(left.status));
|
|
36
|
+
return active || left.startedAt.localeCompare(right.startedAt) || left.id.localeCompare(right.id);
|
|
37
|
+
});
|
|
38
|
+
const rows = ordered.slice(0, MAX_VISIBLE_ROWS).map((item) => {
|
|
39
|
+
const status = mark(item.status, frame);
|
|
40
|
+
const profile = cut(safe(item.profile?.name ?? '') || 'subagent', Math.max(1, Math.min(24, width - 2)));
|
|
41
|
+
const tasks = subagentTasks(item.checklist);
|
|
42
|
+
const used = 2 + visibleWidth(profile) + GAP.length + (tasks ? GAP.length + tasks.length : 0);
|
|
43
|
+
const title = cut(safe(item.title) || 'Subagent', Math.max(1, width - used));
|
|
44
|
+
return { id: item.id, mark: status.mark, tone: status.tone, profile, color: item.profile?.color ?? null, title, tasks };
|
|
45
|
+
});
|
|
46
|
+
const overflow = ordered.length - rows.length;
|
|
47
|
+
return { rows, overflow: overflow > 0 ? cut(`+${overflow} more · /subagents`, width) : null };
|
|
48
|
+
}
|
|
49
|
+
/** A row as plain text, the way it reads on screen. */
|
|
50
|
+
export function subagentRowText(row) {
|
|
51
|
+
return [`${row.mark} ${row.profile}`, row.title, row.tasks].filter(Boolean).join(GAP);
|
|
52
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface OpenTuiTheme {
|
|
2
|
+
background: string;
|
|
3
|
+
panel: string;
|
|
4
|
+
/** The translucent layer that dims the console behind a centered page dialog. */
|
|
5
|
+
scrim: string;
|
|
6
|
+
border: string;
|
|
7
|
+
borderFocus: string;
|
|
8
|
+
text: string;
|
|
9
|
+
muted: string;
|
|
10
|
+
dim: string;
|
|
11
|
+
/** The shared selected-row field and its two text tones. */
|
|
12
|
+
selectionBg: string;
|
|
13
|
+
selectionText: string;
|
|
14
|
+
selectionMuted: string;
|
|
15
|
+
questionSelectedBg: string;
|
|
16
|
+
questionActionBg: string;
|
|
17
|
+
questionActionText: string;
|
|
18
|
+
primary: string;
|
|
19
|
+
accent: string;
|
|
20
|
+
success: string;
|
|
21
|
+
warning: string;
|
|
22
|
+
error: string;
|
|
23
|
+
/** The composer's Bash mode marker and rules; the command body keeps the normal text color. */
|
|
24
|
+
bashMode: string;
|
|
25
|
+
/** The composer's rules while plan mode is on. */
|
|
26
|
+
planMode: string;
|
|
27
|
+
/** Image and pasted-text labels in the composer and in sent messages. */
|
|
28
|
+
attachment: string;
|
|
29
|
+
purple: string;
|
|
30
|
+
cyan: string;
|
|
31
|
+
}
|
|
32
|
+
export declare const opentuiTheme: OpenTuiTheme;
|
|
33
|
+
/** The colour of an agent profile's name (PROP-0029); an unknown name falls back to the accent. */
|
|
34
|
+
export declare function profileColor(theme: OpenTuiTheme, name: string): string;
|
|
35
|
+
export declare function blendHexColor(from: string, to: string, amount: number): string;
|
|
36
|
+
export declare function composerRuleColor(theme: OpenTuiTheme, mode: {
|
|
37
|
+
bash: boolean;
|
|
38
|
+
plan: boolean;
|
|
39
|
+
overlay: boolean;
|
|
40
|
+
}): string;
|
|
41
|
+
export declare function noticeColor(theme: OpenTuiTheme, type: string): string;
|
|
42
|
+
export declare function toolColor(theme: OpenTuiTheme, status: 'running' | 'ok' | 'error' | 'pending'): string;
|
|
43
|
+
export declare function thinkingLevelColor(theme: OpenTuiTheme, level: string): string;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// OpenCode's terminal palette, carried by the Harness's own product name.
|
|
2
|
+
import { SELECTION_COLORS } from "../tui/selection.js";
|
|
3
|
+
export const opentuiTheme = {
|
|
4
|
+
background: '#0b0b0d',
|
|
5
|
+
panel: '#111114',
|
|
6
|
+
scrim: '#0b0b0d99',
|
|
7
|
+
border: '#2a2a31',
|
|
8
|
+
borderFocus: '#3a3a44',
|
|
9
|
+
text: '#e6e6e9',
|
|
10
|
+
muted: '#8b8b95',
|
|
11
|
+
dim: '#5c5c66',
|
|
12
|
+
selectionBg: SELECTION_COLORS.dark.background,
|
|
13
|
+
selectionText: SELECTION_COLORS.dark.text,
|
|
14
|
+
selectionMuted: SELECTION_COLORS.dark.muted,
|
|
15
|
+
questionSelectedBg: '#193322',
|
|
16
|
+
questionActionBg: '#35754a',
|
|
17
|
+
questionActionText: '#f7fff8',
|
|
18
|
+
primary: '#fab283',
|
|
19
|
+
accent: '#8ab4f8',
|
|
20
|
+
success: '#a6da95',
|
|
21
|
+
warning: '#eed49f',
|
|
22
|
+
error: '#ed8796',
|
|
23
|
+
bashMode: '#00ff88',
|
|
24
|
+
planMode: '#8ab4f8',
|
|
25
|
+
attachment: '#5ea1ff',
|
|
26
|
+
purple: '#c6a0f6',
|
|
27
|
+
cyan: '#8bd5ca'
|
|
28
|
+
};
|
|
29
|
+
/** The colour of an agent profile's name (PROP-0029); an unknown name falls back to the accent. */
|
|
30
|
+
export function profileColor(theme, name) {
|
|
31
|
+
switch (name) {
|
|
32
|
+
case 'blue': return theme.accent;
|
|
33
|
+
case 'green': return theme.success;
|
|
34
|
+
case 'yellow': return theme.warning;
|
|
35
|
+
case 'red': return theme.error;
|
|
36
|
+
case 'magenta': return '#f5bde6';
|
|
37
|
+
case 'cyan': return theme.cyan;
|
|
38
|
+
case 'orange': return theme.primary;
|
|
39
|
+
case 'purple': return theme.purple;
|
|
40
|
+
default: return theme.accent;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function blendHexColor(from, to, amount) {
|
|
44
|
+
const start = /^#([\da-f]{6})$/iu.exec(from)?.[1];
|
|
45
|
+
const end = /^#([\da-f]{6})$/iu.exec(to)?.[1];
|
|
46
|
+
if (!start || !end)
|
|
47
|
+
return from;
|
|
48
|
+
const weight = Number.isFinite(amount) ? Math.max(0, Math.min(1, amount)) : 0;
|
|
49
|
+
const channels = [0, 2, 4].map(offset => {
|
|
50
|
+
const first = Number.parseInt(start.slice(offset, offset + 2), 16);
|
|
51
|
+
const second = Number.parseInt(end.slice(offset, offset + 2), 16);
|
|
52
|
+
return Math.round(first + (second - first) * weight).toString(16).padStart(2, '0');
|
|
53
|
+
});
|
|
54
|
+
return `#${channels.join('')}`;
|
|
55
|
+
}
|
|
56
|
+
export function composerRuleColor(theme, mode) {
|
|
57
|
+
if (mode.bash)
|
|
58
|
+
return theme.bashMode;
|
|
59
|
+
if (mode.plan)
|
|
60
|
+
return theme.planMode;
|
|
61
|
+
return mode.overlay ? theme.border : theme.dim;
|
|
62
|
+
}
|
|
63
|
+
export function noticeColor(theme, type) {
|
|
64
|
+
if (type === 'error')
|
|
65
|
+
return theme.error;
|
|
66
|
+
if (type === 'warning')
|
|
67
|
+
return theme.warning;
|
|
68
|
+
return theme.muted;
|
|
69
|
+
}
|
|
70
|
+
export function toolColor(theme, status) {
|
|
71
|
+
if (status === 'error')
|
|
72
|
+
return theme.error;
|
|
73
|
+
if (status === 'running')
|
|
74
|
+
return theme.warning;
|
|
75
|
+
if (status === 'pending')
|
|
76
|
+
return theme.dim;
|
|
77
|
+
return theme.success;
|
|
78
|
+
}
|
|
79
|
+
export function thinkingLevelColor(theme, level) {
|
|
80
|
+
if (level === 'off')
|
|
81
|
+
return theme.dim;
|
|
82
|
+
if (level === 'minimal' || level === 'low')
|
|
83
|
+
return theme.muted;
|
|
84
|
+
if (level === 'medium')
|
|
85
|
+
return theme.accent;
|
|
86
|
+
return theme.primary;
|
|
87
|
+
}
|