@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,370 @@
|
|
|
1
|
+
import { matchesKey, stripTerminalSequences, truncateToWidth, visibleWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
2
|
+
const PERIODS = [
|
|
3
|
+
{ key: '1', period: 'today', label: 'today' },
|
|
4
|
+
{ key: '2', period: '7d', label: '7d' },
|
|
5
|
+
{ key: '3', period: '30d', label: '30d' },
|
|
6
|
+
{ key: '0', period: 'all', label: 'all time' }
|
|
7
|
+
];
|
|
8
|
+
const SCOPES = [
|
|
9
|
+
{ key: 'c', scope: 'account', label: 'account' },
|
|
10
|
+
{ key: 's', scope: 'session', label: 'session' },
|
|
11
|
+
{ key: 'p', scope: 'project', label: 'project' },
|
|
12
|
+
{ key: 'a', scope: 'all', label: 'all accounts' }
|
|
13
|
+
];
|
|
14
|
+
const BARS = '▁▂▃▄▅▆▇█';
|
|
15
|
+
function clean(value) {
|
|
16
|
+
return stripTerminalSequences(value).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, ' ').replace(/\t/g, ' ');
|
|
17
|
+
}
|
|
18
|
+
function oneLine(value) {
|
|
19
|
+
return clean(value).replace(/\s+/g, ' ').trim();
|
|
20
|
+
}
|
|
21
|
+
function count(value) {
|
|
22
|
+
if (!Number.isFinite(value))
|
|
23
|
+
return '0';
|
|
24
|
+
if (value >= 1_000_000_000)
|
|
25
|
+
return `${(value / 1_000_000_000).toFixed(1)}B`;
|
|
26
|
+
if (value >= 1_000_000)
|
|
27
|
+
return `${(value / 1_000_000).toFixed(1)}M`;
|
|
28
|
+
if (value >= 1_000)
|
|
29
|
+
return `${(value / 1_000).toFixed(1)}K`;
|
|
30
|
+
return String(Math.round(value));
|
|
31
|
+
}
|
|
32
|
+
function money(value) {
|
|
33
|
+
if (value === null)
|
|
34
|
+
return '—';
|
|
35
|
+
return value >= 1 ? `$${value.toFixed(2)}` : `$${value.toFixed(3)}`;
|
|
36
|
+
}
|
|
37
|
+
function duration(milliseconds) {
|
|
38
|
+
if (milliseconds === null)
|
|
39
|
+
return '—';
|
|
40
|
+
if (milliseconds < 1_000)
|
|
41
|
+
return `${Math.round(milliseconds)}ms`;
|
|
42
|
+
return `${(milliseconds / 1_000).toFixed(1)}s`;
|
|
43
|
+
}
|
|
44
|
+
function until(iso, now = Date.now()) {
|
|
45
|
+
if (!iso)
|
|
46
|
+
return '';
|
|
47
|
+
const at = Date.parse(iso);
|
|
48
|
+
if (Number.isNaN(at))
|
|
49
|
+
return '';
|
|
50
|
+
const delta = at - now;
|
|
51
|
+
if (delta <= 0)
|
|
52
|
+
return 'reset due';
|
|
53
|
+
const minutes = Math.round(delta / 60_000);
|
|
54
|
+
if (minutes < 60)
|
|
55
|
+
return `resets in ${minutes}m`;
|
|
56
|
+
const hours = Math.floor(minutes / 60);
|
|
57
|
+
if (hours < 48)
|
|
58
|
+
return `resets in ${hours}h ${minutes % 60}m`;
|
|
59
|
+
return `resets in ${Math.floor(hours / 24)}d`;
|
|
60
|
+
}
|
|
61
|
+
function ago(iso) {
|
|
62
|
+
const at = Date.parse(iso);
|
|
63
|
+
if (Number.isNaN(at))
|
|
64
|
+
return 'unknown time';
|
|
65
|
+
const minutes = Math.max(0, Math.round((Date.now() - at) / 60_000));
|
|
66
|
+
if (minutes < 1)
|
|
67
|
+
return 'just now';
|
|
68
|
+
if (minutes < 60)
|
|
69
|
+
return `${minutes}m ago`;
|
|
70
|
+
const hours = Math.round(minutes / 60);
|
|
71
|
+
return hours < 48 ? `${hours}h ago` : `${Math.round(hours / 24)}d ago`;
|
|
72
|
+
}
|
|
73
|
+
function usageLine(totals) {
|
|
74
|
+
const parts = [
|
|
75
|
+
`${count(totals.inputTokens)} in`,
|
|
76
|
+
`${count(totals.cacheReadTokens)} cached`,
|
|
77
|
+
`${count(totals.outputTokens)} out`,
|
|
78
|
+
`${count(totals.requests)} requests`,
|
|
79
|
+
money(totals.costUsd)
|
|
80
|
+
];
|
|
81
|
+
if (totals.unpricedRequests)
|
|
82
|
+
parts.push(`${totals.unpricedRequests} unpriced`);
|
|
83
|
+
return parts.join(' · ');
|
|
84
|
+
}
|
|
85
|
+
function share(value, total) {
|
|
86
|
+
if (!total)
|
|
87
|
+
return '0%';
|
|
88
|
+
return `${Math.round((value / total) * 100)}%`;
|
|
89
|
+
}
|
|
90
|
+
function tokensOf(row) {
|
|
91
|
+
return row.inputTokens + row.cacheReadTokens + row.cacheWriteTokens + row.outputTokens;
|
|
92
|
+
}
|
|
93
|
+
/** A quota-left bar: green with room, amber when it narrows, red when it runs out. */
|
|
94
|
+
function barTone(percent, rejected = false) {
|
|
95
|
+
if (rejected)
|
|
96
|
+
return 'error';
|
|
97
|
+
if (percent === null || percent === undefined)
|
|
98
|
+
return 'dim';
|
|
99
|
+
if (percent >= 50)
|
|
100
|
+
return 'success';
|
|
101
|
+
if (percent >= 20)
|
|
102
|
+
return 'warning';
|
|
103
|
+
return 'error';
|
|
104
|
+
}
|
|
105
|
+
function bar(percent, width, theme, rejected = false) {
|
|
106
|
+
const track = '░'.repeat(width);
|
|
107
|
+
if (percent === null || percent === undefined)
|
|
108
|
+
return `[${theme.fg('dim', track)}]`;
|
|
109
|
+
const filled = Math.max(0, Math.min(width, Math.round((Math.max(0, Math.min(100, percent)) / 100) * width)));
|
|
110
|
+
return `[${theme.fg(barTone(percent, rejected), '█'.repeat(filled))}${theme.fg('dim', '░'.repeat(Math.max(0, width - filled)))}]`;
|
|
111
|
+
}
|
|
112
|
+
/** A label on the left and a quiet value on the right of the same row. */
|
|
113
|
+
function spread(left, right, width) {
|
|
114
|
+
if (!right)
|
|
115
|
+
return left;
|
|
116
|
+
const rightText = truncateToWidth(right, Math.max(0, width - visibleWidth(left) - 2), '');
|
|
117
|
+
return left + ' '.repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(rightText))) + rightText;
|
|
118
|
+
}
|
|
119
|
+
export class UsagePage {
|
|
120
|
+
options;
|
|
121
|
+
presentation = 'centered';
|
|
122
|
+
focused = false;
|
|
123
|
+
tab;
|
|
124
|
+
period = '7d';
|
|
125
|
+
scope = 'account';
|
|
126
|
+
summary;
|
|
127
|
+
limitsSummary;
|
|
128
|
+
limits;
|
|
129
|
+
busy = false;
|
|
130
|
+
error;
|
|
131
|
+
refreshLimits;
|
|
132
|
+
offset = 0;
|
|
133
|
+
scrollRows = 1;
|
|
134
|
+
maxOffset = 0;
|
|
135
|
+
disposed = false;
|
|
136
|
+
constructor(options) {
|
|
137
|
+
this.options = options;
|
|
138
|
+
this.tab = options.initialTab ?? 'analysis';
|
|
139
|
+
this.period = options.initialPeriod ?? '7d';
|
|
140
|
+
this.refreshLimits = this.tab === 'limits';
|
|
141
|
+
}
|
|
142
|
+
tabs() {
|
|
143
|
+
return [{ id: 'analysis', title: 'Analysis' }, { id: 'limits', title: 'Limits' }];
|
|
144
|
+
}
|
|
145
|
+
activeTab() {
|
|
146
|
+
return this.tab;
|
|
147
|
+
}
|
|
148
|
+
selectTab(id) {
|
|
149
|
+
if ((id === 'analysis' || id === 'limits') && id !== this.tab) {
|
|
150
|
+
this.tab = id;
|
|
151
|
+
this.offset = 0;
|
|
152
|
+
this.options.changed();
|
|
153
|
+
if (id === 'limits' && !this.limits) {
|
|
154
|
+
this.refreshLimits = true;
|
|
155
|
+
void this.load();
|
|
156
|
+
}
|
|
157
|
+
else if (id === 'analysis' && !this.summary)
|
|
158
|
+
void this.load();
|
|
159
|
+
else if (id === 'limits' && this.refreshLimits)
|
|
160
|
+
void this.load();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
hint() {
|
|
164
|
+
return this.tab === 'analysis'
|
|
165
|
+
? '↑/↓ scroll · PgUp/PgDn page · 1 today 2 7d 3 30d 0 all · c account s session p project a all accounts · r refresh · Tab limits · Esc close'
|
|
166
|
+
: '↑/↓ scroll · PgUp/PgDn page · r read provider limits · Tab analysis · Esc close';
|
|
167
|
+
}
|
|
168
|
+
activate() {
|
|
169
|
+
if (!this.summary && !this.limits)
|
|
170
|
+
void this.load();
|
|
171
|
+
}
|
|
172
|
+
dispose() {
|
|
173
|
+
this.disposed = true;
|
|
174
|
+
}
|
|
175
|
+
async load() {
|
|
176
|
+
if (this.busy || this.disposed)
|
|
177
|
+
return;
|
|
178
|
+
this.busy = true;
|
|
179
|
+
this.error = undefined;
|
|
180
|
+
this.options.changed();
|
|
181
|
+
try {
|
|
182
|
+
if (this.tab === 'analysis') {
|
|
183
|
+
this.summary = await this.options.call('terminal.usage.summary', {
|
|
184
|
+
scope: this.scope,
|
|
185
|
+
period: this.period,
|
|
186
|
+
timezoneOffsetMinutes: -new Date().getTimezoneOffset()
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// The provider's windows and this app's own ledger answer different questions; show both.
|
|
191
|
+
const [limits, summary] = await Promise.all([
|
|
192
|
+
this.options.call('terminal.usage.limits', { refresh: this.refreshLimits }),
|
|
193
|
+
this.options.call('terminal.usage.summary', {
|
|
194
|
+
scope: 'account',
|
|
195
|
+
period: '7d',
|
|
196
|
+
timezoneOffsetMinutes: -new Date().getTimezoneOffset()
|
|
197
|
+
})
|
|
198
|
+
]);
|
|
199
|
+
this.limits = limits;
|
|
200
|
+
this.limitsSummary = summary;
|
|
201
|
+
this.refreshLimits = false;
|
|
202
|
+
}
|
|
203
|
+
this.offset = 0;
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
this.error = error instanceof Error ? error.message : 'Usage could not be read.';
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
this.busy = false;
|
|
210
|
+
if (!this.disposed)
|
|
211
|
+
this.options.changed();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
handleInput(data) {
|
|
215
|
+
const page = Math.max(1, this.scrollRows - 1);
|
|
216
|
+
if (matchesKey(data, 'up'))
|
|
217
|
+
this.offset = Math.max(0, this.offset - 1);
|
|
218
|
+
else if (matchesKey(data, 'down'))
|
|
219
|
+
this.offset = Math.min(this.maxOffset, this.offset + 1);
|
|
220
|
+
else if (matchesKey(data, 'pageUp'))
|
|
221
|
+
this.offset = Math.max(0, this.offset - page);
|
|
222
|
+
else if (matchesKey(data, 'pageDown'))
|
|
223
|
+
this.offset = Math.min(this.maxOffset, this.offset + page);
|
|
224
|
+
else if (matchesKey(data, 'home'))
|
|
225
|
+
this.offset = 0;
|
|
226
|
+
else if (matchesKey(data, 'end'))
|
|
227
|
+
this.offset = this.maxOffset;
|
|
228
|
+
else if (data === 'r') {
|
|
229
|
+
if (this.tab === 'limits')
|
|
230
|
+
this.refreshLimits = true;
|
|
231
|
+
void this.load();
|
|
232
|
+
}
|
|
233
|
+
else if (this.tab === 'analysis' && PERIODS.some((entry) => entry.key === data)) {
|
|
234
|
+
this.period = PERIODS.find((entry) => entry.key === data).period;
|
|
235
|
+
void this.load();
|
|
236
|
+
}
|
|
237
|
+
else if (this.tab === 'analysis' && SCOPES.some((entry) => entry.key === data)) {
|
|
238
|
+
this.scope = SCOPES.find((entry) => entry.key === data).scope;
|
|
239
|
+
void this.load();
|
|
240
|
+
}
|
|
241
|
+
else
|
|
242
|
+
return false;
|
|
243
|
+
this.options.changed();
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
analysisContent(width, theme) {
|
|
247
|
+
const summary = this.summary;
|
|
248
|
+
const header = [];
|
|
249
|
+
if (!summary) {
|
|
250
|
+
header.push(theme.fg(this.error ? 'error' : 'dim', this.error ?? (this.busy ? 'Reading usage…' : 'No usage to show.')));
|
|
251
|
+
return { header, body: [] };
|
|
252
|
+
}
|
|
253
|
+
const scope = SCOPES.find((entry) => entry.scope === summary.scope)?.label ?? summary.scope;
|
|
254
|
+
const period = PERIODS.find((entry) => entry.period === summary.period)?.label ?? summary.period;
|
|
255
|
+
header.push(theme.bold(theme.fg('accent', `${scope} · ${period}`)));
|
|
256
|
+
header.push(...wrapTextWithAnsi(theme.fg('muted', usageLine(summary.totals)), width));
|
|
257
|
+
const details = [
|
|
258
|
+
summary.totals.cacheHitPercent === null ? null : `${summary.totals.cacheHitPercent}% cache hits`,
|
|
259
|
+
`TTFT ${duration(summary.totals.avgTtftMs)}`,
|
|
260
|
+
`request ${duration(summary.totals.avgDurationMs)}`,
|
|
261
|
+
summary.totals.estimatedCostUsd ? `${money(summary.totals.estimatedCostUsd)} estimated` : null
|
|
262
|
+
].filter((entry) => Boolean(entry));
|
|
263
|
+
if (details.length)
|
|
264
|
+
header.push(...wrapTextWithAnsi(theme.fg('dim', details.join(' · ')), width));
|
|
265
|
+
if (this.error)
|
|
266
|
+
header.push(...wrapTextWithAnsi(theme.fg('error', this.error), width));
|
|
267
|
+
const body = [''];
|
|
268
|
+
const total = tokensOf(summary.totals);
|
|
269
|
+
const gap = () => {
|
|
270
|
+
if (body.length && body[body.length - 1] !== '')
|
|
271
|
+
body.push('');
|
|
272
|
+
};
|
|
273
|
+
if (summary.byDay.length) {
|
|
274
|
+
const max = Math.max(...summary.byDay.map((day) => day.inputTokens + day.cacheReadTokens + day.outputTokens), 1);
|
|
275
|
+
const spark = summary.byDay.slice(-14).map((day) => BARS[Math.min(BARS.length - 1, Math.round(((day.inputTokens + day.cacheReadTokens + day.outputTokens) / max) * (BARS.length - 1)))]).join('');
|
|
276
|
+
body.push(theme.fg('accent', spark));
|
|
277
|
+
for (const day of summary.byDay.slice(-3).reverse()) {
|
|
278
|
+
body.push(...wrapTextWithAnsi(theme.fg('muted', ` ${day.day} ${count(day.inputTokens + day.cacheReadTokens + day.outputTokens)} tokens · ${count(day.requests)} requests · ${money(day.costUsd)}`), width));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const section = (rows) => {
|
|
282
|
+
if (!rows.length)
|
|
283
|
+
return;
|
|
284
|
+
gap();
|
|
285
|
+
for (const row of rows) {
|
|
286
|
+
const line = ` ${oneLine(row.label)} ${share(tokensOf(row), total)} ${count(tokensOf(row))} tokens · ${count(row.requests)} requests · ${money(row.costUsd)}`;
|
|
287
|
+
body.push(...wrapTextWithAnsi(theme.fg('muted', line), width));
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
section(summary.byModel);
|
|
291
|
+
section(summary.byProvider);
|
|
292
|
+
section(summary.byAccount);
|
|
293
|
+
section(summary.bySession);
|
|
294
|
+
if (body.length === 1)
|
|
295
|
+
body.push(theme.fg('dim', 'No requests were recorded in this period.'));
|
|
296
|
+
while (body.length > 1 && body[body.length - 1] === '')
|
|
297
|
+
body.pop();
|
|
298
|
+
return { header, body };
|
|
299
|
+
}
|
|
300
|
+
limitsContent(width, theme) {
|
|
301
|
+
const report = this.limits;
|
|
302
|
+
const header = [];
|
|
303
|
+
if (!report) {
|
|
304
|
+
header.push(theme.fg(this.error ? 'error' : 'dim', this.error ?? (this.busy ? 'Reading provider limits…' : 'No limit readings yet.')));
|
|
305
|
+
return { header, body: [] };
|
|
306
|
+
}
|
|
307
|
+
if (this.error)
|
|
308
|
+
header.push(...wrapTextWithAnsi(theme.fg('error', this.error), width));
|
|
309
|
+
const body = [];
|
|
310
|
+
const barWidth = Math.max(8, Math.min(16, Math.floor(width / 6)));
|
|
311
|
+
for (const account of report.accounts) {
|
|
312
|
+
if (body.length)
|
|
313
|
+
body.push('');
|
|
314
|
+
body.push(theme.bold(oneLine(account.label)) + theme.fg('dim', `${account.plan ? ` · ${oneLine(account.plan)}` : ''} · ${account.health}`));
|
|
315
|
+
if (!account.providers.length) {
|
|
316
|
+
body.push(theme.fg('dim', ' No limit readings.'));
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
for (const provider of account.providers) {
|
|
320
|
+
body.push('');
|
|
321
|
+
const source = provider.source === 'provider' ? '' : provider.source;
|
|
322
|
+
const meta = [source, ago(provider.observedAt)].filter(Boolean).join(' · ');
|
|
323
|
+
const tone = provider.source === 'unavailable' ? 'warning' : 'muted';
|
|
324
|
+
body.push(...wrapTextWithAnsi(theme.fg(tone, ` ${provider.provider}${meta ? ` · ${meta}` : ''}${provider.reason ? ` · ${oneLine(provider.reason)}` : ''}`), width));
|
|
325
|
+
for (const limit of provider.limits) {
|
|
326
|
+
body.push('');
|
|
327
|
+
const reset = until(limit.resetsAt);
|
|
328
|
+
body.push(...wrapTextWithAnsi(spread(theme.bold(` ${oneLine(limit.label)}`), theme.fg('dim', reset), width), width));
|
|
329
|
+
const used = limit.usedPercent ?? null;
|
|
330
|
+
const remainingPercent = limit.remainingPercent
|
|
331
|
+
?? (used === null
|
|
332
|
+
? limit.remaining !== null && limit.remaining !== undefined && limit.limit !== null && limit.limit !== undefined && limit.limit > 0
|
|
333
|
+
? Math.max(0, Math.min(100, (limit.remaining / limit.limit) * 100))
|
|
334
|
+
: null
|
|
335
|
+
: Math.max(0, Math.min(100, 100 - used)));
|
|
336
|
+
const rejected = limit.status === 'rejected';
|
|
337
|
+
const tone = barTone(remainingPercent, rejected);
|
|
338
|
+
const absolute = limit.remaining !== null && limit.remaining !== undefined
|
|
339
|
+
? `${limit.unit === 'usd' ? money(limit.remaining) : limit.unit === 'currency' ? `${limit.remaining.toFixed(2)} ${limit.detail ?? ''}`.trim() : count(limit.remaining)} left`
|
|
340
|
+
: limit.limit !== null && limit.limit !== undefined
|
|
341
|
+
? `limit ${count(limit.limit)}${limit.unit && limit.unit !== 'percent' ? ` ${limit.unit}` : ''}`
|
|
342
|
+
: '';
|
|
343
|
+
const value = remainingPercent === null ? absolute || '—' : `${Math.round(remainingPercent)}% left`;
|
|
344
|
+
const suffix = remainingPercent !== null && absolute ? ` ${theme.fg('dim', absolute)}` : '';
|
|
345
|
+
body.push(...wrapTextWithAnsi(` ${bar(remainingPercent, barWidth, theme, rejected)} ${theme.fg(tone, value)}${suffix}`, width));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (!report.accounts.length)
|
|
350
|
+
body.push(theme.fg('dim', 'No accounts.'));
|
|
351
|
+
const local = this.limitsSummary?.totals;
|
|
352
|
+
if (local) {
|
|
353
|
+
if (body.length)
|
|
354
|
+
body.push('');
|
|
355
|
+
body.push(...wrapTextWithAnsi(theme.fg('dim', `7d · ${count(local.inputTokens + local.cacheReadTokens + local.outputTokens)} tokens · ${count(local.requests)} requests · ${money(local.costUsd)}`), width));
|
|
356
|
+
}
|
|
357
|
+
return { header, body };
|
|
358
|
+
}
|
|
359
|
+
render(width, rows) {
|
|
360
|
+
if (width < 1 || rows < 1)
|
|
361
|
+
return [];
|
|
362
|
+
const theme = this.options.theme();
|
|
363
|
+
const { header, body } = this.tab === 'analysis' ? this.analysisContent(width, theme) : this.limitsContent(width, theme);
|
|
364
|
+
const bodyRows = Math.max(0, rows - header.length);
|
|
365
|
+
this.scrollRows = Math.max(1, bodyRows);
|
|
366
|
+
this.maxOffset = Math.max(0, body.length - bodyRows);
|
|
367
|
+
this.offset = Math.max(0, Math.min(this.offset, this.maxOffset));
|
|
368
|
+
return [...header, ...body.slice(this.offset, this.offset + bodyRows)].slice(0, rows).map((line) => truncateToWidth(line, width, ''));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Worktrees page (`/worktree`): the managed worktrees of the repository this session works in.
|
|
3
|
+
* A person makes, switches to, branches, hands off, removes, restores and prunes them; switching
|
|
4
|
+
* restarts the terminal in the chosen folder as a new conversation (PROP-0016).
|
|
5
|
+
*/
|
|
6
|
+
import type { Theme } from '@earendil-works/pi-coding-agent';
|
|
7
|
+
import type { MenuCall, MenuUi } from "../../pluginMenu.js";
|
|
8
|
+
import type { TabInfo } from "../chrome.js";
|
|
9
|
+
import { type ListRow } from "../list.js";
|
|
10
|
+
import type { TerminalMouseInput } from "../mouse.js";
|
|
11
|
+
import type { TerminalPage } from "../page.js";
|
|
12
|
+
import type { WorktreeRecord, WorktreeStatus } from "../../../worktrees/types.js";
|
|
13
|
+
export interface WorktreeOverview {
|
|
14
|
+
repo: string | null;
|
|
15
|
+
current: string | null;
|
|
16
|
+
worktrees: WorktreeStatus[];
|
|
17
|
+
removed: WorktreeRecord[];
|
|
18
|
+
reason: string | null;
|
|
19
|
+
}
|
|
20
|
+
export interface WorktreesPageOptions {
|
|
21
|
+
theme(): Theme;
|
|
22
|
+
call: MenuCall;
|
|
23
|
+
ui: MenuUi & {
|
|
24
|
+
input(title: string, placeholder?: string): Promise<string | undefined>;
|
|
25
|
+
};
|
|
26
|
+
/** Ends the frontend so the host restarts it in the chosen folder. */
|
|
27
|
+
relaunch(): void;
|
|
28
|
+
changed(): void;
|
|
29
|
+
}
|
|
30
|
+
type Row = {
|
|
31
|
+
kind: 'new' | 'permanent' | 'prune' | 'trust' | 'main';
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'worktree';
|
|
34
|
+
status: WorktreeStatus;
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'removed';
|
|
37
|
+
record: WorktreeRecord;
|
|
38
|
+
};
|
|
39
|
+
export declare const WORKTREE_ACTIONS: {
|
|
40
|
+
readonly switch: "Switch this terminal here (new conversation)";
|
|
41
|
+
readonly branch: "Create branch here";
|
|
42
|
+
readonly toSource: "Hand off its changes to the main checkout";
|
|
43
|
+
readonly toWorktree: "Bring the main checkout's changes here";
|
|
44
|
+
readonly remove: "Remove";
|
|
45
|
+
readonly removeForce: "Remove, keeping a snapshot of unsaved work";
|
|
46
|
+
};
|
|
47
|
+
export declare class WorktreesPage implements TerminalPage {
|
|
48
|
+
private readonly options;
|
|
49
|
+
presentation: "centered";
|
|
50
|
+
private overview;
|
|
51
|
+
private error;
|
|
52
|
+
private busy;
|
|
53
|
+
private disposed;
|
|
54
|
+
private readonly list;
|
|
55
|
+
constructor(options: WorktreesPageOptions);
|
|
56
|
+
get focused(): boolean;
|
|
57
|
+
set focused(value: boolean);
|
|
58
|
+
activate(): void;
|
|
59
|
+
dispose(): void;
|
|
60
|
+
tabs(): readonly TabInfo[];
|
|
61
|
+
activeTab(): string;
|
|
62
|
+
hint(): string;
|
|
63
|
+
reload(): Promise<void>;
|
|
64
|
+
rows(): ListRow<Row>[];
|
|
65
|
+
private attempt;
|
|
66
|
+
activateRow(row: Row): Promise<void>;
|
|
67
|
+
private worktreeMenu;
|
|
68
|
+
handleMouse(event: TerminalMouseInput): import("@earendil-works/pi-tui").TuiMouseEventResult | undefined;
|
|
69
|
+
handleInput(data: string): boolean;
|
|
70
|
+
render(width: number, rows: number): string[];
|
|
71
|
+
}
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { truncateToWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
2
|
+
import { FilterList } from "../list.js";
|
|
3
|
+
const TABS = [{ id: 'worktrees', title: 'Worktrees' }];
|
|
4
|
+
export const WORKTREE_ACTIONS = {
|
|
5
|
+
switch: 'Switch this terminal here (new conversation)',
|
|
6
|
+
branch: 'Create branch here',
|
|
7
|
+
toSource: 'Hand off its changes to the main checkout',
|
|
8
|
+
toWorktree: "Bring the main checkout's changes here",
|
|
9
|
+
remove: 'Remove',
|
|
10
|
+
removeForce: 'Remove, keeping a snapshot of unsaved work'
|
|
11
|
+
};
|
|
12
|
+
export class WorktreesPage {
|
|
13
|
+
options;
|
|
14
|
+
presentation = 'centered';
|
|
15
|
+
overview;
|
|
16
|
+
error;
|
|
17
|
+
busy = false;
|
|
18
|
+
disposed = false;
|
|
19
|
+
list;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.options = options;
|
|
22
|
+
this.list = new FilterList({
|
|
23
|
+
theme: options.theme, search: 'always', noun: ['worktree', 'worktrees'], empty: 'No worktrees yet',
|
|
24
|
+
onActivate: row => { if (row.data)
|
|
25
|
+
void this.activateRow(row.data); }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
get focused() { return this.list.focused; }
|
|
29
|
+
set focused(value) { this.list.focused = value; }
|
|
30
|
+
activate() { void this.reload(); }
|
|
31
|
+
dispose() { this.disposed = true; }
|
|
32
|
+
tabs() { return TABS; }
|
|
33
|
+
activeTab() { return 'worktrees'; }
|
|
34
|
+
hint() { return 'Enter choose · type to search · Esc close'; }
|
|
35
|
+
async reload() {
|
|
36
|
+
try {
|
|
37
|
+
this.overview = await this.options.call('terminal.worktrees.list');
|
|
38
|
+
this.error = undefined;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
this.error = error instanceof Error ? error.message : String(error);
|
|
42
|
+
}
|
|
43
|
+
if (this.disposed)
|
|
44
|
+
return;
|
|
45
|
+
this.list.setRows(this.rows());
|
|
46
|
+
this.options.changed();
|
|
47
|
+
}
|
|
48
|
+
rows() {
|
|
49
|
+
const overview = this.overview;
|
|
50
|
+
if (!overview?.repo)
|
|
51
|
+
return [];
|
|
52
|
+
const rows = [
|
|
53
|
+
{ id: 'new', label: 'New managed worktree', group: 'Actions', detail: 'detached, carries your changes', tone: 'accent', data: { kind: 'new' } },
|
|
54
|
+
{ id: 'permanent', label: 'New permanent worktree', group: 'Actions', detail: 'named, never pruned', tone: 'muted', data: { kind: 'permanent' } }
|
|
55
|
+
];
|
|
56
|
+
if (overview.current)
|
|
57
|
+
rows.push({ id: 'main', label: 'Switch back to the main checkout', group: 'Actions', tone: 'muted', data: { kind: 'main' } });
|
|
58
|
+
rows.push({ id: 'prune', label: 'Prune older managed worktrees', group: 'Actions', tone: 'muted', data: { kind: 'prune' } }, { id: 'trust', label: 'Trust the setup script as it is now', group: 'Actions', tone: 'muted', data: { kind: 'trust' } });
|
|
59
|
+
for (const status of overview.worktrees) {
|
|
60
|
+
const record = status.record;
|
|
61
|
+
const here = record.id === overview.current ? ' · this terminal' : record.lease ? ' · in use' : '';
|
|
62
|
+
const changes = status.changes ? `${status.changes} changed` : 'clean';
|
|
63
|
+
rows.push({
|
|
64
|
+
id: `worktree:${record.id}`, label: record.name, group: 'Worktrees',
|
|
65
|
+
detail: `${record.branch ?? 'detached'} · ${changes}${status.ahead ? ` · ${status.ahead} commit(s)` : ''} · ${record.kind}${here}`,
|
|
66
|
+
tone: record.id === overview.current ? 'success' : status.changes ? 'warning' : 'muted',
|
|
67
|
+
searchText: `${record.name} ${record.branch ?? ''} ${record.path}`,
|
|
68
|
+
data: { kind: 'worktree', status }
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
for (const record of overview.removed) {
|
|
72
|
+
rows.push({
|
|
73
|
+
id: `removed:${record.id}`, label: record.name, group: 'Removed (restorable)',
|
|
74
|
+
detail: record.removed?.snapshot ? 'snapshot kept' : 'clean when removed', tone: 'dim', data: { kind: 'removed', record }
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return rows;
|
|
78
|
+
}
|
|
79
|
+
async attempt(work) {
|
|
80
|
+
if (this.busy)
|
|
81
|
+
return;
|
|
82
|
+
this.busy = true;
|
|
83
|
+
try {
|
|
84
|
+
const message = await work();
|
|
85
|
+
if (message)
|
|
86
|
+
this.options.ui.notify(message, 'info');
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
this.options.ui.notify(error instanceof Error ? error.message : String(error), 'error');
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
this.busy = false;
|
|
93
|
+
}
|
|
94
|
+
await this.reload();
|
|
95
|
+
}
|
|
96
|
+
async activateRow(row) {
|
|
97
|
+
const call = this.options.call;
|
|
98
|
+
const ui = this.options.ui;
|
|
99
|
+
switch (row.kind) {
|
|
100
|
+
case 'new':
|
|
101
|
+
case 'permanent':
|
|
102
|
+
return this.attempt(async () => {
|
|
103
|
+
const name = row.kind === 'permanent' ? await ui.input('Worktree name', 'lowercase-with-dashes') : undefined;
|
|
104
|
+
if (row.kind === 'permanent' && !name)
|
|
105
|
+
return;
|
|
106
|
+
const result = await call('terminal.worktrees.create', { kind: row.kind === 'permanent' ? 'permanent' : 'managed', ...(name ? { name } : {}) });
|
|
107
|
+
if (result.setup.state === 'untrusted')
|
|
108
|
+
ui.notify('The repository has a setup script you have not trusted; it did not run.', 'warning');
|
|
109
|
+
if (await ui.confirm(`Worktree ${result.worktree.name} is ready`, 'Switch this terminal there now? It starts a new conversation in that folder.')) {
|
|
110
|
+
await call('terminal.worktrees.switch', { id: result.worktree.id });
|
|
111
|
+
this.options.relaunch();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
case 'main':
|
|
115
|
+
return this.attempt(async () => {
|
|
116
|
+
await call('terminal.worktrees.switch', { main: true });
|
|
117
|
+
this.options.relaunch();
|
|
118
|
+
});
|
|
119
|
+
case 'prune':
|
|
120
|
+
return this.attempt(async () => {
|
|
121
|
+
const result = await call('terminal.worktrees.prune');
|
|
122
|
+
return `Removed ${result.removed.length} older worktree(s); unsaved work was kept as snapshots.`;
|
|
123
|
+
});
|
|
124
|
+
case 'trust':
|
|
125
|
+
return this.attempt(async () => {
|
|
126
|
+
if (!await ui.confirm('Trust the setup script?', 'Read .xharness/worktree-setup first. It will run in every new worktree of this repository until its text changes.'))
|
|
127
|
+
return;
|
|
128
|
+
await call('terminal.worktrees.trustSetup');
|
|
129
|
+
return 'Setup script trusted.';
|
|
130
|
+
});
|
|
131
|
+
case 'removed':
|
|
132
|
+
return this.attempt(async () => {
|
|
133
|
+
await call('terminal.worktrees.restore', { id: row.record.id });
|
|
134
|
+
return `Restored ${row.record.name}.`;
|
|
135
|
+
});
|
|
136
|
+
case 'worktree':
|
|
137
|
+
return this.worktreeMenu(row.status);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async worktreeMenu(status) {
|
|
141
|
+
const record = status.record;
|
|
142
|
+
const current = this.overview?.current === record.id;
|
|
143
|
+
const choices = [
|
|
144
|
+
...(current ? [] : [WORKTREE_ACTIONS.switch]),
|
|
145
|
+
...(record.branch ? [] : [WORKTREE_ACTIONS.branch]),
|
|
146
|
+
WORKTREE_ACTIONS.toSource,
|
|
147
|
+
WORKTREE_ACTIONS.toWorktree,
|
|
148
|
+
...(current ? [] : [status.changes || status.ahead ? WORKTREE_ACTIONS.removeForce : WORKTREE_ACTIONS.remove])
|
|
149
|
+
];
|
|
150
|
+
const choice = await this.options.ui.select(`${record.name} · ${record.path}`, choices);
|
|
151
|
+
const call = this.options.call;
|
|
152
|
+
const id = record.id;
|
|
153
|
+
if (choice === WORKTREE_ACTIONS.switch)
|
|
154
|
+
return this.attempt(async () => {
|
|
155
|
+
await call('terminal.worktrees.switch', { id });
|
|
156
|
+
this.options.relaunch();
|
|
157
|
+
});
|
|
158
|
+
if (choice === WORKTREE_ACTIONS.branch)
|
|
159
|
+
return this.attempt(async () => {
|
|
160
|
+
const name = await this.options.ui.input('Branch name', 'my-change');
|
|
161
|
+
if (!name)
|
|
162
|
+
return;
|
|
163
|
+
const updated = await call('terminal.worktrees.branch', { id, name });
|
|
164
|
+
return `${record.name} is on branch ${updated.branch}.`;
|
|
165
|
+
});
|
|
166
|
+
if (choice === WORKTREE_ACTIONS.toSource || choice === WORKTREE_ACTIONS.toWorktree)
|
|
167
|
+
return this.attempt(async () => {
|
|
168
|
+
const direction = choice === WORKTREE_ACTIONS.toSource ? 'toSource' : 'toWorktree';
|
|
169
|
+
const result = await call('terminal.worktrees.handoff', { id, direction });
|
|
170
|
+
if (!result.patched && !result.copied.length)
|
|
171
|
+
return 'Nothing to hand off.';
|
|
172
|
+
return `Handed off.${result.stash ? ' The origin keeps a copy in git stash.' : ''}`;
|
|
173
|
+
});
|
|
174
|
+
if (choice === WORKTREE_ACTIONS.remove || choice === WORKTREE_ACTIONS.removeForce)
|
|
175
|
+
return this.attempt(async () => {
|
|
176
|
+
const force = choice === WORKTREE_ACTIONS.removeForce;
|
|
177
|
+
if (force && !await this.options.ui.confirm(`Remove ${record.name}?`, 'Its unsaved changes and commits are kept as a snapshot you can restore from this page.'))
|
|
178
|
+
return;
|
|
179
|
+
await call('terminal.worktrees.remove', { id, force });
|
|
180
|
+
return `Removed ${record.name}.`;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
handleMouse(event) {
|
|
184
|
+
return this.list.handleMouse(event);
|
|
185
|
+
}
|
|
186
|
+
handleInput(data) {
|
|
187
|
+
return this.list.handleInput(data);
|
|
188
|
+
}
|
|
189
|
+
render(width, rows) {
|
|
190
|
+
const theme = this.options.theme();
|
|
191
|
+
if (width < 1 || rows < 1)
|
|
192
|
+
return [];
|
|
193
|
+
if (this.error)
|
|
194
|
+
return [theme.fg('error', `Could not read worktrees: ${this.error}`)];
|
|
195
|
+
if (!this.overview)
|
|
196
|
+
return [theme.fg('dim', 'Reading worktrees…')];
|
|
197
|
+
if (!this.overview.repo) {
|
|
198
|
+
return wrapTextWithAnsi(`Worktrees need a git repository. ${this.overview.reason ?? ''}`.trim(), width).slice(0, rows).map(line => theme.fg('dim', line));
|
|
199
|
+
}
|
|
200
|
+
const header = truncateToWidth(theme.fg('dim', `Repository ${this.overview.repo}`), width, '');
|
|
201
|
+
return [header, '', ...this.list.render(width, Math.max(1, rows - 2))].slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Theme, ToolRenderers } from '@earendil-works/pi-coding-agent';
|
|
2
2
|
export interface PiAdapter {
|
|
3
|
-
builtins: InlineExtension[];
|
|
4
3
|
setTheme(theme: Theme): void;
|
|
5
4
|
withBuiltInRenderers(toolName: string, definition: ToolRenderers | undefined): ToolRenderers | undefined;
|
|
6
5
|
readClipboardText(): Promise<string | null>;
|
|
@@ -12,18 +12,16 @@ export function loadPiAdapter() {
|
|
|
12
12
|
const pi = await loadPi();
|
|
13
13
|
assertPiAdapterVersion(pi.VERSION);
|
|
14
14
|
const root = pi.getPackageDir();
|
|
15
|
-
const [
|
|
16
|
-
import(pathToFileURL(join(root, 'dist/extensions/index.js')).href),
|
|
15
|
+
const [theme, renderers, clipboard, images] = await Promise.all([
|
|
17
16
|
import(pathToFileURL(join(root, 'dist/modes/interactive/theme/theme.js')).href),
|
|
18
17
|
import(pathToFileURL(join(root, 'dist/core/tools/renderers/index.js')).href),
|
|
19
18
|
import(pathToFileURL(join(root, 'dist/utils/clipboard.js')).href),
|
|
20
19
|
import(pathToFileURL(join(root, 'dist/utils/clipboard-image.js')).href)
|
|
21
20
|
]);
|
|
22
|
-
if (
|
|
23
|
-
throw new Error('The pinned Pi terminal adapter no longer exposes its required
|
|
21
|
+
if (typeof theme.setThemeInstance !== 'function' || typeof renderers.withBuiltInRenderers !== 'function' || typeof clipboard.readClipboardText !== 'function' || typeof clipboard.readClipboardFilePaths !== 'function' || typeof images.readClipboardImage !== 'function' || typeof images.extensionForImageMimeType !== 'function') {
|
|
22
|
+
throw new Error('The pinned Pi terminal adapter no longer exposes its required presentation hooks.');
|
|
24
23
|
}
|
|
25
24
|
adapter = {
|
|
26
|
-
builtins: extensions.builtInExtensions,
|
|
27
25
|
setTheme: theme.setThemeInstance,
|
|
28
26
|
withBuiltInRenderers: renderers.withBuiltInRenderers,
|
|
29
27
|
readClipboardText: clipboard.readClipboardText,
|