@xingular/harness 1.2.0 → 1.3.1-unstable.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 +63 -0
- package/README.md +26 -17
- package/builtin/skills/plugin-authoring/SKILL.md +34 -0
- package/dist/cli.js +7362 -6239
- package/dist/sdk/agents/builtin.js +3 -3
- package/dist/sdk/agents/planResearch.d.ts +23 -1
- package/dist/sdk/agents/planResearch.js +71 -8
- package/dist/sdk/agents/profiles.d.ts +16 -4
- package/dist/sdk/agents/profiles.js +75 -12
- package/dist/sdk/annotations/bridge.d.ts +2 -0
- package/dist/sdk/annotations/bridge.js +4 -0
- package/dist/sdk/annotations/capture.d.ts +44 -2
- package/dist/sdk/annotations/capture.js +104 -3
- package/dist/sdk/annotations/delivery.d.ts +24 -17
- package/dist/sdk/annotations/delivery.js +82 -49
- package/dist/sdk/annotations/inspect.js +7 -6
- package/dist/sdk/annotations/pageScript.js +4 -4
- package/dist/sdk/annotations/part.d.ts +0 -2
- package/dist/sdk/annotations/part.js +4 -33
- package/dist/sdk/annotations/queue.d.ts +2 -2
- package/dist/sdk/annotations/queue.js +12 -7
- package/dist/sdk/annotations/serverMethods.js +11 -7
- package/dist/sdk/annotations/store.d.ts +13 -21
- package/dist/sdk/annotations/store.js +47 -37
- package/dist/sdk/capabilities/runSnapshot.js +10 -4
- package/dist/sdk/commands/list.js +2 -0
- package/dist/sdk/commands/registry.d.ts +11 -3
- package/dist/sdk/commands/registry.js +3 -2
- package/dist/sdk/core/links.d.ts +51 -0
- package/dist/sdk/core/links.js +155 -0
- package/dist/sdk/core/shell.d.ts +20 -0
- package/dist/sdk/core/shell.js +73 -0
- package/dist/sdk/core/workspaceBashTool.d.ts +17 -1
- package/dist/sdk/core/workspaceBashTool.js +16 -2
- package/dist/sdk/core/workspaceSandbox.d.ts +10 -0
- package/dist/sdk/core/workspaceSandbox.js +52 -12
- package/dist/sdk/core/workspaceWriteOperations.d.ts +11 -0
- package/dist/sdk/core/workspaceWriteOperations.js +26 -8
- package/dist/sdk/doctor/checks.js +71 -12
- package/dist/sdk/harness.d.ts +5 -0
- package/dist/sdk/harness.js +11 -6
- package/dist/sdk/hub/hub.js +5 -3
- package/dist/sdk/index.d.ts +3 -2
- package/dist/sdk/index.js +2 -1
- package/dist/sdk/plugins/builtin.js +14 -9
- package/dist/sdk/plugins/builtinRegistry.d.ts +3 -1
- package/dist/sdk/plugins/builtinRegistry.js +9 -3
- package/dist/sdk/plugins/discover.d.ts +7 -1
- package/dist/sdk/plugins/discover.js +41 -2
- package/dist/sdk/plugins/identity.js +1 -1
- package/dist/sdk/plugins/install.d.ts +6 -2
- package/dist/sdk/plugins/install.js +29 -5
- package/dist/sdk/plugins/inventory.js +3 -3
- package/dist/sdk/plugins/manifest.d.ts +2 -0
- package/dist/sdk/plugins/manifest.js +21 -4
- package/dist/sdk/plugins/plugins.d.ts +2 -0
- package/dist/sdk/plugins/plugins.js +1 -0
- package/dist/sdk/plugins/proposals.d.ts +17 -0
- package/dist/sdk/plugins/proposals.js +147 -0
- package/dist/sdk/plugins/types.d.ts +1 -1
- package/dist/sdk/plugins/types.js +1 -1
- package/dist/sdk/prompt/compile.d.ts +13 -4
- package/dist/sdk/prompt/compile.js +46 -20
- package/dist/sdk/prompt/standard.d.ts +1 -1
- package/dist/sdk/prompt/standard.js +1 -1
- package/dist/sdk/server/methods.js +14 -2
- package/dist/sdk/sessions/compactionCheckpoint.d.ts +3 -3
- package/dist/sdk/sessions/compactionCheckpoint.js +12 -12
- package/dist/sdk/sessions/continuation.d.ts +4 -2
- package/dist/sdk/sessions/continuation.js +7 -6
- package/dist/sdk/sessions/contracts.d.ts +22 -4
- package/dist/sdk/sessions/contracts.js +12 -7
- package/dist/sdk/sessions/planMode.js +2 -2
- package/dist/sdk/sessions/runStore.js +3 -1
- package/dist/sdk/sessions/runner.js +2 -2
- package/dist/sdk/sessions/sessionState.d.ts +13 -0
- package/dist/sdk/sessions/sessionState.js +31 -0
- package/dist/sdk/sessions/sessions.d.ts +7 -0
- package/dist/sdk/sessions/sessions.js +73 -15
- package/dist/sdk/sessions/subagentService.d.ts +15 -0
- package/dist/sdk/sessions/subagentService.js +195 -55
- package/dist/sdk/sessions/subagents.d.ts +10 -1
- package/dist/sdk/sessions/subagents.js +4 -0
- package/dist/sdk/sessions/workSlice.d.ts +8 -5
- package/dist/sdk/sessions/workSlice.js +77 -45
- package/dist/sdk/terminal/controller.d.ts +20 -0
- package/dist/sdk/terminal/controller.js +43 -18
- package/dist/sdk/terminal/opentui/ansi.d.ts +25 -0
- package/dist/sdk/terminal/opentui/ansi.js +192 -0
- package/dist/sdk/terminal/opentui/model.d.ts +163 -0
- package/dist/sdk/terminal/opentui/model.js +482 -0
- package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +3 -3
- package/dist/sdk/terminal/opentui/subagentsWidget.js +7 -5
- package/dist/sdk/terminal/opentui/theme.d.ts +2 -0
- package/dist/sdk/terminal/opentui/theme.js +2 -0
- package/dist/sdk/terminal/piExtension.js +98 -15
- package/dist/sdk/terminal/subagentMonitor.d.ts +17 -4
- package/dist/sdk/terminal/subagentMonitor.js +29 -8
- package/dist/sdk/terminal/terminals.d.ts +16 -0
- package/dist/sdk/terminal/terminals.js +130 -26
- package/dist/sdk/terminal/trajectory.d.ts +6 -1
- package/dist/sdk/terminal/trajectory.js +8 -0
- package/dist/sdk/terminal/tui/annotateCommand.d.ts +5 -1
- package/dist/sdk/terminal/tui/annotateCommand.js +21 -7
- package/dist/sdk/terminal/tui/annotateSend.d.ts +30 -0
- package/dist/sdk/terminal/tui/annotateSend.js +40 -0
- package/dist/sdk/terminal/tui/chrome.d.ts +4 -0
- package/dist/sdk/terminal/tui/chrome.js +13 -0
- package/dist/sdk/terminal/tui/commandPages.d.ts +1 -1
- package/dist/sdk/terminal/tui/commandPages.js +16 -11
- package/dist/sdk/terminal/tui/commands.d.ts +4 -0
- package/dist/sdk/terminal/tui/commands.js +12 -2
- package/dist/sdk/terminal/tui/dialogs.js +1 -0
- package/dist/sdk/terminal/tui/pageOverlay.d.ts +3 -0
- package/dist/sdk/terminal/tui/pageOverlay.js +25 -7
- package/dist/sdk/terminal/tui/pages/agentsPage.js +5 -2
- package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +70 -16
- package/dist/sdk/terminal/tui/pages/annotatePage.js +349 -115
- package/dist/sdk/terminal/tui/pages/browserPage.js +5 -4
- package/dist/sdk/terminal/tui/pages/hubPage.d.ts +14 -1
- package/dist/sdk/terminal/tui/pages/hubPage.js +192 -28
- package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +14 -1
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +295 -25
- package/dist/sdk/terminal/tui/pages/subagentsPage.js +8 -5
- package/dist/sdk/terminal/tui/pages/usagePage.d.ts +4 -2
- package/dist/sdk/terminal/tui/pages/usagePage.js +39 -12
- package/dist/sdk/terminal/tui/pluginCommands.d.ts +1 -1
- package/dist/sdk/terminal/tui/pluginCommands.js +13 -3
- package/dist/sdk/tools/defaultSkills.d.ts +4 -0
- package/dist/sdk/tools/defaultSkills.js +20 -0
- package/dist/sdk/tools/definitions.d.ts +3 -1
- package/dist/sdk/tools/definitions.js +17 -1
- package/dist/sdk/tools/dispatch.js +12 -0
- package/dist/sdk/tools/harnessTools.js +1 -1
- package/dist/sdk/tools/sessionName.d.ts +9 -0
- package/dist/sdk/tools/sessionName.js +58 -11
- package/dist/sdk/tools/spawnSubagent.js +9 -7
- package/dist/sdk/tools/todo.d.ts +15 -0
- package/dist/sdk/tools/todo.js +64 -0
- package/dist/sdk/world/world.d.ts +0 -2
- package/dist/terminal-opentui.js +3331 -1584
- package/dist/terminal-pi.js +421 -133
- package/docs/README.md +3 -0
- package/docs/architecture/00-overview.md +1 -1
- package/docs/architecture/01-extension-points.md +1 -1
- package/docs/architecture/02-sessions-and-runs.md +73 -36
- package/docs/architecture/03-variants.md +1 -3
- package/docs/architecture/04-prompt.md +24 -13
- package/docs/architecture/06-plugins-and-capabilities.md +46 -10
- package/docs/architecture/07-tools-and-gate.md +49 -8
- package/docs/architecture/09-accounts-and-connectors.md +8 -2
- package/docs/architecture/10-server-protocol.md +68 -16
- package/docs/architecture/11-terminal.md +94 -45
- package/docs/architecture/12-storage.md +1 -0
- package/docs/architecture/13-world-tools.md +8 -6
- package/docs/architecture/14-surfaces-and-parity.md +4 -1
- package/docs/architecture/README.md +20 -18
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +2 -0
- package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +6 -2
- package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +32 -21
- package/docs/architecture/proposals/PROP-0044-work-slice-defaults.md +93 -0
- package/docs/architecture/proposals/PROP-0045-rename-checklist-to-todo.md +132 -0
- package/docs/architecture/proposals/PROP-0046-progress-aware-stall-guards.md +82 -0
- package/docs/architecture/proposals/PROP-0047-plugins-hub-separation.md +122 -0
- package/docs/architecture/proposals/PROP-0048-simple-annotate.md +85 -0
- package/docs/architecture/proposals/README.md +11 -6
- package/docs/decisions/ADR-0039-progress-aware-stall-guards.md +39 -0
- package/docs/decisions/ADR-0040-architecture-change-workflow.md +31 -0
- package/docs/decisions/ADR-0041-plugins-hub-separation.md +41 -0
- package/docs/decisions/ADR-0042-windows-shell-without-sandbox.md +40 -0
- package/docs/decisions/ADR-0043-parents-continue-stopped-subagents.md +49 -0
- package/docs/decisions/ADR-0044-annotations-pending-or-sent.md +24 -0
- package/docs/decisions/ADR-0045-default-plugin-authoring-skill.md +9 -0
- package/docs/decisions/ADR-0046-hash-bound-plugin-proposals.md +20 -0
- package/docs/decisions/ADR-0047-links-open-with-the-os-handler.md +47 -0
- package/docs/doctor.md +14 -1
- package/docs/parity.md +16 -13
- package/docs/releases/1.3.0.md +21 -0
- package/docs/releases/1.3.1-unstable.1.md +8 -0
- package/docs/releases/README.md +9 -6
- package/docs/terminal-tui.md +127 -65
- package/package.json +1 -1
- package/dist/sdk/terminal/tui/pages/notesPage.d.ts +0 -75
- package/dist/sdk/terminal/tui/pages/notesPage.js +0 -201
- package/dist/sdk/tools/checklist.d.ts +0 -9
- package/dist/sdk/tools/checklist.js +0 -55
|
@@ -6,28 +6,42 @@
|
|
|
6
6
|
import { describePlugin, surfaceBadge } from "../../../plugins/surfaces.js";
|
|
7
7
|
import { matchesKey, truncateToWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
8
8
|
import { BACK, applyPluginAction, menuRows, rowActions } from "../../pluginMenu.js";
|
|
9
|
-
import { safeText } from "../chrome.js";
|
|
9
|
+
import { inspectorListWidth, safeText, splitColumns } from "../chrome.js";
|
|
10
10
|
import { FilterList } from "../list.js";
|
|
11
11
|
const SECTIONS = [
|
|
12
12
|
{ id: 'plugins', title: 'Plugins' },
|
|
13
|
-
{ id: 'trust', title: 'Trust' }
|
|
13
|
+
{ id: 'trust', title: 'Trust' },
|
|
14
|
+
{ id: 'packages', title: 'Packages' },
|
|
15
|
+
{ id: 'proposals', title: 'Proposals' }
|
|
14
16
|
];
|
|
15
17
|
const SETTINGS_ROW = 'settings:';
|
|
18
|
+
const PACKAGE_ROW = 'package:';
|
|
19
|
+
const INSTALL_PACKAGE = 'action:install-package';
|
|
16
20
|
const isSwitch = (setting) => setting.min === 0 && setting.max === 1;
|
|
17
21
|
const settingValueText = (setting) => isSwitch(setting)
|
|
18
22
|
? setting.value === 1 ? 'on' : 'off'
|
|
19
23
|
: `${setting.value.toLocaleString('en-US')}${setting.value === setting.default ? ' · default' : ''}`;
|
|
20
24
|
const settingsOf = (plugin) => plugin.settings ?? [];
|
|
25
|
+
// Never execute or silently hide terminal controls in code being reviewed.
|
|
26
|
+
const reviewText = (line) => line.replace(/[\x00-\x1f\x7f-\x9f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, char => `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
21
27
|
export class PluginsPage {
|
|
22
28
|
options;
|
|
29
|
+
presentation = 'fullscreen';
|
|
23
30
|
plugins;
|
|
31
|
+
proposals = [];
|
|
32
|
+
proposalError;
|
|
33
|
+
preview;
|
|
34
|
+
previewOffset = 0;
|
|
35
|
+
previewRows = 1;
|
|
24
36
|
section = 'plugins';
|
|
25
37
|
projectId = null;
|
|
38
|
+
hubManagedIds = new Set();
|
|
26
39
|
error;
|
|
27
40
|
restart = false;
|
|
28
41
|
busy = false;
|
|
29
42
|
disposed = false;
|
|
30
43
|
list;
|
|
44
|
+
pointerLayout;
|
|
31
45
|
/** The plugin whose settings are open, or null on the list. */
|
|
32
46
|
settingsFor = null;
|
|
33
47
|
saved = false;
|
|
@@ -57,6 +71,8 @@ export class PluginsPage {
|
|
|
57
71
|
}
|
|
58
72
|
activeTab() { return this.settingsPlugin() ? 'settings' : this.section; }
|
|
59
73
|
hint() {
|
|
74
|
+
if (this.preview)
|
|
75
|
+
return this.preview.status === 'pending' ? '↑↓ scroll · PgUp/PgDn page · Enter decide · Esc back' : '↑↓ scroll · PgUp/PgDn page · Esc back';
|
|
60
76
|
if (this.settingsPlugin())
|
|
61
77
|
return this.settingsList.query ? 'Enter change · Esc clear' : 'Enter change · Esc back';
|
|
62
78
|
return this.list.query ? 'Enter actions · Esc clear' : 'Enter actions · type to search';
|
|
@@ -70,6 +86,20 @@ export class PluginsPage {
|
|
|
70
86
|
this.projectId = (await call('plugins.session').catch(() => ({ projectId: null }))).projectId;
|
|
71
87
|
this.plugins = await call('plugins.list');
|
|
72
88
|
this.error = undefined;
|
|
89
|
+
try {
|
|
90
|
+
this.proposals = await call('plugins.proposals.list');
|
|
91
|
+
this.proposalError = undefined;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
this.proposals = [];
|
|
95
|
+
this.proposalError = error instanceof Error ? error.message : String(error);
|
|
96
|
+
}
|
|
97
|
+
this.hubManagedIds = new Set();
|
|
98
|
+
try {
|
|
99
|
+
const overview = await call('hub.list');
|
|
100
|
+
this.hubManagedIds = new Set(overview.installed.filter(receipt => receipt.entry.delivery.kind === 'pi-package').flatMap(receipt => receipt.resourceIds));
|
|
101
|
+
}
|
|
102
|
+
catch { /* Package actions still go through the receipt-aware Harness service. */ }
|
|
73
103
|
}
|
|
74
104
|
catch (error) {
|
|
75
105
|
this.error = error instanceof Error ? error.message : String(error);
|
|
@@ -91,8 +121,23 @@ export class PluginsPage {
|
|
|
91
121
|
searchText: `${setting.label} ${setting.description}`
|
|
92
122
|
})));
|
|
93
123
|
}
|
|
94
|
-
/**
|
|
124
|
+
/** Switch, trust, settings and package actions stay grouped under their owning plugin. */
|
|
95
125
|
rows() {
|
|
126
|
+
if (this.section === 'proposals')
|
|
127
|
+
return this.proposals.map(proposal => ({
|
|
128
|
+
id: `proposal:${proposal.id}`, label: safeText(proposal.name), detail: proposal.status,
|
|
129
|
+
tone: proposal.status === 'pending' ? 'warning' : 'muted',
|
|
130
|
+
searchText: `${proposal.name} ${proposal.description} ${proposal.status}`
|
|
131
|
+
}));
|
|
132
|
+
if (this.section === 'packages')
|
|
133
|
+
return [
|
|
134
|
+
...(this.plugins ?? []).filter(plugin => plugin.install).map(plugin => {
|
|
135
|
+
const source = plugin.install.kind === 'folder' ? 'local folder' : 'package';
|
|
136
|
+
const hub = this.hubManagedIds.has(plugin.id);
|
|
137
|
+
return { id: `${PACKAGE_ROW}${plugin.id}`, group: safeText(plugin.title), label: 'Manage package', detail: `${source} · ${hub ? 'Hub' : 'local'} · ${plugin.version ?? plugin.install.source}`, tone: 'muted', searchText: `${plugin.title} ${plugin.install.source} ${plugin.version ?? ''} package ${hub ? 'Hub' : 'local'}` };
|
|
138
|
+
}),
|
|
139
|
+
{ id: INSTALL_PACKAGE, group: 'Packages', label: 'Install package…', detail: 'Folder, npm or Git', tone: 'accent', searchText: 'install add package plugin npm Git folder' }
|
|
140
|
+
];
|
|
96
141
|
const menu = this.menu();
|
|
97
142
|
const out = [];
|
|
98
143
|
const placed = new Set();
|
|
@@ -107,18 +152,19 @@ export class PluginsPage {
|
|
|
107
152
|
if (this.section === 'plugins' && last && settingsOf(row.plugin).length)
|
|
108
153
|
out.push(settingsRow(row.plugin));
|
|
109
154
|
});
|
|
110
|
-
if (this.section === 'plugins')
|
|
155
|
+
if (this.section === 'plugins')
|
|
111
156
|
for (const plugin of this.plugins ?? [])
|
|
112
157
|
if (settingsOf(plugin).length && !placed.has(plugin.id))
|
|
113
158
|
out.push(settingsRow(plugin));
|
|
114
|
-
}
|
|
115
159
|
return out;
|
|
116
160
|
}
|
|
117
161
|
menu() {
|
|
118
162
|
const rows = menuRows(this.plugins ?? [], this.projectId);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
163
|
+
if (this.section === 'trust')
|
|
164
|
+
return rows.filter(row => row.whole && Boolean(row.plugin.code));
|
|
165
|
+
if (this.section === 'packages')
|
|
166
|
+
return [];
|
|
167
|
+
return rows.filter(row => rowActions(row, this.projectId).some(action => action.startsWith('Switch ')));
|
|
122
168
|
}
|
|
123
169
|
key(row) { return `${row.whole ? 'plugin' : 'item'}:${row.id}`; }
|
|
124
170
|
toRow(row) {
|
|
@@ -130,6 +176,24 @@ export class PluginsPage {
|
|
|
130
176
|
};
|
|
131
177
|
}
|
|
132
178
|
async open(id) {
|
|
179
|
+
if (this.section === 'proposals' && id.startsWith('proposal:')) {
|
|
180
|
+
if (this.busy)
|
|
181
|
+
return;
|
|
182
|
+
this.busy = true;
|
|
183
|
+
try {
|
|
184
|
+
this.preview = await this.options.call('plugins.proposals.preview', { id: id.slice('proposal:'.length) });
|
|
185
|
+
this.previewOffset = 0;
|
|
186
|
+
this.pointerLayout = undefined;
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
this.options.ui.notify(error instanceof Error ? error.message : String(error), 'error');
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
this.busy = false;
|
|
193
|
+
this.options.changed();
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
133
197
|
if (id.startsWith(SETTINGS_ROW)) {
|
|
134
198
|
this.settingsFor = id.slice(SETTINGS_ROW.length);
|
|
135
199
|
this.saved = false;
|
|
@@ -137,6 +201,16 @@ export class PluginsPage {
|
|
|
137
201
|
this.options.changed();
|
|
138
202
|
return;
|
|
139
203
|
}
|
|
204
|
+
if (id === INSTALL_PACKAGE) {
|
|
205
|
+
await this.installPackage();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (id.startsWith(PACKAGE_ROW)) {
|
|
209
|
+
const plugin = this.plugins?.find(candidate => candidate.id === id.slice(PACKAGE_ROW.length));
|
|
210
|
+
if (plugin?.install)
|
|
211
|
+
await this.managePackage(plugin);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
140
214
|
const row = this.menu().find(candidate => this.key(candidate) === id);
|
|
141
215
|
if (!row || this.busy)
|
|
142
216
|
return;
|
|
@@ -151,6 +225,104 @@ export class PluginsPage {
|
|
|
151
225
|
}
|
|
152
226
|
await this.reload();
|
|
153
227
|
}
|
|
228
|
+
async decideProposal() {
|
|
229
|
+
const preview = this.preview;
|
|
230
|
+
if (!preview || preview.status !== 'pending' || this.busy)
|
|
231
|
+
return;
|
|
232
|
+
this.busy = true;
|
|
233
|
+
try {
|
|
234
|
+
const action = await this.options.ui.select(`Proposal ${safeText(preview.name)} · ${preview.hash}`, ['Accept proposal', 'Reject proposal', BACK]);
|
|
235
|
+
if (action !== 'Accept proposal' && action !== 'Reject proposal')
|
|
236
|
+
return;
|
|
237
|
+
const accept = action === 'Accept proposal';
|
|
238
|
+
if (!await this.options.ui.confirm(action + '?', accept ? 'Install this reviewed proposal switched off and untrusted? Review and trust code separately.' : 'Reject this reviewed proposal?'))
|
|
239
|
+
return;
|
|
240
|
+
await this.options.call(accept ? 'plugins.proposals.accept' : 'plugins.proposals.reject', { id: preview.id, hash: preview.hash });
|
|
241
|
+
this.preview = undefined;
|
|
242
|
+
if (accept)
|
|
243
|
+
this.restart = true;
|
|
244
|
+
this.options.ui.notify(accept ? 'Accepted switched off and untrusted. Changes apply after restart.' : 'Proposal rejected.', 'info');
|
|
245
|
+
await this.reload();
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
this.preview = undefined;
|
|
249
|
+
this.options.ui.notify(error instanceof Error ? error.message : String(error), 'error');
|
|
250
|
+
await this.reload();
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
this.busy = false;
|
|
254
|
+
if (!this.disposed)
|
|
255
|
+
this.options.changed();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async installPackage() {
|
|
259
|
+
if (this.busy)
|
|
260
|
+
return;
|
|
261
|
+
this.busy = true;
|
|
262
|
+
try {
|
|
263
|
+
const source = await this.options.ui.input('Install local plugin package', 'npm:package@version, Git address, or absolute folder path');
|
|
264
|
+
if (!source?.trim())
|
|
265
|
+
return;
|
|
266
|
+
const confirmed = await this.options.ui.confirm('Install package?', `${source.trim()} will be installed without running package code. It arrives switched off and code remains untrusted until reviewed.`);
|
|
267
|
+
if (!confirmed)
|
|
268
|
+
return;
|
|
269
|
+
await this.options.call('plugins.install', { source: source.trim() });
|
|
270
|
+
this.restart = true;
|
|
271
|
+
this.options.ui.notify('Installed switched off. Changes apply when this session restarts.', 'info');
|
|
272
|
+
await this.reload();
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
this.options.ui.notify(error instanceof Error ? error.message : String(error), 'error');
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
this.busy = false;
|
|
279
|
+
if (!this.disposed)
|
|
280
|
+
this.options.changed();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async managePackage(plugin) {
|
|
284
|
+
if (this.busy || !plugin.install)
|
|
285
|
+
return;
|
|
286
|
+
const hubManaged = this.hubManagedIds.has(plugin.id);
|
|
287
|
+
const actions = [
|
|
288
|
+
...(hubManaged || plugin.install.kind === 'folder' ? [] : ['Update package']),
|
|
289
|
+
hubManaged ? 'Uninstall Hub version' : 'Remove package',
|
|
290
|
+
BACK
|
|
291
|
+
];
|
|
292
|
+
this.busy = true;
|
|
293
|
+
try {
|
|
294
|
+
const action = await this.options.ui.select(`${plugin.title}: ${plugin.install.source}`, actions);
|
|
295
|
+
if (!action || action === BACK)
|
|
296
|
+
return;
|
|
297
|
+
if (action === 'Update package') {
|
|
298
|
+
const confirmed = await this.options.ui.confirm(`Update ${plugin.title}?`, 'Fetches a newer package version without running its scripts. Changed code remains subject to trust review.');
|
|
299
|
+
if (!confirmed)
|
|
300
|
+
return;
|
|
301
|
+
await this.options.call('plugins.update', { pluginId: plugin.id });
|
|
302
|
+
this.options.ui.notify('Updated. Changes apply when this session restarts.', 'info');
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
const title = hubManaged ? `Uninstall ${plugin.title} from Hub?` : `Remove ${plugin.title}?`;
|
|
306
|
+
const message = hubManaged
|
|
307
|
+
? 'This removes the exact Hub receipt and only its recorded package resources.'
|
|
308
|
+
: 'This removes the installed package. A local folder is moved to the Harness trash.';
|
|
309
|
+
if (!await this.options.ui.confirm(title, message))
|
|
310
|
+
return;
|
|
311
|
+
await this.options.call('plugins.remove', { pluginId: plugin.id });
|
|
312
|
+
this.options.ui.notify(hubManaged ? 'Hub package uninstalled.' : 'Package removed.', 'info');
|
|
313
|
+
}
|
|
314
|
+
this.restart = true;
|
|
315
|
+
await this.reload();
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
this.options.ui.notify(error instanceof Error ? error.message : String(error), 'error');
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
this.busy = false;
|
|
322
|
+
if (!this.disposed)
|
|
323
|
+
this.options.changed();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
154
326
|
back() {
|
|
155
327
|
this.settingsFor = null;
|
|
156
328
|
this.saved = false;
|
|
@@ -196,9 +368,30 @@ export class PluginsPage {
|
|
|
196
368
|
}
|
|
197
369
|
}
|
|
198
370
|
handleMouse(event) {
|
|
199
|
-
|
|
371
|
+
if (this.preview)
|
|
372
|
+
return { handled: true, render: false };
|
|
373
|
+
const layout = this.pointerLayout;
|
|
374
|
+
if (!layout)
|
|
375
|
+
return undefined;
|
|
376
|
+
if (event.x < 0 || event.x >= layout.width || event.y < layout.top || event.y >= layout.top + layout.rows)
|
|
377
|
+
return { handled: true, render: false };
|
|
378
|
+
return layout.list.handleMouse({ ...event, y: event.y - layout.top });
|
|
200
379
|
}
|
|
201
380
|
handleInput(data) {
|
|
381
|
+
if (this.preview) {
|
|
382
|
+
if (matchesKey(data, 'escape'))
|
|
383
|
+
this.preview = undefined;
|
|
384
|
+
else if (matchesKey(data, 'enter'))
|
|
385
|
+
void this.decideProposal();
|
|
386
|
+
else if (matchesKey(data, 'home'))
|
|
387
|
+
this.previewOffset = 0;
|
|
388
|
+
else if (matchesKey(data, 'end'))
|
|
389
|
+
this.previewOffset = Number.MAX_SAFE_INTEGER;
|
|
390
|
+
else
|
|
391
|
+
this.previewOffset = Math.max(0, this.previewOffset + (matchesKey(data, 'down') ? 1 : matchesKey(data, 'up') ? -1 : matchesKey(data, 'pageDown') ? this.previewRows : matchesKey(data, 'pageUp') ? -this.previewRows : 0));
|
|
392
|
+
this.options.changed();
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
202
395
|
if (this.settingsPlugin()) {
|
|
203
396
|
if (matchesKey(data, 'tab') || matchesKey(data, 'shift+tab'))
|
|
204
397
|
return true;
|
|
@@ -206,44 +399,121 @@ export class PluginsPage {
|
|
|
206
399
|
}
|
|
207
400
|
if (matchesKey(data, 'tab') || matchesKey(data, 'shift+tab')) {
|
|
208
401
|
if (!this.list.query) {
|
|
209
|
-
|
|
402
|
+
const direction = matchesKey(data, 'shift+tab') ? -1 : 1;
|
|
403
|
+
const current = SECTIONS.findIndex(section => section.id === this.section);
|
|
404
|
+
this.section = SECTIONS[(current + direction + SECTIONS.length) % SECTIONS.length].id;
|
|
210
405
|
this.fill();
|
|
211
406
|
}
|
|
212
407
|
return true;
|
|
213
408
|
}
|
|
214
409
|
return this.list.handleInput(data);
|
|
215
410
|
}
|
|
411
|
+
selectedPlugin() {
|
|
412
|
+
const selectedId = this.list.selected?.id;
|
|
413
|
+
return selectedId?.startsWith(SETTINGS_ROW)
|
|
414
|
+
? this.plugins?.find(candidate => `${SETTINGS_ROW}${candidate.id}` === selectedId)
|
|
415
|
+
: selectedId?.startsWith(PACKAGE_ROW)
|
|
416
|
+
? this.plugins?.find(candidate => `${PACKAGE_ROW}${candidate.id}` === selectedId)
|
|
417
|
+
: this.menu().find(candidate => this.key(candidate) === selectedId)?.plugin;
|
|
418
|
+
}
|
|
419
|
+
pluginInspector(plugin, width, rows) {
|
|
420
|
+
const theme = this.options.theme();
|
|
421
|
+
if (width < 1 || rows < 1)
|
|
422
|
+
return [];
|
|
423
|
+
if (!plugin) {
|
|
424
|
+
const action = this.list.selected?.id === INSTALL_PACKAGE;
|
|
425
|
+
return [theme.fg('muted', action ? 'Add a package from npm, Git, or a local folder.' : 'Select a plugin to inspect it.')].slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
426
|
+
}
|
|
427
|
+
const selectedId = this.list.selected?.id;
|
|
428
|
+
const row = this.menu().find(candidate => this.key(candidate) === selectedId);
|
|
429
|
+
const title = row ? row.whole ? row.plugin.title : row.name : plugin.title;
|
|
430
|
+
const lines = [theme.bold(safeText(title))];
|
|
431
|
+
if (title !== plugin.title)
|
|
432
|
+
lines.push(theme.fg('dim', `Part of ${safeText(plugin.title)}`));
|
|
433
|
+
lines.push('', ...wrapTextWithAnsi(safeText(describePlugin(plugin)), width));
|
|
434
|
+
if (row) {
|
|
435
|
+
const status = this.section === 'trust' && row.whole
|
|
436
|
+
? plugin.trust === 'trusted' ? 'Code trusted' : 'Code not trusted'
|
|
437
|
+
: row.on ? 'Enabled' : 'Disabled';
|
|
438
|
+
lines.push('', theme.fg(row.on ? 'success' : 'dim', status));
|
|
439
|
+
}
|
|
440
|
+
if (plugin.code) {
|
|
441
|
+
lines.push(theme.fg('muted', `Code trust: ${plugin.trust === 'trusted' ? 'trusted' : 'not trusted'}`));
|
|
442
|
+
lines.push(theme.fg('muted', `Fingerprint: ${plugin.code.hash.slice(0, 12)}`));
|
|
443
|
+
}
|
|
444
|
+
if (plugin.install) {
|
|
445
|
+
lines.push('', theme.fg('muted', `Package: ${this.hubManagedIds.has(plugin.id) ? 'Hub receipt' : 'local'}`));
|
|
446
|
+
lines.push(...wrapTextWithAnsi(theme.fg('dim', safeText(plugin.install.source)), width));
|
|
447
|
+
}
|
|
448
|
+
if (settingsOf(plugin).length)
|
|
449
|
+
lines.push('', theme.fg('muted', `${settingsOf(plugin).length} settings`));
|
|
450
|
+
return lines.slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
451
|
+
}
|
|
216
452
|
render(width, rows) {
|
|
217
453
|
const theme = this.options.theme();
|
|
218
454
|
if (width < 1 || rows < 1)
|
|
219
455
|
return [];
|
|
220
|
-
if (
|
|
456
|
+
if (this.preview) {
|
|
457
|
+
const proposal = this.preview;
|
|
458
|
+
const text = [`Name: ${proposal.name}`, `Description: ${proposal.description}`, `Status: ${proposal.status}`, `SHA-256: ${proposal.hash}`, '', '--- package.json ---', proposal.manifest, ...Object.entries(proposal.files).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).flatMap(([path, content]) => ['', `--- ${path} ---`, content])].join('\n');
|
|
459
|
+
const content = text.split('\n').flatMap(line => wrapTextWithAnsi(reviewText(line), width));
|
|
460
|
+
this.previewRows = Math.max(1, rows - 1);
|
|
461
|
+
this.previewOffset = Math.min(this.previewOffset, Math.max(0, content.length - this.previewRows));
|
|
462
|
+
return [theme.bold(`Proposal preview · ${this.previewOffset + 1}/${Math.max(1, content.length)}`), ...content.slice(this.previewOffset, this.previewOffset + this.previewRows)].slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
463
|
+
}
|
|
464
|
+
if (this.section === 'proposals' && this.proposalError)
|
|
465
|
+
return [theme.fg('error', `Could not read proposals: ${safeText(this.proposalError)}`)];
|
|
466
|
+
if (!this.plugins) {
|
|
467
|
+
this.pointerLayout = undefined;
|
|
221
468
|
return [theme.fg(this.error ? 'error' : 'dim', this.error ? `Could not read plugins: ${this.error}` : 'Reading plugins…')];
|
|
469
|
+
}
|
|
222
470
|
const plugin = this.settingsPlugin();
|
|
223
471
|
if (plugin)
|
|
224
472
|
return this.renderSettings(plugin, width, rows);
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
473
|
+
const note = this.restart ? [theme.fg('warning', 'Saved. Changes apply when this session restarts.')] : [];
|
|
474
|
+
const paneWidth = inspectorListWidth(width);
|
|
475
|
+
if (paneWidth !== undefined) {
|
|
476
|
+
const bodyRows = Math.max(0, rows - note.length);
|
|
477
|
+
const rightWidth = Math.max(0, width - paneWidth - 3);
|
|
478
|
+
const listLines = this.list.render(paneWidth, bodyRows);
|
|
479
|
+
this.pointerLayout = { list: this.list, width: paneWidth, top: note.length, rows: listLines.length };
|
|
480
|
+
return [...note, ...splitColumns(listLines, this.pluginInspector(this.selectedPlugin(), rightWidth, bodyRows), paneWidth, width, bodyRows, theme)]
|
|
481
|
+
.slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
482
|
+
}
|
|
483
|
+
const selected = this.selectedPlugin();
|
|
229
484
|
const detail = selected && rows >= 8 ? wrapTextWithAnsi(safeText(describePlugin(selected)), width).slice(0, 3) : [];
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
485
|
+
const listRows = Math.max(2, rows - note.length - (detail.length ? detail.length + 1 : 0));
|
|
486
|
+
const listLines = this.list.render(width, listRows);
|
|
487
|
+
this.pointerLayout = { list: this.list, width, top: note.length, rows: Math.min(listLines.length, Math.max(0, rows - note.length)) };
|
|
488
|
+
const lines = [...note, ...listLines];
|
|
489
|
+
if (detail.length)
|
|
490
|
+
lines.push('', ...detail.map(line => theme.fg('muted', line)));
|
|
235
491
|
return lines.slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
236
492
|
}
|
|
237
493
|
renderSettings(plugin, width, rows) {
|
|
238
494
|
const theme = this.options.theme();
|
|
239
495
|
const setting = settingsOf(plugin).find(candidate => candidate.key === this.settingsList.selected?.id);
|
|
240
496
|
const range = setting && !isSwitch(setting) ? [`From ${setting.min.toLocaleString('en-US')} to ${setting.max.toLocaleString('en-US')} · default ${setting.default.toLocaleString('en-US')}`] : [];
|
|
497
|
+
const note = this.saved ? [theme.fg('success', 'Saved.')] : [];
|
|
498
|
+
const paneWidth = inspectorListWidth(width);
|
|
499
|
+
if (paneWidth !== undefined) {
|
|
500
|
+
const bodyRows = Math.max(0, rows - note.length);
|
|
501
|
+
const rightWidth = Math.max(0, width - paneWidth - 3);
|
|
502
|
+
const listLines = this.settingsList.render(paneWidth, bodyRows);
|
|
503
|
+
const details = setting
|
|
504
|
+
? [theme.bold(safeText(setting.label)), '', ...wrapTextWithAnsi(safeText(setting.description), rightWidth), '', ...range.map(line => theme.fg('muted', line)), theme.fg('dim', `Current value: ${settingValueText(setting)}`)]
|
|
505
|
+
: [theme.fg('muted', 'Select a setting to review its value and description.')];
|
|
506
|
+
this.pointerLayout = { list: this.settingsList, width: paneWidth, top: note.length, rows: listLines.length };
|
|
507
|
+
return [...note, ...splitColumns(listLines, details, paneWidth, width, bodyRows, theme)]
|
|
508
|
+
.slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
509
|
+
}
|
|
241
510
|
const detail = setting && rows >= 8 ? [...wrapTextWithAnsi(safeText(setting.description), width).slice(0, 3), ...range] : [];
|
|
242
|
-
const
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
511
|
+
const listRows = Math.max(2, rows - note.length - (detail.length ? detail.length + 1 : 0));
|
|
512
|
+
const listLines = this.settingsList.render(width, listRows);
|
|
513
|
+
this.pointerLayout = { list: this.settingsList, width, top: note.length, rows: Math.min(listLines.length, Math.max(0, rows - note.length)) };
|
|
514
|
+
const lines = [...note, ...listLines];
|
|
515
|
+
if (detail.length)
|
|
516
|
+
lines.push('', ...detail.map(line => theme.fg('muted', line)));
|
|
247
517
|
return lines.slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
248
518
|
}
|
|
249
519
|
}
|
|
@@ -88,9 +88,11 @@ export class SubagentsPage {
|
|
|
88
88
|
activeTab() { return 'subagents'; }
|
|
89
89
|
hint() { return '↑↓ select · type to search'; }
|
|
90
90
|
rows(width = 80, now = this.options.now?.() ?? Date.now()) {
|
|
91
|
+
// The main agent is pinned first so its children read against it.
|
|
91
92
|
const ordered = [...this.options.snapshot()].sort((left, right) => {
|
|
93
|
+
const main = Number(right.relation === 'main') - Number(left.relation === 'main');
|
|
92
94
|
const active = Number(ACTIVE_SUBAGENT_STATUSES.has(right.status)) - Number(ACTIVE_SUBAGENT_STATUSES.has(left.status));
|
|
93
|
-
return active || left.startedAt.localeCompare(right.startedAt) || left.id.localeCompare(right.id);
|
|
95
|
+
return main || active || left.startedAt.localeCompare(right.startedAt) || left.id.localeCompare(right.id);
|
|
94
96
|
});
|
|
95
97
|
return ordered.map(item => {
|
|
96
98
|
const profile = clean(item.profile?.name ?? '') || 'subagent';
|
|
@@ -115,9 +117,10 @@ export class SubagentsPage {
|
|
|
115
117
|
render(width, rows) {
|
|
116
118
|
const items = this.rows(width, this.options.now?.() ?? Date.now());
|
|
117
119
|
this.list.setRows(items);
|
|
118
|
-
const
|
|
120
|
+
const children = items.filter(item => item.data?.relation !== 'main');
|
|
121
|
+
const active = children.filter(item => item.data && ACTIVE_SUBAGENT_STATUSES.has(item.data.status)).length;
|
|
119
122
|
const selected = this.list.selected?.data;
|
|
120
|
-
const header = headerRow('Subagents', `${active} active · ${
|
|
123
|
+
const header = headerRow('Subagents', `${active} active · ${children.length} total`, width, this.options.theme());
|
|
121
124
|
const bodyRows = Math.max(0, rows - 7);
|
|
122
125
|
const listRows = Math.max(1, Math.min(this.list.height, Math.floor(bodyRows * 0.6)));
|
|
123
126
|
const output = [header, '', ...this.list.render(width, listRows), ''];
|
|
@@ -129,8 +132,8 @@ export class SubagentsPage {
|
|
|
129
132
|
output.push(neutralRule(width, theme));
|
|
130
133
|
output.push(padded(theme.fg('muted', `Profile: ${clean(selected.profile?.name ?? '') || 'copy of the parent'}`), width));
|
|
131
134
|
output.push(padded(theme.fg('accent', 'Task'), width));
|
|
132
|
-
output.push(...detailLines(selected.goal || 'Subagent'));
|
|
133
|
-
output.push(padded(theme.fg('muted', `Status: ${[selected.status.replaceAll('_', ' '), subagentTasks(selected.
|
|
135
|
+
output.push(...detailLines(selected.goal || (selected.relation === 'main' ? 'Main agent' : 'Subagent')));
|
|
136
|
+
output.push(padded(theme.fg('muted', `Status: ${[selected.status.replaceAll('_', ' '), subagentTasks(selected.todo), terminalSubagentDuration(selected)].filter(Boolean).join(' · ')}`), width));
|
|
134
137
|
output.push(padded(theme.fg('accent', 'Current activity'), width));
|
|
135
138
|
output.push(...detailLines(selected.activity));
|
|
136
139
|
}
|
|
@@ -2,7 +2,7 @@ import type { Theme } from '@earendil-works/pi-coding-agent';
|
|
|
2
2
|
import type { UsagePeriod } from "../../../usage/types.js";
|
|
3
3
|
import type { TabInfo } from "../chrome.js";
|
|
4
4
|
import type { TerminalPage } from "../page.js";
|
|
5
|
-
type UsageTab = 'analysis' | 'limits';
|
|
5
|
+
type UsageTab = 'analysis' | 'limits' | 'accounts';
|
|
6
6
|
interface UsagePageOptions {
|
|
7
7
|
theme(): Theme;
|
|
8
8
|
call(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
@@ -12,7 +12,7 @@ interface UsagePageOptions {
|
|
|
12
12
|
}
|
|
13
13
|
export declare class UsagePage implements TerminalPage {
|
|
14
14
|
private readonly options;
|
|
15
|
-
presentation: "
|
|
15
|
+
presentation: "fullscreen";
|
|
16
16
|
focused: boolean;
|
|
17
17
|
private tab;
|
|
18
18
|
private period;
|
|
@@ -20,7 +20,9 @@ export declare class UsagePage implements TerminalPage {
|
|
|
20
20
|
private summary?;
|
|
21
21
|
private limitsSummary?;
|
|
22
22
|
private limits?;
|
|
23
|
+
private allLimits?;
|
|
23
24
|
private busy;
|
|
25
|
+
private reloadPending;
|
|
24
26
|
private error?;
|
|
25
27
|
private refreshLimits;
|
|
26
28
|
private offset;
|
|
@@ -118,7 +118,7 @@ function spread(left, right, width) {
|
|
|
118
118
|
}
|
|
119
119
|
export class UsagePage {
|
|
120
120
|
options;
|
|
121
|
-
presentation = '
|
|
121
|
+
presentation = 'fullscreen';
|
|
122
122
|
focused = false;
|
|
123
123
|
tab;
|
|
124
124
|
period = '7d';
|
|
@@ -126,7 +126,9 @@ export class UsagePage {
|
|
|
126
126
|
summary;
|
|
127
127
|
limitsSummary;
|
|
128
128
|
limits;
|
|
129
|
+
allLimits;
|
|
129
130
|
busy = false;
|
|
131
|
+
reloadPending = false;
|
|
130
132
|
error;
|
|
131
133
|
refreshLimits;
|
|
132
134
|
offset = 0;
|
|
@@ -140,17 +142,19 @@ export class UsagePage {
|
|
|
140
142
|
this.refreshLimits = this.tab === 'limits';
|
|
141
143
|
}
|
|
142
144
|
tabs() {
|
|
143
|
-
return [{ id: 'analysis', title: 'Analysis' }, { id: 'limits', title: 'Limits' }];
|
|
145
|
+
return [{ id: 'analysis', title: 'Analysis' }, { id: 'limits', title: 'Limits' }, { id: 'accounts', title: 'All accounts' }];
|
|
144
146
|
}
|
|
145
147
|
activeTab() {
|
|
146
148
|
return this.tab;
|
|
147
149
|
}
|
|
148
150
|
selectTab(id) {
|
|
149
|
-
if ((id === 'analysis' || id === 'limits') && id !== this.tab) {
|
|
151
|
+
if ((id === 'analysis' || id === 'limits' || id === 'accounts') && id !== this.tab) {
|
|
150
152
|
this.tab = id;
|
|
151
153
|
this.offset = 0;
|
|
152
154
|
this.options.changed();
|
|
153
|
-
if (id === '
|
|
155
|
+
if (id === 'accounts')
|
|
156
|
+
void this.load();
|
|
157
|
+
else if (id === 'limits' && !this.limits) {
|
|
154
158
|
this.refreshLimits = true;
|
|
155
159
|
void this.load();
|
|
156
160
|
}
|
|
@@ -162,19 +166,23 @@ export class UsagePage {
|
|
|
162
166
|
}
|
|
163
167
|
hint() {
|
|
164
168
|
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
|
|
166
|
-
: '↑/↓ scroll · PgUp/PgDn page · r read provider limits
|
|
169
|
+
? '↑/↓ scroll · PgUp/PgDn page · 1 today 2 7d 3 30d 0 all · c account s session p project a all accounts · r refresh'
|
|
170
|
+
: '↑/↓ scroll · PgUp/PgDn page · r read provider limits';
|
|
167
171
|
}
|
|
168
172
|
activate() {
|
|
169
|
-
if (!this.summary && !this.limits)
|
|
173
|
+
if (!this.summary && !this.limits && !this.allLimits)
|
|
170
174
|
void this.load();
|
|
171
175
|
}
|
|
172
176
|
dispose() {
|
|
173
177
|
this.disposed = true;
|
|
174
178
|
}
|
|
175
179
|
async load() {
|
|
176
|
-
if (this.
|
|
180
|
+
if (this.disposed)
|
|
181
|
+
return;
|
|
182
|
+
if (this.busy) {
|
|
183
|
+
this.reloadPending = true;
|
|
177
184
|
return;
|
|
185
|
+
}
|
|
178
186
|
this.busy = true;
|
|
179
187
|
this.error = undefined;
|
|
180
188
|
this.options.changed();
|
|
@@ -186,6 +194,14 @@ export class UsagePage {
|
|
|
186
194
|
timezoneOffsetMinutes: -new Date().getTimezoneOffset()
|
|
187
195
|
});
|
|
188
196
|
}
|
|
197
|
+
else if (this.tab === 'accounts') {
|
|
198
|
+
if (!this.allLimits) {
|
|
199
|
+
this.allLimits = await this.options.call('terminal.usage.allLimits', { refresh: false });
|
|
200
|
+
if (!this.disposed)
|
|
201
|
+
this.options.changed();
|
|
202
|
+
}
|
|
203
|
+
this.allLimits = await this.options.call('terminal.usage.allLimits', { refresh: true });
|
|
204
|
+
}
|
|
189
205
|
else {
|
|
190
206
|
// The provider's windows and this app's own ledger answer different questions; show both.
|
|
191
207
|
const [limits, summary] = await Promise.all([
|
|
@@ -207,12 +223,23 @@ export class UsagePage {
|
|
|
207
223
|
}
|
|
208
224
|
finally {
|
|
209
225
|
this.busy = false;
|
|
210
|
-
if (!this.disposed)
|
|
226
|
+
if (!this.disposed) {
|
|
211
227
|
this.options.changed();
|
|
228
|
+
if (this.reloadPending) {
|
|
229
|
+
this.reloadPending = false;
|
|
230
|
+
void this.load();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
212
233
|
}
|
|
213
234
|
}
|
|
214
235
|
handleInput(data) {
|
|
215
236
|
const page = Math.max(1, this.scrollRows - 1);
|
|
237
|
+
if (matchesKey(data, 'tab') || matchesKey(data, 'shift+tab')) {
|
|
238
|
+
const tabs = ['analysis', 'limits', 'accounts'];
|
|
239
|
+
const direction = matchesKey(data, 'shift+tab') ? -1 : 1;
|
|
240
|
+
this.selectTab(tabs[(tabs.indexOf(this.tab) + direction + tabs.length) % tabs.length]);
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
216
243
|
if (matchesKey(data, 'up'))
|
|
217
244
|
this.offset = Math.max(0, this.offset - 1);
|
|
218
245
|
else if (matchesKey(data, 'down'))
|
|
@@ -298,7 +325,7 @@ export class UsagePage {
|
|
|
298
325
|
return { header, body };
|
|
299
326
|
}
|
|
300
327
|
limitsContent(width, theme) {
|
|
301
|
-
const report = this.limits;
|
|
328
|
+
const report = this.tab === 'accounts' ? this.allLimits : this.limits;
|
|
302
329
|
const header = [];
|
|
303
330
|
if (!report) {
|
|
304
331
|
header.push(theme.fg(this.error ? 'error' : 'dim', this.error ?? (this.busy ? 'Reading provider limits…' : 'No limit readings yet.')));
|
|
@@ -318,7 +345,7 @@ export class UsagePage {
|
|
|
318
345
|
}
|
|
319
346
|
for (const provider of account.providers) {
|
|
320
347
|
body.push('');
|
|
321
|
-
const source = provider.source === 'provider' ? '' : provider.source;
|
|
348
|
+
const source = this.tab === 'accounts' ? `cached · ${provider.source}` : provider.source === 'provider' ? '' : provider.source;
|
|
322
349
|
const meta = [source, ago(provider.observedAt)].filter(Boolean).join(' · ');
|
|
323
350
|
const tone = provider.source === 'unavailable' ? 'warning' : 'muted';
|
|
324
351
|
body.push(...wrapTextWithAnsi(theme.fg(tone, ` ${provider.provider}${meta ? ` · ${meta}` : ''}${provider.reason ? ` · ${oneLine(provider.reason)}` : ''}`), width));
|
|
@@ -348,7 +375,7 @@ export class UsagePage {
|
|
|
348
375
|
}
|
|
349
376
|
if (!report.accounts.length)
|
|
350
377
|
body.push(theme.fg('dim', 'No accounts.'));
|
|
351
|
-
const local = this.limitsSummary?.totals;
|
|
378
|
+
const local = this.tab === 'limits' ? this.limitsSummary?.totals : undefined;
|
|
352
379
|
if (local) {
|
|
353
380
|
if (body.length)
|
|
354
381
|
body.push('');
|
|
@@ -6,7 +6,7 @@ type PageOptions = {
|
|
|
6
6
|
runId?: string;
|
|
7
7
|
url?: string;
|
|
8
8
|
annotate?: AnnotateStart;
|
|
9
|
-
usageTab?: 'analysis' | 'limit';
|
|
9
|
+
usageTab?: 'analysis' | 'limit' | 'accounts';
|
|
10
10
|
usagePeriod?: 'today' | '7d' | '30d' | 'all';
|
|
11
11
|
};
|
|
12
12
|
type OpenPage = (id: CommandPageId, options?: PageOptions) => Promise<void>;
|