@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
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { stripTerminalSequences, truncateToWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
2
|
+
import { formatVariant } from "../../../agents/profiles.js";
|
|
3
|
+
import { headerRow, neutralRule, padded } from "../chrome.js";
|
|
4
|
+
import { FilterList } from "../list.js";
|
|
5
|
+
export const AGENT_ACTIONS = {
|
|
6
|
+
edit: 'Edit in your editor',
|
|
7
|
+
copy: 'Copy to a new profile',
|
|
8
|
+
remove: 'Delete'
|
|
9
|
+
};
|
|
10
|
+
export const AGENT_SCOPES = {
|
|
11
|
+
project: 'Project: this repository only',
|
|
12
|
+
global: 'Global: every folder'
|
|
13
|
+
};
|
|
14
|
+
const TABS = [{ id: 'agents', title: 'Agents' }];
|
|
15
|
+
const GROUP = { builtin: 'Built-in', global: 'Global', project: 'Project', variant: 'From the variant' };
|
|
16
|
+
function clean(value) {
|
|
17
|
+
return stripTerminalSequences(value).replace(/[\x00-\x08\x0b-\x1f\x7f]/g, ' ').replace(/[ \t]+/g, ' ').trim();
|
|
18
|
+
}
|
|
19
|
+
export class AgentsPage {
|
|
20
|
+
options;
|
|
21
|
+
presentation = 'fullscreen';
|
|
22
|
+
overview;
|
|
23
|
+
error;
|
|
24
|
+
busy = false;
|
|
25
|
+
disposed = false;
|
|
26
|
+
list;
|
|
27
|
+
constructor(options) {
|
|
28
|
+
this.options = options;
|
|
29
|
+
this.list = new FilterList({
|
|
30
|
+
theme: options.theme, search: 'always', noun: ['profile', 'profiles'], empty: 'No profiles',
|
|
31
|
+
onActivate: row => { if (row.data)
|
|
32
|
+
void this.activateRow(row.data); }
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
get focused() { return this.list.focused; }
|
|
36
|
+
set focused(value) { this.list.focused = value; }
|
|
37
|
+
activate() { void this.reload(); }
|
|
38
|
+
dispose() { this.disposed = true; }
|
|
39
|
+
tabs() { return TABS; }
|
|
40
|
+
activeTab() { return 'agents'; }
|
|
41
|
+
hint() { return 'Enter choose · type to search'; }
|
|
42
|
+
async reload() {
|
|
43
|
+
try {
|
|
44
|
+
this.overview = await this.options.call('terminal.agents.list');
|
|
45
|
+
this.error = undefined;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
this.error = error instanceof Error ? error.message : String(error);
|
|
49
|
+
}
|
|
50
|
+
if (this.disposed)
|
|
51
|
+
return;
|
|
52
|
+
this.list.setRows(this.rows());
|
|
53
|
+
this.options.changed();
|
|
54
|
+
}
|
|
55
|
+
rows() {
|
|
56
|
+
const overview = this.overview;
|
|
57
|
+
if (!overview)
|
|
58
|
+
return [];
|
|
59
|
+
const rows = [{ id: 'new', label: 'New profile', group: 'Actions', detail: 'from a template, then opens in your editor', tone: 'accent', data: { kind: 'new' } }];
|
|
60
|
+
for (const source of ['builtin', 'global', 'project', 'variant']) {
|
|
61
|
+
for (const profile of overview.profiles.filter(entry => entry.source === source)) {
|
|
62
|
+
rows.push({
|
|
63
|
+
id: `profile:${profile.name}`, label: profile.name, group: GROUP[source], detail: clean(profile.description), tone: 'muted',
|
|
64
|
+
searchText: `${profile.name} ${profile.description} ${source}`, data: { kind: 'profile', profile }
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const problem of overview.problems) {
|
|
69
|
+
rows.push({
|
|
70
|
+
id: `problem:${problem.source}:${problem.name}`, label: problem.name, group: 'Cannot be read', detail: clean(problem.error), tone: 'error',
|
|
71
|
+
searchText: `${problem.name} ${problem.error}`, data: { kind: 'problem', problem }
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return rows;
|
|
75
|
+
}
|
|
76
|
+
async attempt(work) {
|
|
77
|
+
if (this.busy)
|
|
78
|
+
return;
|
|
79
|
+
this.busy = true;
|
|
80
|
+
try {
|
|
81
|
+
const message = await work();
|
|
82
|
+
if (message)
|
|
83
|
+
this.options.ui.notify(message, 'info');
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
this.options.ui.notify(error instanceof Error ? error.message : String(error), 'error');
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
this.busy = false;
|
|
90
|
+
}
|
|
91
|
+
await this.reload();
|
|
92
|
+
}
|
|
93
|
+
async open(path) {
|
|
94
|
+
if (!this.options.edit)
|
|
95
|
+
return `Edit the profile at ${path}.`;
|
|
96
|
+
await this.options.edit(path);
|
|
97
|
+
}
|
|
98
|
+
async create(from) {
|
|
99
|
+
const ui = this.options.ui;
|
|
100
|
+
const name = (await ui.input(from ? `Name for the copy of ${from}` : 'Profile name', 'lowercase-with-dashes'))?.trim();
|
|
101
|
+
if (!name)
|
|
102
|
+
return;
|
|
103
|
+
const scopes = this.overview?.dirs.project ? [AGENT_SCOPES.project, AGENT_SCOPES.global] : [AGENT_SCOPES.global];
|
|
104
|
+
const choice = await ui.select(`Where should ${name} live?`, scopes);
|
|
105
|
+
if (!choice)
|
|
106
|
+
return;
|
|
107
|
+
const scope = choice === AGENT_SCOPES.project ? 'project' : 'global';
|
|
108
|
+
const made = await this.options.call('terminal.agents.create', { name, scope, ...(from ? { from } : {}) });
|
|
109
|
+
return (await this.open(made.path)) ?? `Saved ${made.name}.`;
|
|
110
|
+
}
|
|
111
|
+
async activateRow(row) {
|
|
112
|
+
if (row.kind === 'new')
|
|
113
|
+
return this.attempt(() => this.create(null));
|
|
114
|
+
if (row.kind === 'problem') {
|
|
115
|
+
const { problem } = row;
|
|
116
|
+
const scope = problem.source === 'global' || problem.source === 'project' ? problem.source : null;
|
|
117
|
+
const choice = await this.options.ui.select(`${problem.name} · ${problem.error}`, [AGENT_ACTIONS.edit, ...(scope ? [AGENT_ACTIONS.remove] : [])]);
|
|
118
|
+
if (choice === AGENT_ACTIONS.edit)
|
|
119
|
+
return this.attempt(() => this.open(problem.path));
|
|
120
|
+
if (choice === AGENT_ACTIONS.remove && scope)
|
|
121
|
+
return this.remove(problem.name, scope);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const { profile } = row;
|
|
125
|
+
const own = profile.source === 'global' || profile.source === 'project' ? profile.source : null;
|
|
126
|
+
const choice = await this.options.ui.select(`${profile.name} · ${GROUP[profile.source]}`, [...(own ? [AGENT_ACTIONS.edit] : []), AGENT_ACTIONS.copy, ...(own ? [AGENT_ACTIONS.remove] : [])]);
|
|
127
|
+
if (choice === AGENT_ACTIONS.edit && profile.path)
|
|
128
|
+
return this.attempt(() => this.open(profile.path));
|
|
129
|
+
if (choice === AGENT_ACTIONS.copy)
|
|
130
|
+
return this.attempt(() => this.create(profile.name));
|
|
131
|
+
if (choice === AGENT_ACTIONS.remove && own)
|
|
132
|
+
return this.remove(profile.name, own);
|
|
133
|
+
}
|
|
134
|
+
async remove(name, scope) {
|
|
135
|
+
if (!await this.options.ui.confirm(`Delete ${name}?`, `This removes your ${scope} profile file. A built-in profile of the same name comes back.`))
|
|
136
|
+
return;
|
|
137
|
+
return this.attempt(async () => {
|
|
138
|
+
await this.options.call('terminal.agents.remove', { name, scope });
|
|
139
|
+
return `Deleted ${name}.`;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
handleMouse(event) {
|
|
143
|
+
return this.list.handleMouse(event);
|
|
144
|
+
}
|
|
145
|
+
handleInput(data) {
|
|
146
|
+
if (this.list.handleInput(data)) {
|
|
147
|
+
this.options.changed();
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
render(width, rows) {
|
|
153
|
+
const theme = this.options.theme();
|
|
154
|
+
if (width < 1 || rows < 1)
|
|
155
|
+
return [];
|
|
156
|
+
if (this.error)
|
|
157
|
+
return [theme.fg('error', `Could not read agent profiles: ${this.error}`)];
|
|
158
|
+
if (!this.overview)
|
|
159
|
+
return [theme.fg('dim', 'Reading agent profiles…')];
|
|
160
|
+
const header = headerRow('Agents', `${this.overview.profiles.length} profiles`, width, theme);
|
|
161
|
+
const bodyRows = Math.max(0, rows - 7);
|
|
162
|
+
const listRows = Math.max(1, Math.min(this.list.height, Math.floor(bodyRows * 0.55)));
|
|
163
|
+
const output = [header, '', ...this.list.render(width, listRows), ''];
|
|
164
|
+
const selected = this.list.selected?.data;
|
|
165
|
+
const textWidth = Math.max(1, width - 4);
|
|
166
|
+
output.push(neutralRule(width, theme));
|
|
167
|
+
if (selected?.kind === 'profile') {
|
|
168
|
+
const profile = selected.profile;
|
|
169
|
+
const field = (label, value) => padded(`${theme.fg('muted', `${label}: `)}${theme.fg('text', clean(value))}`, width);
|
|
170
|
+
output.push(padded(theme.fg('accent', `${profile.name} · ${profile.color}`), width));
|
|
171
|
+
output.push(...wrapTextWithAnsi(theme.fg('text', clean(profile.description)), textWidth));
|
|
172
|
+
output.push(field('Tools', profile.tools?.join(', ') ?? 'all of the parent\'s'));
|
|
173
|
+
output.push(field('Plugins', profile.plugins?.join(', ') ?? 'all of the parent\'s'));
|
|
174
|
+
output.push(field('Variant', formatVariant(profile.variant) ?? 'the parent\'s'));
|
|
175
|
+
output.push(field('Model', [profile.model ?? 'the parent\'s', profile.effort ? `effort ${profile.effort}` : null].filter(Boolean).join(' · ')));
|
|
176
|
+
output.push(field('File', profile.path ?? 'built in'));
|
|
177
|
+
output.push(padded(theme.fg('accent', 'Instructions'), width));
|
|
178
|
+
output.push(...profile.instructions.split('\n').flatMap(line => wrapTextWithAnsi(theme.fg('muted', clean(line)), textWidth)));
|
|
179
|
+
}
|
|
180
|
+
else if (selected?.kind === 'problem') {
|
|
181
|
+
output.push(padded(theme.fg('error', selected.problem.error), width));
|
|
182
|
+
output.push(padded(theme.fg('muted', selected.problem.path), width));
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
const dirs = this.overview.dirs;
|
|
186
|
+
output.push(padded(theme.fg('muted', `Your profiles live in ${dirs.global}${dirs.project ? ` and ${dirs.project}` : ''}. A project profile wins over a global one, and both over a built-in one.`), width));
|
|
187
|
+
}
|
|
188
|
+
return output.slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The annotate pane (`/plugin:annotate`, PROP-0018): the person works in a browser window or an app's own
|
|
3
|
+
* windows, and this page is the panel beside it. With the inspector on, hovering outlines a
|
|
4
|
+
* component and a click opens a comment popover with three choices: keep it as a note, add it to
|
|
5
|
+
* this session's queue, or send it to the agent now. The page lists the queue, what was sent and
|
|
6
|
+
* what the agent says it fixed, and the notes kept on the side.
|
|
7
|
+
*/
|
|
8
|
+
import type { Theme } from '@earendil-works/pi-coding-agent';
|
|
9
|
+
import type { MenuCall, MenuUi } from "../../pluginMenu.js";
|
|
10
|
+
import type { TabInfo } from "../chrome.js";
|
|
11
|
+
import { type ListRow } from "../list.js";
|
|
12
|
+
import type { TerminalMouseInput } from "../mouse.js";
|
|
13
|
+
import type { TerminalPage } from "../page.js";
|
|
14
|
+
import type { Note, NoteTarget } from "../../../annotations/store.js";
|
|
15
|
+
export type AnnotateStart = {
|
|
16
|
+
kind: 'web';
|
|
17
|
+
url?: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'cdp';
|
|
20
|
+
endpoint: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'bridge';
|
|
24
|
+
name?: string;
|
|
25
|
+
};
|
|
26
|
+
export interface AnnotatePageOptions {
|
|
27
|
+
theme(): Theme;
|
|
28
|
+
call: MenuCall;
|
|
29
|
+
ui: MenuUi & {
|
|
30
|
+
input(title: string, placeholder?: string): Promise<string | undefined>;
|
|
31
|
+
};
|
|
32
|
+
changed(): void;
|
|
33
|
+
start: AnnotateStart;
|
|
34
|
+
/** How often the inspector is asked for the person's next comment, in milliseconds. */
|
|
35
|
+
pollMs?: number;
|
|
36
|
+
}
|
|
37
|
+
type Row = {
|
|
38
|
+
kind: 'note';
|
|
39
|
+
note: Note;
|
|
40
|
+
};
|
|
41
|
+
export declare const ANNOTATE_ACTIONS: {
|
|
42
|
+
readonly reveal: "Show it in the window";
|
|
43
|
+
readonly send: "Send to the agent now";
|
|
44
|
+
readonly confirm: "Confirm sending it (it came from the page)";
|
|
45
|
+
readonly queue: "Ask the agent to fix it (add to queue)";
|
|
46
|
+
readonly dequeue: "Take it out of the queue";
|
|
47
|
+
readonly edit: "Edit the comment";
|
|
48
|
+
readonly resolve: "Resolve";
|
|
49
|
+
readonly delete: "Delete";
|
|
50
|
+
};
|
|
51
|
+
export declare function describeTarget(target: NoteTarget): string;
|
|
52
|
+
/** Which section of the panel a note belongs in, for this session; null when it is not shown. */
|
|
53
|
+
export declare function section(note: Note, sessionId: string | null): 'Queue' | 'Sent and fixed' | 'Saved' | null;
|
|
54
|
+
export declare class AnnotatePage implements TerminalPage {
|
|
55
|
+
private readonly options;
|
|
56
|
+
presentation: "fullscreen";
|
|
57
|
+
private notes;
|
|
58
|
+
private sessionId;
|
|
59
|
+
private target;
|
|
60
|
+
private bridgeSnippet;
|
|
61
|
+
private error;
|
|
62
|
+
private busy;
|
|
63
|
+
private disposed;
|
|
64
|
+
private polling;
|
|
65
|
+
private timer;
|
|
66
|
+
private refreshes;
|
|
67
|
+
private readonly list;
|
|
68
|
+
constructor(options: AnnotatePageOptions);
|
|
69
|
+
get focused(): boolean;
|
|
70
|
+
set focused(value: boolean);
|
|
71
|
+
tabs(): readonly TabInfo[];
|
|
72
|
+
activeTab(): string;
|
|
73
|
+
hint(): string;
|
|
74
|
+
activate(): void;
|
|
75
|
+
dispose(): void;
|
|
76
|
+
private start;
|
|
77
|
+
private refreshTarget;
|
|
78
|
+
reload(): Promise<void>;
|
|
79
|
+
rows(): ListRow<Row>[];
|
|
80
|
+
private queued;
|
|
81
|
+
/** Polls the inspector for the person's next comment, and the notes every few seconds. */
|
|
82
|
+
private schedule;
|
|
83
|
+
private tick;
|
|
84
|
+
private toggleInspect;
|
|
85
|
+
private attempt;
|
|
86
|
+
sendQueue(): Promise<void>;
|
|
87
|
+
private chooseWindow;
|
|
88
|
+
private noteMenu;
|
|
89
|
+
handleMouse(event: TerminalMouseInput): import("@earendil-works/pi-tui").TuiMouseEventResult | undefined;
|
|
90
|
+
handleInput(data: string): boolean;
|
|
91
|
+
private header;
|
|
92
|
+
render(width: number, rows: number): string[];
|
|
93
|
+
}
|
|
94
|
+
export {};
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { truncateToWidth } from '@earendil-works/pi-tui';
|
|
2
|
+
import { FilterList } from "../list.js";
|
|
3
|
+
const TABS = [{ id: 'annotate', title: 'Annotate' }];
|
|
4
|
+
export const ANNOTATE_ACTIONS = {
|
|
5
|
+
reveal: 'Show it in the window',
|
|
6
|
+
send: 'Send to the agent now',
|
|
7
|
+
confirm: 'Confirm sending it (it came from the page)',
|
|
8
|
+
queue: 'Ask the agent to fix it (add to queue)',
|
|
9
|
+
dequeue: 'Take it out of the queue',
|
|
10
|
+
edit: 'Edit the comment',
|
|
11
|
+
resolve: 'Resolve',
|
|
12
|
+
delete: 'Delete'
|
|
13
|
+
};
|
|
14
|
+
const clip = (text, size) => (text.length > size ? `${text.slice(0, size - 1)}…` : text);
|
|
15
|
+
export function describeTarget(target) {
|
|
16
|
+
if (target.kind === 'text')
|
|
17
|
+
return `“${clip(target.exact.replace(/\s+/g, ' '), 50)}”`;
|
|
18
|
+
if (target.kind === 'element')
|
|
19
|
+
return `<${target.tag}> ${clip(target.name || target.text || target.selector, 40)}`;
|
|
20
|
+
return 'whole page';
|
|
21
|
+
}
|
|
22
|
+
/** Which section of the panel a note belongs in, for this session; null when it is not shown. */
|
|
23
|
+
export function section(note, sessionId) {
|
|
24
|
+
if (note.status === 'resolved')
|
|
25
|
+
return null;
|
|
26
|
+
const delivery = note.delivery;
|
|
27
|
+
if (delivery && delivery.sessionId === sessionId)
|
|
28
|
+
return delivery.state === 'queued' ? 'Queue' : 'Sent and fixed';
|
|
29
|
+
if (!delivery)
|
|
30
|
+
return 'Saved';
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
export class AnnotatePage {
|
|
34
|
+
options;
|
|
35
|
+
presentation = 'fullscreen';
|
|
36
|
+
notes;
|
|
37
|
+
sessionId = null;
|
|
38
|
+
target = null;
|
|
39
|
+
bridgeSnippet = null;
|
|
40
|
+
error;
|
|
41
|
+
busy = false;
|
|
42
|
+
disposed = false;
|
|
43
|
+
polling = false;
|
|
44
|
+
timer = null;
|
|
45
|
+
refreshes = 0;
|
|
46
|
+
list;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.options = options;
|
|
49
|
+
this.list = new FilterList({
|
|
50
|
+
theme: options.theme, search: 'never', noun: ['note', 'notes'], empty: 'Nothing here yet. Press i and click a component in the window.',
|
|
51
|
+
onActivate: row => { if (row.data)
|
|
52
|
+
void this.noteMenu(row.data.note); }
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
get focused() { return this.list.focused; }
|
|
56
|
+
set focused(value) { this.list.focused = value; }
|
|
57
|
+
tabs() { return TABS; }
|
|
58
|
+
activeTab() { return 'annotate'; }
|
|
59
|
+
hint() {
|
|
60
|
+
return `${this.target?.inspecting ? 'i stop inspecting' : 'i inspect'} · s send queue · x unqueue · t window · Enter note · Esc close`;
|
|
61
|
+
}
|
|
62
|
+
activate() {
|
|
63
|
+
void this.start();
|
|
64
|
+
}
|
|
65
|
+
dispose() {
|
|
66
|
+
this.disposed = true;
|
|
67
|
+
if (this.timer)
|
|
68
|
+
clearTimeout(this.timer);
|
|
69
|
+
this.timer = null;
|
|
70
|
+
void this.options.call('terminal.annotations.stopInspect').catch(() => undefined);
|
|
71
|
+
}
|
|
72
|
+
async start() {
|
|
73
|
+
const { call } = this.options;
|
|
74
|
+
const start = this.options.start;
|
|
75
|
+
await this.attempt(async () => {
|
|
76
|
+
if (start.kind === 'web') {
|
|
77
|
+
const url = start.url ?? await this.options.ui.input('Page or dev server address', 'http://localhost:5173');
|
|
78
|
+
if (!url)
|
|
79
|
+
return;
|
|
80
|
+
await call('terminal.annotations.open', { url });
|
|
81
|
+
}
|
|
82
|
+
else if (start.kind === 'cdp') {
|
|
83
|
+
await call('terminal.annotations.attach', { kind: 'cdp', endpoint: start.endpoint, ...(start.name ? { name: start.name } : {}) });
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const attached = await call('terminal.annotations.attach', { kind: 'bridge', ...(start.name ? { name: start.name } : {}) });
|
|
87
|
+
this.bridgeSnippet = attached.bridge?.snippet ?? null;
|
|
88
|
+
}
|
|
89
|
+
await this.refreshTarget();
|
|
90
|
+
if (this.target?.windows.length || start.kind !== 'bridge')
|
|
91
|
+
await this.toggleInspect(true);
|
|
92
|
+
return start.kind === 'bridge' && !this.target?.windows.length ? 'Load the bridge script in the app (shown at the top), then reload the app.' : undefined;
|
|
93
|
+
});
|
|
94
|
+
this.schedule();
|
|
95
|
+
}
|
|
96
|
+
async refreshTarget() {
|
|
97
|
+
this.target = await this.options.call('terminal.annotations.target');
|
|
98
|
+
}
|
|
99
|
+
async reload() {
|
|
100
|
+
try {
|
|
101
|
+
const result = await this.options.call('terminal.annotations.list');
|
|
102
|
+
this.notes = result.notes;
|
|
103
|
+
this.sessionId = result.sessionId;
|
|
104
|
+
this.error = undefined;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
this.error = error instanceof Error ? error.message : String(error);
|
|
108
|
+
}
|
|
109
|
+
if (this.disposed)
|
|
110
|
+
return;
|
|
111
|
+
this.list.setRows(this.rows());
|
|
112
|
+
this.options.changed();
|
|
113
|
+
}
|
|
114
|
+
rows() {
|
|
115
|
+
const order = ['Queue', 'Sent and fixed', 'Saved'];
|
|
116
|
+
const rows = [];
|
|
117
|
+
for (const group of order) {
|
|
118
|
+
for (const note of (this.notes ?? []).filter(entry => section(entry, this.sessionId) === group)) {
|
|
119
|
+
const delivery = note.delivery;
|
|
120
|
+
const state = !delivery ? describeTarget(note.target)
|
|
121
|
+
: delivery.state === 'queued' ? `${describeTarget(note.target)}${delivery.confirm ? ' · confirm to send' : delivery.autoSend ? ' · sends when idle' : ''}`
|
|
122
|
+
: delivery.state === 'fixed' ? `fixed: ${clip(delivery.summary ?? '', 50)}` : `sent · ${describeTarget(note.target)}`;
|
|
123
|
+
rows.push({
|
|
124
|
+
id: `note:${note.id}`, label: clip(note.body.replace(/\s+/g, ' '), 60), group: group === 'Queue' ? `Queue (${this.queued().length})` : group,
|
|
125
|
+
detail: state,
|
|
126
|
+
tone: delivery?.state === 'fixed' ? 'success' : delivery?.confirm ? 'warning' : delivery?.state === 'queued' ? 'accent' : 'muted',
|
|
127
|
+
data: { kind: 'note', note }
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return rows;
|
|
132
|
+
}
|
|
133
|
+
queued() {
|
|
134
|
+
return (this.notes ?? []).filter(note => section(note, this.sessionId) === 'Queue');
|
|
135
|
+
}
|
|
136
|
+
/** Polls the inspector for the person's next comment, and the notes every few seconds. */
|
|
137
|
+
schedule() {
|
|
138
|
+
if (this.disposed)
|
|
139
|
+
return;
|
|
140
|
+
this.timer = setTimeout(() => void this.tick(), this.options.pollMs ?? 400);
|
|
141
|
+
this.timer.unref?.();
|
|
142
|
+
}
|
|
143
|
+
async tick() {
|
|
144
|
+
if (this.disposed)
|
|
145
|
+
return;
|
|
146
|
+
if (!this.polling) {
|
|
147
|
+
this.polling = true;
|
|
148
|
+
try {
|
|
149
|
+
let changed = ++this.refreshes % 5 === 0;
|
|
150
|
+
if (this.target?.inspecting) {
|
|
151
|
+
const step = await this.options.call('terminal.annotations.next');
|
|
152
|
+
if (step.message)
|
|
153
|
+
this.options.ui.notify(step.message, 'info');
|
|
154
|
+
if (step.note)
|
|
155
|
+
changed = true;
|
|
156
|
+
if (!step.active) {
|
|
157
|
+
await this.refreshTarget();
|
|
158
|
+
this.options.changed();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else if (this.target?.kind === 'bridge' && !this.target.windows.length) {
|
|
162
|
+
// Waiting for the app to load the bridge script.
|
|
163
|
+
await this.refreshTarget();
|
|
164
|
+
if (this.target.windows.length) {
|
|
165
|
+
await this.toggleInspect(true);
|
|
166
|
+
changed = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (changed)
|
|
170
|
+
await this.reload();
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
this.error = error instanceof Error ? error.message : String(error);
|
|
174
|
+
this.options.changed();
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
this.polling = false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
this.schedule();
|
|
181
|
+
}
|
|
182
|
+
async toggleInspect(on) {
|
|
183
|
+
await this.options.call(on ? 'terminal.annotations.inspect' : 'terminal.annotations.stopInspect');
|
|
184
|
+
await this.refreshTarget();
|
|
185
|
+
this.options.changed();
|
|
186
|
+
}
|
|
187
|
+
async attempt(work) {
|
|
188
|
+
if (this.busy)
|
|
189
|
+
return;
|
|
190
|
+
this.busy = true;
|
|
191
|
+
try {
|
|
192
|
+
const message = await work();
|
|
193
|
+
if (message)
|
|
194
|
+
this.options.ui.notify(message, 'info');
|
|
195
|
+
this.error = undefined;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
this.options.ui.notify(error instanceof Error ? error.message : String(error), 'error');
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
this.busy = false;
|
|
202
|
+
}
|
|
203
|
+
await this.reload();
|
|
204
|
+
}
|
|
205
|
+
sendQueue() {
|
|
206
|
+
return this.attempt(async () => {
|
|
207
|
+
const waiting = this.queued();
|
|
208
|
+
if (!waiting.length)
|
|
209
|
+
return 'The queue is empty.';
|
|
210
|
+
const unconfirmed = waiting.filter(note => note.delivery?.confirm);
|
|
211
|
+
if (unconfirmed.length) {
|
|
212
|
+
if (!await this.options.ui.confirm(`Send ${waiting.length} note(s) to the agent?`, `${unconfirmed.length} came from the page's own scripts. Check that you wrote them.`))
|
|
213
|
+
return;
|
|
214
|
+
for (const note of unconfirmed)
|
|
215
|
+
await this.options.call('terminal.annotations.confirm', { id: note.id, expectedVersion: note.version });
|
|
216
|
+
}
|
|
217
|
+
const result = await this.options.call('terminal.annotations.send');
|
|
218
|
+
return result.state === 'sent' ? `Sent ${result.ids.length} note(s) to the agent.` : result.state === 'busy' ? 'The agent is working; the queue goes out when it finishes.' : 'Nothing was sent.';
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
chooseWindow() {
|
|
222
|
+
return this.attempt(async () => {
|
|
223
|
+
await this.refreshTarget();
|
|
224
|
+
const windows = this.target?.windows ?? [];
|
|
225
|
+
if (!windows.length)
|
|
226
|
+
return 'No window is open.';
|
|
227
|
+
const labels = windows.map(window => `${window.current ? '● ' : ''}${clip(window.title || window.url, 60)} — ${clip(window.url, 50)}`);
|
|
228
|
+
const choice = await this.options.ui.select('Inspect which window?', labels);
|
|
229
|
+
const index = choice ? labels.indexOf(choice) : -1;
|
|
230
|
+
if (index < 0)
|
|
231
|
+
return;
|
|
232
|
+
await this.options.call('terminal.annotations.selectWindow', { window: windows[index].id });
|
|
233
|
+
await this.refreshTarget();
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
async noteMenu(note) {
|
|
237
|
+
const { call, ui } = this.options;
|
|
238
|
+
const where = section(note, this.sessionId);
|
|
239
|
+
const choices = [
|
|
240
|
+
ANNOTATE_ACTIONS.reveal,
|
|
241
|
+
...(where === 'Queue' ? [note.delivery?.confirm ? ANNOTATE_ACTIONS.confirm : ANNOTATE_ACTIONS.send, ANNOTATE_ACTIONS.dequeue] : [ANNOTATE_ACTIONS.queue]),
|
|
242
|
+
ANNOTATE_ACTIONS.edit,
|
|
243
|
+
ANNOTATE_ACTIONS.resolve,
|
|
244
|
+
ANNOTATE_ACTIONS.delete
|
|
245
|
+
];
|
|
246
|
+
const choice = await ui.select(`${clip(note.source.app?.name ?? note.source.title ?? note.source.url, 50)} · ${describeTarget(note.target)}`, choices);
|
|
247
|
+
const id = note.id;
|
|
248
|
+
if (choice === ANNOTATE_ACTIONS.reveal)
|
|
249
|
+
return this.attempt(async () => {
|
|
250
|
+
const result = await call('terminal.annotations.reveal', { id });
|
|
251
|
+
return result.state === 'matched' ? 'Marked in the window.' : `The target is ${result.state}${result.reason ? `: ${result.reason}` : ''}.`;
|
|
252
|
+
});
|
|
253
|
+
if (choice === ANNOTATE_ACTIONS.queue)
|
|
254
|
+
return this.attempt(async () => {
|
|
255
|
+
await call('terminal.annotations.queue', { id, expectedVersion: note.version });
|
|
256
|
+
return 'Added to the queue.';
|
|
257
|
+
});
|
|
258
|
+
if (choice === ANNOTATE_ACTIONS.dequeue)
|
|
259
|
+
return this.attempt(async () => {
|
|
260
|
+
await call('terminal.annotations.dequeue', { id, expectedVersion: note.version });
|
|
261
|
+
});
|
|
262
|
+
if (choice === ANNOTATE_ACTIONS.confirm || choice === ANNOTATE_ACTIONS.send)
|
|
263
|
+
return this.attempt(async () => {
|
|
264
|
+
if (choice === ANNOTATE_ACTIONS.confirm)
|
|
265
|
+
await call('terminal.annotations.confirm', { id, expectedVersion: note.version });
|
|
266
|
+
const result = await call('terminal.annotations.send', { ids: [id] });
|
|
267
|
+
return result.state === 'sent' ? 'Sent to the agent.' : result.state === 'busy' ? 'It goes to the agent when it finishes.' : undefined;
|
|
268
|
+
});
|
|
269
|
+
if (choice === ANNOTATE_ACTIONS.edit)
|
|
270
|
+
return this.attempt(async () => {
|
|
271
|
+
const body = await ui.input('Comment', note.body);
|
|
272
|
+
if (!body?.trim() || body === note.body)
|
|
273
|
+
return;
|
|
274
|
+
await call('terminal.annotations.update', { id, expectedVersion: note.version, patch: { body } });
|
|
275
|
+
});
|
|
276
|
+
if (choice === ANNOTATE_ACTIONS.resolve)
|
|
277
|
+
return this.attempt(async () => {
|
|
278
|
+
await call('terminal.annotations.update', { id, expectedVersion: note.version, patch: { status: 'resolved' } });
|
|
279
|
+
return 'Resolved.';
|
|
280
|
+
});
|
|
281
|
+
if (choice === ANNOTATE_ACTIONS.delete)
|
|
282
|
+
return this.attempt(async () => {
|
|
283
|
+
if (!await ui.confirm('Delete this note?', 'It cannot be restored.'))
|
|
284
|
+
return;
|
|
285
|
+
await call('terminal.annotations.delete', { id, expectedVersion: note.version });
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
handleMouse(event) {
|
|
289
|
+
return this.list.handleMouse(event);
|
|
290
|
+
}
|
|
291
|
+
handleInput(data) {
|
|
292
|
+
if (data === 'i') {
|
|
293
|
+
void this.attempt(() => this.toggleInspect(!this.target?.inspecting));
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
if (data === 's') {
|
|
297
|
+
void this.sendQueue();
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
if (data === 't') {
|
|
301
|
+
void this.chooseWindow();
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
if (data === 'x') {
|
|
305
|
+
const row = this.list.selected?.data;
|
|
306
|
+
if (row && section(row.note, this.sessionId) === 'Queue')
|
|
307
|
+
void this.attempt(async () => { await this.options.call('terminal.annotations.dequeue', { id: row.note.id, expectedVersion: row.note.version }); });
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
return this.list.handleInput(data);
|
|
311
|
+
}
|
|
312
|
+
header(width) {
|
|
313
|
+
const theme = this.options.theme();
|
|
314
|
+
const target = this.target;
|
|
315
|
+
const current = target?.windows.find(window => window.current);
|
|
316
|
+
const kind = !target ? 'Starting…' : target.kind === 'web' ? 'Browser window' : target.kind === 'cdp' ? `App · ${target.app?.name ?? ''} (DevTools)` : `App · ${target.app?.name ?? ''} (page bridge)`;
|
|
317
|
+
const lines = [
|
|
318
|
+
`${theme.bold(kind)}${current ? theme.fg('muted', ` · ${clip(current.title || current.url, 40)} · ${clip(current.url, 60)}`) : ''}`,
|
|
319
|
+
target?.inspecting
|
|
320
|
+
? theme.fg('success', '● Inspecting: hover a component in the window and click it to comment.')
|
|
321
|
+
: theme.fg('dim', '○ Inspector off. Press i to turn it on.')
|
|
322
|
+
];
|
|
323
|
+
if (this.bridgeSnippet && target?.kind === 'bridge') {
|
|
324
|
+
lines.push(theme.fg(target.windows.length ? 'dim' : 'warning', `Load in the app while developing: ${this.bridgeSnippet}`));
|
|
325
|
+
}
|
|
326
|
+
if (this.error)
|
|
327
|
+
lines.push(theme.fg('error', this.error));
|
|
328
|
+
lines.push('');
|
|
329
|
+
return lines.map(line => truncateToWidth(line, width, ''));
|
|
330
|
+
}
|
|
331
|
+
render(width, rows) {
|
|
332
|
+
const theme = this.options.theme();
|
|
333
|
+
if (width < 1 || rows < 1)
|
|
334
|
+
return [];
|
|
335
|
+
const header = this.header(width);
|
|
336
|
+
if (!this.notes)
|
|
337
|
+
return [...header, theme.fg('dim', 'Reading page notes…')].slice(0, rows);
|
|
338
|
+
const body = this.list.render(width, Math.max(1, rows - header.length));
|
|
339
|
+
return [...header, ...body].slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
340
|
+
}
|
|
341
|
+
}
|