@xingular/harness 1.0.0 → 1.1.1
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 +33 -5
- package/README.md +70 -19
- package/dist/cli.js +10321 -5785
- package/dist/sdk/agents/builtin.d.ts +2 -0
- package/dist/sdk/agents/builtin.js +34 -0
- package/dist/sdk/agents/narrow.d.ts +13 -0
- package/dist/sdk/agents/narrow.js +29 -0
- package/dist/sdk/agents/profiles.d.ts +80 -0
- package/dist/sdk/agents/profiles.js +221 -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 +570 -0
- package/dist/sdk/annotations/part.d.ts +10 -0
- package/dist/sdk/annotations/part.js +144 -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 +160 -0
- package/dist/sdk/annotations/store.js +386 -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/cli.js +19 -11
- package/dist/sdk/core/bus.js +1 -1
- package/dist/sdk/core/env.d.ts +1 -1
- package/dist/sdk/core/env.js +1 -1
- package/dist/sdk/doctor/serverMethods.d.ts +3 -0
- package/dist/sdk/doctor/serverMethods.js +8 -0
- package/dist/sdk/harness.d.ts +15 -0
- package/dist/sdk/harness.js +44 -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 +10 -0
- package/dist/sdk/index.js +7 -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/plugins/builtin.d.ts +1 -0
- package/dist/sdk/plugins/builtin.js +128 -33
- package/dist/sdk/plugins/builtinRegistry.d.ts +53 -0
- package/dist/sdk/plugins/builtinRegistry.js +49 -0
- package/dist/sdk/plugins/cli.d.ts +1 -1
- package/dist/sdk/plugins/cli.js +4 -3
- package/dist/sdk/prompt/compile.d.ts +5 -2
- package/dist/sdk/prompt/compile.js +25 -8
- 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 +9 -86
- package/dist/sdk/server/params.d.ts +4 -0
- package/dist/sdk/server/params.js +11 -0
- package/dist/sdk/sessions/contracts.d.ts +17 -0
- package/dist/sdk/sessions/contracts.js +42 -3
- package/dist/sdk/sessions/planMode.d.ts +5 -2
- package/dist/sdk/sessions/planMode.js +12 -3
- package/dist/sdk/sessions/reasoningSummaries.d.ts +2 -2
- package/dist/sdk/sessions/reasoningSummaries.js +3 -3
- package/dist/sdk/sessions/registry.d.ts +1 -0
- package/dist/sdk/sessions/registry.js +4 -1
- package/dist/sdk/sessions/runIndex.js +7 -7
- package/dist/sdk/sessions/runStore.js +3 -1
- package/dist/sdk/sessions/runner.js +2 -2
- package/dist/sdk/sessions/sessions.d.ts +17 -5
- package/dist/sdk/sessions/sessions.js +79 -12
- 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 +13 -2
- package/dist/sdk/sessions/subagentService.js +34 -16
- package/dist/sdk/sessions/subagents.d.ts +9 -1
- package/dist/sdk/sessions/subagents.js +19 -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 +186 -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 +7 -1
- package/dist/sdk/terminal/controller.js +34 -2
- package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +28 -0
- package/dist/sdk/terminal/opentui/subagentsWidget.js +52 -0
- package/dist/sdk/terminal/piExtension.js +33 -48
- package/dist/sdk/terminal/socket.d.ts +6 -3
- package/dist/sdk/terminal/socket.js +40 -9
- package/dist/sdk/terminal/standalone.d.ts +2 -0
- package/dist/sdk/terminal/standalone.js +50 -10
- package/dist/sdk/terminal/subagentMonitor.d.ts +38 -0
- package/dist/sdk/terminal/subagentMonitor.js +121 -0
- package/dist/sdk/terminal/terminals.d.ts +42 -6
- package/dist/sdk/terminal/terminals.js +359 -18
- 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 +5 -1
- package/dist/sdk/terminal/tui/commandPages.js +54 -6
- package/dist/sdk/terminal/tui/commands.d.ts +8 -4
- package/dist/sdk/terminal/tui/commands.js +181 -58
- 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/modelPicker.js +1 -6
- package/dist/sdk/terminal/tui/page.d.ts +3 -0
- package/dist/sdk/terminal/tui/pageOverlay.js +17 -6
- 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/settingsPage.d.ts +8 -0
- package/dist/sdk/terminal/tui/pages/settingsPage.js +16 -1
- package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +23 -0
- package/dist/sdk/terminal/tui/pages/subagentsPage.js +70 -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/worktreesPage.d.ts +72 -0
- package/dist/sdk/terminal/tui/pages/worktreesPage.js +203 -0
- 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 +14 -0
- package/dist/sdk/terminal/tui/pluginCommands.js +27 -0
- package/dist/sdk/terminal/tui/questions.d.ts +2 -0
- package/dist/sdk/terminal/tui/runtime.js +4 -2
- 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 +2 -2
- package/dist/sdk/tools/harnessTools.d.ts +2 -0
- package/dist/sdk/tools/harnessTools.js +1 -1
- package/dist/sdk/tools/spawnSubagent.js +13 -4
- 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 +14540 -7374
- package/dist/terminal-pi.js +285 -666
- package/docs/README.md +22 -2
- package/docs/architecture/00-overview.md +28 -16
- package/docs/architecture/02-sessions-and-runs.md +72 -15
- package/docs/architecture/03-variants.md +10 -2
- package/docs/architecture/04-prompt.md +26 -6
- package/docs/architecture/06-plugins-and-capabilities.md +39 -3
- package/docs/architecture/07-tools-and-gate.md +21 -6
- package/docs/architecture/09-accounts-and-connectors.md +8 -0
- package/docs/architecture/10-server-protocol.md +111 -2
- package/docs/architecture/11-terminal.md +185 -5
- package/docs/architecture/12-storage.md +15 -1
- package/docs/architecture/13-world-tools.md +28 -6
- package/docs/architecture/14-surfaces-and-parity.md +89 -0
- package/docs/architecture/README.md +15 -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 +127 -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/README.md +25 -0
- 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/doctor.md +10 -4
- package/docs/features/README.md +26 -0
- package/docs/features/interfaces/README.md +1 -1
- package/docs/future-work.md +48 -0
- package/docs/parity.md +78 -0
- package/docs/releases/1.0.0.md +2 -2
- package/docs/releases/4.0.0.md +46 -0
- package/docs/releases/README.md +20 -15
- package/docs/study/00-index.md +5 -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 +325 -93
- package/package.json +3 -3
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { matchesKey, stripTerminalSequences, truncateToWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
2
|
-
import { selectedRow, selectionText } from "../chrome.js";
|
|
1
|
+
import { matchesKey, stripTerminalSequences, truncateToWidth, visibleWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
3
2
|
import { FilterList } from "../list.js";
|
|
4
|
-
import { isPrimaryClick } from "../mouse.js";
|
|
5
3
|
const ACTIVE = new Set(['queued', 'running', 'waiting_for_approval', 'waiting_for_input']);
|
|
6
4
|
const payloadOf = (event) => event.payload && typeof event.payload === 'object' ? event.payload : {};
|
|
7
5
|
const stringOf = (value) => typeof value === 'string' ? value : undefined;
|
|
@@ -68,17 +66,20 @@ const SECTIONS = [
|
|
|
68
66
|
];
|
|
69
67
|
export class TrajectoryPage {
|
|
70
68
|
options;
|
|
69
|
+
presentation = 'fullscreen';
|
|
71
70
|
runs = [];
|
|
72
71
|
section = 'runs';
|
|
73
72
|
runList;
|
|
73
|
+
eventList;
|
|
74
74
|
runIndex = 0;
|
|
75
75
|
run;
|
|
76
76
|
events = [];
|
|
77
|
-
selected = 0;
|
|
78
77
|
detailOffset = 0;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
listStart = 0;
|
|
79
|
+
listEnd = 0;
|
|
80
|
+
listWidth = 0;
|
|
81
|
+
detailsStart = -1;
|
|
82
|
+
wideLayout = false;
|
|
82
83
|
loading = false;
|
|
83
84
|
loaded = false;
|
|
84
85
|
disposed = false;
|
|
@@ -87,15 +88,19 @@ export class TrajectoryPage {
|
|
|
87
88
|
constructor(options) {
|
|
88
89
|
this.options = options;
|
|
89
90
|
this.runList = new FilterList({
|
|
90
|
-
theme: options.theme, search: '
|
|
91
|
+
theme: options.theme, search: 'always', noun: ['run', 'runs'], empty: 'No runs recorded for this native session yet',
|
|
91
92
|
onActivate: row => { if (row.data)
|
|
92
93
|
void this.selectRun(row.data); }
|
|
93
94
|
});
|
|
95
|
+
this.eventList = new FilterList({
|
|
96
|
+
theme: options.theme, search: 'always', noun: ['event', 'events'], empty: 'No recorded events',
|
|
97
|
+
onActivate: () => { this.detailOffset = 0; this.options.changed(); }
|
|
98
|
+
});
|
|
94
99
|
}
|
|
95
100
|
get selectedRunId() { return this.run?.id; }
|
|
96
|
-
get selectedEvent() { return this.
|
|
97
|
-
get focused() { return this.runList.focused; }
|
|
98
|
-
set focused(value) { this.runList.focused = value; }
|
|
101
|
+
get selectedEvent() { return this.eventList.selected?.data; }
|
|
102
|
+
get focused() { return this.section === 'runs' ? this.runList.focused : this.eventList.focused; }
|
|
103
|
+
set focused(value) { this.runList.focused = value; this.eventList.focused = value; }
|
|
99
104
|
tabs() { return SECTIONS; }
|
|
100
105
|
activeTab() { return this.section; }
|
|
101
106
|
activate() {
|
|
@@ -153,8 +158,11 @@ export class TrajectoryPage {
|
|
|
153
158
|
return;
|
|
154
159
|
this.run = snapshot.run;
|
|
155
160
|
this.events = snapshot.events.slice().sort(bySequence);
|
|
161
|
+
this.eventList.setRows(this.eventRows());
|
|
162
|
+
const latest = this.events.at(-1);
|
|
163
|
+
if (latest)
|
|
164
|
+
this.eventList.select(String(latest.sequence));
|
|
156
165
|
this.runList.select(run.id);
|
|
157
|
-
this.selected = Math.max(0, this.events.length - 1);
|
|
158
166
|
this.detailOffset = 0;
|
|
159
167
|
}
|
|
160
168
|
async refresh() {
|
|
@@ -162,7 +170,8 @@ export class TrajectoryPage {
|
|
|
162
170
|
return;
|
|
163
171
|
this.loading = true;
|
|
164
172
|
const lastSequence = this.events.at(-1)?.sequence;
|
|
165
|
-
const
|
|
173
|
+
const selectedBefore = this.selectedEvent;
|
|
174
|
+
const followedLatest = !selectedBefore || selectedBefore.sequence === lastSequence;
|
|
166
175
|
try {
|
|
167
176
|
const snapshot = await this.options.read(this.run.id, lastSequence);
|
|
168
177
|
if (this.disposed)
|
|
@@ -176,8 +185,14 @@ export class TrajectoryPage {
|
|
|
176
185
|
const existing = new Set(this.events.map(event => event.sequence));
|
|
177
186
|
this.events = [...this.events, ...snapshot.events.filter(event => !existing.has(event.sequence))].sort(bySequence);
|
|
178
187
|
}
|
|
179
|
-
|
|
180
|
-
|
|
188
|
+
this.eventList.setRows(this.eventRows());
|
|
189
|
+
if (followedLatest) {
|
|
190
|
+
const latest = this.events.at(-1);
|
|
191
|
+
if (latest)
|
|
192
|
+
this.eventList.select(String(latest.sequence));
|
|
193
|
+
}
|
|
194
|
+
else if (selectedBefore)
|
|
195
|
+
this.eventList.select(String(selectedBefore.sequence));
|
|
181
196
|
this.error = undefined;
|
|
182
197
|
}
|
|
183
198
|
catch (error) {
|
|
@@ -201,6 +216,18 @@ export class TrajectoryPage {
|
|
|
201
216
|
data: run
|
|
202
217
|
}));
|
|
203
218
|
}
|
|
219
|
+
eventRows() {
|
|
220
|
+
return this.events.map(event => ({
|
|
221
|
+
id: String(event.sequence),
|
|
222
|
+
label: `${timeOf(event.timestamp)} ${event.type}`,
|
|
223
|
+
detail: eventDescription(event),
|
|
224
|
+
searchText: `${event.timestamp} ${event.type} ${eventDescription(event)} ${safe(JSON.stringify(payloadOf(event)))}`,
|
|
225
|
+
data: event
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
eventJson(event) {
|
|
229
|
+
return JSON.stringify({ payload: event.payload, type: event.type, sequence: event.sequence, timestamp: event.timestamp }, null, 2);
|
|
230
|
+
}
|
|
204
231
|
async selectRun(run) {
|
|
205
232
|
const at = this.runs.findIndex(candidate => candidate.id === run.id);
|
|
206
233
|
if (at < 0 || this.loading)
|
|
@@ -209,7 +236,7 @@ export class TrajectoryPage {
|
|
|
209
236
|
this.runList.select(run.id);
|
|
210
237
|
this.run = run;
|
|
211
238
|
this.events = [];
|
|
212
|
-
this.
|
|
239
|
+
this.eventList.setRows([]);
|
|
213
240
|
this.detailOffset = 0;
|
|
214
241
|
this.error = undefined;
|
|
215
242
|
this.loading = true;
|
|
@@ -236,7 +263,7 @@ export class TrajectoryPage {
|
|
|
236
263
|
this.run = this.runs[next];
|
|
237
264
|
this.runList.select(this.run.id);
|
|
238
265
|
this.events = [];
|
|
239
|
-
this.
|
|
266
|
+
this.eventList.setRows([]);
|
|
240
267
|
this.detailOffset = 0;
|
|
241
268
|
this.error = undefined;
|
|
242
269
|
this.loading = true;
|
|
@@ -255,21 +282,38 @@ export class TrajectoryPage {
|
|
|
255
282
|
}
|
|
256
283
|
}
|
|
257
284
|
hint() {
|
|
258
|
-
return this.section === 'runs'
|
|
285
|
+
return this.section === 'runs'
|
|
286
|
+
? '↑/↓ runs · type to search · Enter inspect'
|
|
287
|
+
: '[ ] runs · ↑/↓ events · type to search · PgUp/PgDn details · Shift+C copy';
|
|
259
288
|
}
|
|
260
289
|
handleMouse(event) {
|
|
290
|
+
if (event.type === 'wheel') {
|
|
291
|
+
const down = (event.wheelDelta ?? 1) >= 0;
|
|
292
|
+
if (this.section === 'events' && this.detailsStart >= 0 && (this.wideLayout ? event.x >= this.listWidth + 3 : event.y >= this.detailsStart)) {
|
|
293
|
+
this.detailOffset = Math.max(0, this.detailOffset + (down ? 4 : -4));
|
|
294
|
+
this.options.changed();
|
|
295
|
+
return { handled: true, render: true };
|
|
296
|
+
}
|
|
297
|
+
if (this.section === 'runs')
|
|
298
|
+
this.runList.handleInput(down ? '\x1b[B' : '\x1b[A');
|
|
299
|
+
else
|
|
300
|
+
this.eventList.handleInput(down ? '\x1b[B' : '\x1b[A');
|
|
301
|
+
this.detailOffset = 0;
|
|
302
|
+
this.options.changed();
|
|
303
|
+
return { handled: true, render: true };
|
|
304
|
+
}
|
|
261
305
|
if (event.type === 'click' && event.button !== 'left')
|
|
262
306
|
return undefined;
|
|
263
|
-
if (
|
|
264
|
-
return this.runList.handleMouse({ ...event, y: event.y - 1 });
|
|
265
|
-
const offset = event.y - this.eventRowsStart;
|
|
266
|
-
if (this.eventRowsStart < 0 || offset < 0 || offset >= this.eventRowsCount)
|
|
307
|
+
if (event.type !== 'click' && event.type !== 'move')
|
|
267
308
|
return undefined;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
309
|
+
if (this.section === 'runs') {
|
|
310
|
+
if (event.y < this.listStart || event.y >= this.listEnd)
|
|
311
|
+
return undefined;
|
|
312
|
+
return this.runList.handleMouse({ ...event, y: event.y - this.listStart });
|
|
313
|
+
}
|
|
314
|
+
if (event.y < this.listStart || event.y >= this.listEnd || (this.wideLayout && event.x >= this.listWidth + 3))
|
|
315
|
+
return undefined;
|
|
316
|
+
return this.eventList.handleMouse({ ...event, y: event.y - this.listStart });
|
|
273
317
|
}
|
|
274
318
|
handleInput(data) {
|
|
275
319
|
if (matchesKey(data, 'tab') || matchesKey(data, 'shift+tab')) {
|
|
@@ -288,16 +332,6 @@ export class TrajectoryPage {
|
|
|
288
332
|
void this.changeRun(-1);
|
|
289
333
|
return true;
|
|
290
334
|
}
|
|
291
|
-
if (matchesKey(data, 'up')) {
|
|
292
|
-
this.selected = Math.max(0, this.selected - 1);
|
|
293
|
-
this.detailOffset = 0;
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
if (matchesKey(data, 'down')) {
|
|
297
|
-
this.selected = this.events.length ? Math.min(this.events.length - 1, this.selected + 1) : 0;
|
|
298
|
-
this.detailOffset = 0;
|
|
299
|
-
return true;
|
|
300
|
-
}
|
|
301
335
|
if (matchesKey(data, 'pageUp')) {
|
|
302
336
|
this.detailOffset = Math.max(0, this.detailOffset - 8);
|
|
303
337
|
return true;
|
|
@@ -306,71 +340,110 @@ export class TrajectoryPage {
|
|
|
306
340
|
this.detailOffset += 8;
|
|
307
341
|
return true;
|
|
308
342
|
}
|
|
309
|
-
if (data
|
|
310
|
-
const text =
|
|
343
|
+
if (data === 'C' && this.selectedEvent) {
|
|
344
|
+
const text = this.eventJson(this.selectedEvent);
|
|
311
345
|
void this.options.copy(text).then(() => this.options.notify('Copied selected trajectory event')).catch(error => this.options.notify(String(error), 'error'));
|
|
312
346
|
return true;
|
|
313
347
|
}
|
|
314
|
-
|
|
348
|
+
const selected = this.selectedEvent?.sequence;
|
|
349
|
+
if (!this.eventList.handleInput(data))
|
|
350
|
+
return false;
|
|
351
|
+
if (this.selectedEvent?.sequence !== selected)
|
|
352
|
+
this.detailOffset = 0;
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
runSummary(width, rows) {
|
|
356
|
+
if (!this.run || rows < 1)
|
|
357
|
+
return [];
|
|
358
|
+
const runPosition = `${this.runIndex + 1}/${this.runs.length}`;
|
|
359
|
+
const modelEvent = [...this.events].reverse().find(event => event.type === 'usage.updated');
|
|
360
|
+
const usage = modelEvent ? payloadOf(modelEvent) : {};
|
|
361
|
+
const model = stringOf(usage.model) ?? 'model unavailable';
|
|
362
|
+
const theme = this.options.theme();
|
|
363
|
+
const lines = [theme.bold(theme.fg('accent', `Run ${runPosition} · ${this.run.id} · ${this.run.status}${this.loading ? ' · loading' : ''}`))];
|
|
364
|
+
if (rows >= 3)
|
|
365
|
+
lines.push(theme.fg('muted', `${model} · ${durationLine(this.run)} · ${this.run.mode}`));
|
|
366
|
+
if (rows >= 4)
|
|
367
|
+
lines.push(theme.fg('muted', usageLine(this.events)));
|
|
368
|
+
if (rows >= 6)
|
|
369
|
+
lines.push(theme.fg('dim', this.run.goal ? `Prompt: ${this.run.goal.replace(/\s+/g, ' ').slice(0, 140)}` : 'Prompt text unavailable'));
|
|
370
|
+
if (this.error)
|
|
371
|
+
lines.push(theme.fg('error', `Read error: ${this.error}`));
|
|
372
|
+
return lines.slice(0, Math.min(rows, 5)).map(line => truncateToWidth(line, width, ''));
|
|
373
|
+
}
|
|
374
|
+
detailLines(width, rows) {
|
|
375
|
+
if (rows < 1)
|
|
376
|
+
return [];
|
|
377
|
+
const event = this.selectedEvent;
|
|
378
|
+
const theme = this.options.theme();
|
|
379
|
+
const heading = theme.bold(theme.fg('accent', event ? `Event JSON · ${event.type} · #${event.sequence}` : 'Event JSON'));
|
|
380
|
+
const json = event
|
|
381
|
+
? wrapTextWithAnsi(safe(this.eventJson(event)), Math.max(1, width))
|
|
382
|
+
: [this.events.length ? 'No event selected' : this.loading ? 'Loading recorded events…' : 'No recorded events'];
|
|
383
|
+
const visibleRows = Math.max(0, rows - 1);
|
|
384
|
+
this.detailOffset = Math.min(this.detailOffset, Math.max(0, json.length - visibleRows));
|
|
385
|
+
return [heading, ...json.slice(this.detailOffset, this.detailOffset + visibleRows)
|
|
386
|
+
.map(line => this.options.theme().fg('dim', line))].slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
315
387
|
}
|
|
316
388
|
render(width, rows) {
|
|
317
389
|
const theme = this.options.theme();
|
|
318
390
|
if (width < 1 || rows < 1)
|
|
319
391
|
return [];
|
|
320
392
|
if (this.section === 'runs') {
|
|
321
|
-
this.
|
|
322
|
-
this.
|
|
393
|
+
this.wideLayout = false;
|
|
394
|
+
this.detailsStart = -1;
|
|
323
395
|
if (this.error && !this.runs.length)
|
|
324
396
|
return [theme.fg('error', 'Read error: ' + this.error)];
|
|
325
|
-
const lines = [theme.fg('dim', !this.loaded ? 'Loading recorded runs…' : this.runs.length
|
|
326
|
-
|
|
397
|
+
const lines = [theme.fg('dim', !this.loaded ? 'Loading recorded runs…' : `${this.runs.length} recorded runs`)];
|
|
398
|
+
this.listStart = lines.length;
|
|
399
|
+
const list = this.runList.render(width, Math.max(0, rows - lines.length));
|
|
400
|
+
this.listEnd = this.listStart + list.length;
|
|
401
|
+
lines.push(...list);
|
|
327
402
|
return lines.slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
328
403
|
}
|
|
329
404
|
if (!this.run) {
|
|
330
|
-
this.
|
|
331
|
-
this.
|
|
405
|
+
this.listStart = this.listEnd = 0;
|
|
406
|
+
this.detailsStart = -1;
|
|
332
407
|
if (this.error)
|
|
333
408
|
return [theme.fg('error', 'Read error: ' + this.error)];
|
|
334
409
|
return [theme.fg('dim', this.loading || !this.loaded ? 'Loading recorded runs…' : 'No run selected. Choose a run in the Runs tab.')];
|
|
335
410
|
}
|
|
336
|
-
const
|
|
337
|
-
const
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const firstEvent = Math.max(0, Math.min(this.events.length - ledgerBudget, this.selected - Math.floor(ledgerBudget / 2)));
|
|
358
|
-
this.eventRowsStart = lines.length;
|
|
359
|
-
this.eventRowsFirst = firstEvent;
|
|
360
|
-
this.eventRowsCount = ledgerBudget;
|
|
361
|
-
for (let offset = 0; offset < ledgerBudget; offset++) {
|
|
362
|
-
const index = firstEvent + offset;
|
|
363
|
-
const event = this.events[index];
|
|
364
|
-
const prefix = index === this.selected ? '❯' : ' ';
|
|
365
|
-
const label = truncateToWidth(prefix + ' ' + timeOf(event.timestamp) + ' ' + event.type + ' ' + eventDescription(event), width, '');
|
|
366
|
-
lines.push(index === this.selected ? selectedRow(selectionText(theme, label), width, theme) : theme.fg('muted', label));
|
|
411
|
+
const lines = this.runSummary(width, rows);
|
|
412
|
+
const bodyRows = Math.max(0, rows - lines.length);
|
|
413
|
+
const wide = width >= 72;
|
|
414
|
+
this.wideLayout = wide;
|
|
415
|
+
if (wide) {
|
|
416
|
+
const separatorWidth = 3;
|
|
417
|
+
const leftWidth = Math.max(24, Math.floor((width - separatorWidth) * 0.42));
|
|
418
|
+
const rightWidth = Math.max(1, width - leftWidth - separatorWidth);
|
|
419
|
+
const paneStart = lines.length;
|
|
420
|
+
this.listWidth = leftWidth;
|
|
421
|
+
const events = [theme.bold(theme.fg('accent', 'Events')), ...this.eventList.render(leftWidth, Math.max(0, bodyRows - 1))];
|
|
422
|
+
const details = this.detailLines(rightWidth, bodyRows);
|
|
423
|
+
this.listStart = paneStart + 1;
|
|
424
|
+
this.listEnd = this.listStart + events.length - 1;
|
|
425
|
+
this.detailsStart = paneStart;
|
|
426
|
+
const pane = Array.from({ length: Math.max(events.length, details.length) }, (_, index) => {
|
|
427
|
+
const left = truncateToWidth(events[index] ?? '', leftWidth, '');
|
|
428
|
+
const gap = ' '.repeat(Math.max(0, leftWidth - visibleWidth(left)));
|
|
429
|
+
return left + gap + theme.fg('dim', ' │ ') + (details[index] ?? '');
|
|
430
|
+
});
|
|
431
|
+
lines.push(...pane);
|
|
367
432
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
433
|
+
else {
|
|
434
|
+
lines.push(theme.bold(theme.fg('accent', 'Events')));
|
|
435
|
+
this.listStart = lines.length;
|
|
436
|
+
this.listWidth = width;
|
|
437
|
+
const listRows = Math.min(Math.max(0, bodyRows - 1), Math.max(1, Math.floor(bodyRows * 0.58)));
|
|
438
|
+
const events = this.eventList.render(width, listRows);
|
|
439
|
+
this.listEnd = this.listStart + events.length;
|
|
440
|
+
lines.push(...events);
|
|
441
|
+
const hasRoomForDetails = lines.length < rows;
|
|
442
|
+
if (hasRoomForDetails)
|
|
443
|
+
lines.push('');
|
|
444
|
+
this.detailsStart = lines.length;
|
|
445
|
+
const detailRows = Math.max(0, rows - lines.length);
|
|
446
|
+
lines.push(...this.detailLines(width, detailRows));
|
|
374
447
|
}
|
|
375
448
|
return lines.slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
376
449
|
}
|
|
@@ -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,2 +1,2 @@
|
|
|
1
|
-
export { parseProposedPlan, planModeTools } from "../../sessions/planMode.js";
|
|
1
|
+
export { normalModeTools, parseProposedPlan, planModeTools, QUESTION_TOOL_NAMES } from "../../sessions/planMode.js";
|
|
2
2
|
export type { ProposedPlan } from "../../sessions/planMode.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// The terminal's plan mode uses the session's read-only tool set.
|
|
2
|
-
export { parseProposedPlan, planModeTools } from "../../sessions/planMode.js";
|
|
2
|
+
export { normalModeTools, parseProposedPlan, planModeTools, QUESTION_TOOL_NAMES } from "../../sessions/planMode.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AnnotateStart } from "./pages/annotatePage.js";
|
|
2
|
+
import type { CommandPageId } from "./commandPages.js";
|
|
3
|
+
import type { TerminalCommandHost } from "./commands.js";
|
|
4
|
+
import { parseAnnotate } from "./annotateCommand.js";
|
|
5
|
+
type PageOptions = {
|
|
6
|
+
runId?: string;
|
|
7
|
+
url?: string;
|
|
8
|
+
annotate?: AnnotateStart;
|
|
9
|
+
};
|
|
10
|
+
type OpenPage = (id: CommandPageId, options?: PageOptions) => Promise<void>;
|
|
11
|
+
type Handler = (argument: string) => Promise<void>;
|
|
12
|
+
/** Terminal adapters for commands declared by Harness-owned feature plugins. */
|
|
13
|
+
export declare function createBuiltinPluginCommandHandlers(host: TerminalCommandHost, openPage: OpenPage): Map<string, Handler>;
|
|
14
|
+
export { parseAnnotate };
|