@xingular/harness 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +28 -25
- package/dist/cli.js +6675 -2501
- package/dist/sdk/accounts/accounts.d.ts +27 -3
- package/dist/sdk/accounts/accounts.js +78 -24
- package/dist/sdk/accounts/secrets.d.ts +12 -0
- package/dist/sdk/accounts/secrets.js +114 -7
- package/dist/sdk/accounts/store.d.ts +26 -1
- package/dist/sdk/accounts/store.js +1 -1
- package/dist/sdk/agents/builtin.js +15 -1
- package/dist/sdk/agents/planResearch.d.ts +5 -0
- package/dist/sdk/agents/planResearch.js +19 -0
- package/dist/sdk/agents/profiles.d.ts +2 -1
- package/dist/sdk/agents/profiles.js +9 -4
- package/dist/sdk/annotations/pageScript.js +349 -48
- package/dist/sdk/annotations/part.js +34 -23
- package/dist/sdk/annotations/store.d.ts +7 -0
- package/dist/sdk/annotations/store.js +13 -5
- package/dist/sdk/browser/backend.d.ts +16 -0
- package/dist/sdk/browser/backend.js +17 -0
- package/dist/sdk/browser/contracts.d.ts +19 -0
- package/dist/sdk/browser/contracts.js +116 -0
- package/dist/sdk/browser/tools.d.ts +1 -4
- package/dist/sdk/browser/tools.js +14 -91
- package/dist/sdk/cli.js +5 -4
- package/dist/sdk/commands/list.d.ts +3 -0
- package/dist/sdk/commands/list.js +75 -0
- package/dist/sdk/commands/registry.d.ts +250 -0
- package/dist/sdk/commands/registry.js +47 -0
- package/dist/sdk/commands/routes.d.ts +16 -0
- package/dist/sdk/commands/routes.js +33 -0
- package/dist/sdk/core/commandBudget.d.ts +23 -0
- package/dist/sdk/core/commandBudget.js +57 -0
- package/dist/sdk/core/index.js +17 -1
- package/dist/sdk/core/interactiveProfile.d.ts +46 -0
- package/dist/sdk/core/interactiveProfile.js +143 -0
- package/dist/sdk/core/workspace.d.ts +13 -0
- package/dist/sdk/core/workspace.js +88 -0
- package/dist/sdk/core/workspaceBashTool.d.ts +4 -0
- package/dist/sdk/core/workspaceBashTool.js +36 -0
- package/dist/sdk/core/workspaceSandbox.d.ts +31 -0
- package/dist/sdk/core/workspaceSandbox.js +320 -0
- package/dist/sdk/core/workspaceWriteOperations.d.ts +22 -0
- package/dist/sdk/core/workspaceWriteOperations.js +206 -0
- package/dist/sdk/doctor/types.d.ts +2 -2
- package/dist/sdk/harness.d.ts +7 -0
- package/dist/sdk/harness.js +42 -2
- package/dist/sdk/index.d.ts +6 -1
- package/dist/sdk/index.js +3 -0
- package/dist/sdk/pi/runtime.d.ts +2 -1
- package/dist/sdk/pi/runtime.js +2 -2
- package/dist/sdk/plugins/builtin.js +29 -12
- package/dist/sdk/plugins/builtinRegistry.d.ts +10 -3
- package/dist/sdk/plugins/builtinRegistry.js +30 -8
- package/dist/sdk/plugins/catalogue.js +2 -1
- package/dist/sdk/plugins/commandPrefix.d.ts +8 -0
- package/dist/sdk/plugins/commandPrefix.js +21 -0
- package/dist/sdk/plugins/firstParty.d.ts +1 -1
- package/dist/sdk/plugins/firstParty.js +8 -2
- package/dist/sdk/plugins/inventory.js +4 -3
- package/dist/sdk/plugins/manifest.d.ts +2 -0
- package/dist/sdk/plugins/manifest.js +2 -1
- package/dist/sdk/plugins/surfaces.d.ts +5 -0
- package/dist/sdk/plugins/surfaces.js +11 -0
- package/dist/sdk/plugins/types.d.ts +15 -0
- package/dist/sdk/prompt/compile.d.ts +14 -0
- package/dist/sdk/prompt/compile.js +65 -29
- package/dist/sdk/prompt/standard.d.ts +17 -0
- package/dist/sdk/prompt/standard.js +21 -0
- package/dist/sdk/server/methods.js +18 -0
- package/dist/sdk/server/remote.js +36 -11
- package/dist/sdk/sessions/compactionCheckpoint.d.ts +11 -0
- package/dist/sdk/sessions/compactionCheckpoint.js +79 -0
- package/dist/sdk/sessions/continuation.d.ts +3 -0
- package/dist/sdk/sessions/continuation.js +5 -1
- package/dist/sdk/sessions/contracts.d.ts +168 -0
- package/dist/sdk/sessions/contracts.js +59 -1
- package/dist/sdk/sessions/control.d.ts +72 -0
- package/dist/sdk/sessions/control.js +158 -0
- package/dist/sdk/sessions/initialBash.d.ts +7 -0
- package/dist/sdk/sessions/initialBash.js +7 -0
- package/dist/sdk/sessions/planMode.d.ts +14 -3
- package/dist/sdk/sessions/planMode.js +73 -16
- package/dist/sdk/sessions/registry.d.ts +1 -1
- package/dist/sdk/sessions/registry.js +2 -2
- package/dist/sdk/sessions/runStore.d.ts +14 -0
- package/dist/sdk/sessions/runStore.js +35 -0
- package/dist/sdk/sessions/runner.d.ts +40 -2
- package/dist/sdk/sessions/runner.js +161 -13
- package/dist/sdk/sessions/sessions.d.ts +23 -3
- package/dist/sdk/sessions/sessions.js +202 -17
- package/dist/sdk/sessions/subagentService.d.ts +9 -0
- package/dist/sdk/sessions/subagentService.js +63 -4
- package/dist/sdk/sessions/workSlice.d.ts +103 -0
- package/dist/sdk/sessions/workSlice.js +297 -0
- package/dist/sdk/sessions/workspaceBoundary.d.ts +15 -0
- package/dist/sdk/sessions/workspaceBoundary.js +46 -0
- package/dist/sdk/terminal/browserControl.js +2 -1
- package/dist/sdk/terminal/controller.d.ts +11 -3
- package/dist/sdk/terminal/controller.js +40 -13
- package/dist/sdk/terminal/opentui/theme.d.ts +43 -0
- package/dist/sdk/terminal/opentui/theme.js +87 -0
- package/dist/sdk/terminal/piExtension.d.ts +6 -1
- package/dist/sdk/terminal/piExtension.js +154 -54
- package/dist/sdk/terminal/standalone.d.ts +7 -0
- package/dist/sdk/terminal/standalone.js +72 -13
- package/dist/sdk/terminal/subagentMonitor.d.ts +11 -2
- package/dist/sdk/terminal/subagentMonitor.js +32 -1
- package/dist/sdk/terminal/terminals.d.ts +19 -2
- package/dist/sdk/terminal/terminals.js +245 -35
- package/dist/sdk/terminal/tui/annotateCommand.d.ts +1 -1
- package/dist/sdk/terminal/tui/annotateCommand.js +3 -3
- package/dist/sdk/terminal/tui/commandPages.d.ts +3 -1
- package/dist/sdk/terminal/tui/commandPages.js +3 -0
- package/dist/sdk/terminal/tui/commands.d.ts +2 -0
- package/dist/sdk/terminal/tui/commands.js +205 -124
- package/dist/sdk/terminal/tui/list.d.ts +14 -1
- package/dist/sdk/terminal/tui/list.js +96 -14
- package/dist/sdk/terminal/tui/pageOverlay.js +13 -4
- package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +1 -1
- package/dist/sdk/terminal/tui/pages/notesPage.d.ts +5 -5
- package/dist/sdk/terminal/tui/pages/notesPage.js +18 -18
- package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +0 -5
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +9 -3
- package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +1 -0
- package/dist/sdk/terminal/tui/pages/subagentsPage.js +98 -16
- package/dist/sdk/terminal/tui/pages/usagePage.d.ts +43 -0
- package/dist/sdk/terminal/tui/pages/usagePage.js +370 -0
- package/dist/sdk/terminal/tui/piAdapter.d.ts +1 -2
- package/dist/sdk/terminal/tui/piAdapter.js +3 -5
- package/dist/sdk/terminal/tui/pluginCommands.d.ts +2 -0
- package/dist/sdk/terminal/tui/pluginCommands.js +13 -9
- package/dist/sdk/terminal/tui/runtime.js +23 -3
- package/dist/sdk/terminal/tui/sessionPicker.d.ts +40 -0
- package/dist/sdk/terminal/tui/sessionPicker.js +141 -0
- package/dist/sdk/tools/checklist.js +1 -0
- package/dist/sdk/tools/checkpoint.d.ts +27 -0
- package/dist/sdk/tools/checkpoint.js +154 -0
- package/dist/sdk/tools/getCapabilities.d.ts +31 -0
- package/dist/sdk/tools/getCapabilities.js +34 -0
- package/dist/sdk/tools/spawnSubagent.js +4 -3
- package/dist/sdk/usage/analytics.d.ts +20 -0
- package/dist/sdk/usage/analytics.js +151 -0
- package/dist/sdk/usage/ledger.d.ts +38 -0
- package/dist/sdk/usage/ledger.js +142 -0
- package/dist/sdk/usage/limits/adapters.d.ts +11 -0
- package/dist/sdk/usage/limits/adapters.js +349 -0
- package/dist/sdk/usage/limits/capture.d.ts +8 -0
- package/dist/sdk/usage/limits/capture.js +47 -0
- package/dist/sdk/usage/limits/http.d.ts +12 -0
- package/dist/sdk/usage/limits/http.js +51 -0
- package/dist/sdk/usage/limits/normalize.d.ts +15 -0
- package/dist/sdk/usage/limits/normalize.js +211 -0
- package/dist/sdk/usage/limits/service.d.ts +30 -0
- package/dist/sdk/usage/limits/service.js +176 -0
- package/dist/sdk/usage/limits/types.d.ts +65 -0
- package/dist/sdk/usage/limits/types.js +1 -0
- package/dist/sdk/usage/serverMethods.d.ts +3 -0
- package/dist/sdk/usage/serverMethods.js +22 -0
- package/dist/sdk/usage/service.d.ts +32 -0
- package/dist/sdk/usage/service.js +66 -0
- package/dist/sdk/usage/settings.d.ts +7 -0
- package/dist/sdk/usage/settings.js +28 -0
- package/dist/sdk/usage/types.d.ts +60 -0
- package/dist/sdk/usage/types.js +4 -0
- package/dist/terminal-opentui.js +5207 -1883
- package/dist/terminal-pi.js +1658 -271
- package/docs/architecture/02-sessions-and-runs.md +128 -26
- package/docs/architecture/04-prompt.md +50 -12
- package/docs/architecture/06-plugins-and-capabilities.md +24 -7
- package/docs/architecture/07-tools-and-gate.md +104 -24
- package/docs/architecture/08-memory-and-history.md +4 -2
- package/docs/architecture/09-accounts-and-connectors.md +31 -1
- package/docs/architecture/10-server-protocol.md +58 -3
- package/docs/architecture/11-terminal.md +105 -42
- package/docs/architecture/12-storage.md +30 -2
- package/docs/architecture/13-world-tools.md +22 -1
- package/docs/architecture/14-surfaces-and-parity.md +54 -7
- package/docs/architecture/README.md +3 -1
- package/docs/architecture/proposals/PROP-0017-page-annotations.md +6 -2
- package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +1 -1
- package/docs/architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md +54 -0
- package/docs/architecture/proposals/PROP-0032-planning-quality.md +196 -0
- package/docs/architecture/proposals/PROP-0033-compaction-plan-checkpoints.md +75 -0
- package/docs/architecture/proposals/PROP-0034-conversation-fast-mode.md +120 -0
- package/docs/architecture/proposals/PROP-0035-retain-subagent-status.md +47 -0
- package/docs/architecture/proposals/PROP-0036-first-class-harness-commands.md +59 -0
- package/docs/architecture/proposals/PROP-0037-workspace-write-boundary.md +63 -0
- package/docs/architecture/proposals/PROP-0038-annotate-open-shadow-roots.md +73 -0
- package/docs/architecture/proposals/PROP-0039-workspace-boundary-hardening.md +139 -0
- package/docs/architecture/proposals/PROP-0040-one-launch-path.md +87 -0
- package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +247 -0
- package/docs/architecture/proposals/PROP-0042-usage-and-provider-limits.md +127 -0
- package/docs/architecture/proposals/PROP-0043-read-only-plan-bash.md +74 -0
- package/docs/architecture/proposals/README.md +14 -0
- package/docs/decisions/ADR-0001-owned-terminal.md +1 -1
- package/docs/decisions/ADR-0021-required-command-namespaces.md +1 -1
- package/docs/decisions/ADR-0031-plugin-command-owner-namespaces.md +37 -0
- package/docs/decisions/ADR-0032-compaction-run-checkpoints.md +33 -0
- package/docs/decisions/ADR-0033-plan-mode-research-delegation.md +47 -0
- package/docs/decisions/ADR-0034-first-class-harness-terminal-commands.md +25 -0
- package/docs/decisions/ADR-0035-one-launch-path.md +40 -0
- package/docs/decisions/ADR-0036-workspace-write-operation-boundary.md +54 -0
- package/docs/decisions/ADR-0037-usage-ledger-and-provider-limits.md +44 -0
- package/docs/decisions/ADR-0038-plan-bash-os-read-only.md +29 -0
- package/docs/experiments/EXP-0003-performance-audit/data/session-runtime-replication.json +420 -0
- package/docs/experiments/EXP-0003-performance-audit/data/session-runtime.json +420 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-compact.json +376 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-history.json +422 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-question.json +95 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-wide.json +1036 -0
- package/docs/experiments/EXP-0003-performance-audit/plan.md +109 -0
- package/docs/experiments/EXP-0003-performance-audit/results.md +151 -0
- package/docs/handoff-2026-10-25-app-parity-and-solo-tabs.md +202 -0
- package/docs/parity.md +80 -24
- package/docs/releases/1.1.1.md +28 -0
- package/docs/releases/1.2.0.md +49 -0
- package/docs/releases/4.0.0.md +10 -9
- package/docs/releases/README.md +35 -30
- package/docs/study/00-index.md +3 -0
- package/docs/study/STUDY-0003-long-horizon-runs.md +210 -0
- package/docs/terminal-tui.md +100 -59
- package/package.json +4 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { matchesKey } from '@earendil-works/pi-tui';
|
|
1
|
+
import { matchesKey, visibleWidth } from '@earendil-works/pi-tui';
|
|
2
2
|
import { CENTER_DIALOG_PAD, CHROME_PAD, centerDialogBodyRows, centerDialogContentWidth, centerDialogFrame, centerDialogVerticalPad, contentWidth, pageBodyRows, pageFrame, tabIndexAt, tabStrip } from "./chrome.js";
|
|
3
3
|
import { centeredDialogHost, dialogFits, pageRows } from "./dialogGeometry.js";
|
|
4
4
|
export class PageOverlay {
|
|
@@ -132,11 +132,20 @@ export class PageOverlay {
|
|
|
132
132
|
contentWidth: contentWidth(width),
|
|
133
133
|
centered
|
|
134
134
|
};
|
|
135
|
-
const
|
|
135
|
+
const escapeHint = page.ownsNavigation ? 'Esc back/close' : 'Esc close';
|
|
136
|
+
const fullFooter = page.ownsNavigation ? `${escapeHint} · ${page.hint()}` : [
|
|
136
137
|
page.hint(),
|
|
137
|
-
tabs.length > 1
|
|
138
|
-
|
|
138
|
+
tabs.length > 1 ? 'Tab/Shift+Tab sections' : '',
|
|
139
|
+
escapeHint
|
|
139
140
|
].filter(Boolean).join(' · ');
|
|
141
|
+
const footerWidth = Math.max(0, width - CHROME_PAD * 2);
|
|
142
|
+
const primaryHint = page.hint().split(' · ')[0] ?? '';
|
|
143
|
+
const compactFooter = [
|
|
144
|
+
primaryHint,
|
|
145
|
+
!page.ownsNavigation && tabs.length > 1 ? 'Tab/⇧Tab' : '',
|
|
146
|
+
escapeHint
|
|
147
|
+
].filter(Boolean).join(' · ');
|
|
148
|
+
const footer = visibleWidth(fullFooter) <= footerWidth ? fullFooter : compactFooter;
|
|
140
149
|
return pageFrame({
|
|
141
150
|
title: '',
|
|
142
151
|
headerLine: tabStrip(tabs, page.activeTab(), inner, theme, 'top', this.hoveredTab),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The annotate pane (`/
|
|
2
|
+
* The annotate pane (`/annotate:` and `/annotate:annotate`, PROP-0018): the person works in a browser window or an app's own
|
|
3
3
|
* windows, and this page is the panel beside it. With the inspector on, hovering outlines a
|
|
4
4
|
* component and a click opens a comment popover with three choices: keep it as a note, add it to
|
|
5
5
|
* this session's queue, or send it to the agent now. The page lists the queue, what was sent and
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The Web annotations page (`/annotate:notes`, `/annotate:annotate [url]`): comments a person keeps about web pages. A new annotation
|
|
3
3
|
* is captured in a visible browser window: the whole page, text the person selects, or an element
|
|
4
|
-
* they click.
|
|
4
|
+
* they click. An annotation is private until it is shared with this session, whose agent can then read it
|
|
5
5
|
* with `page_notes_read` (PROP-0017).
|
|
6
6
|
*/
|
|
7
7
|
import type { Theme } from '@earendil-works/pi-coding-agent';
|
|
@@ -18,7 +18,7 @@ export interface NotesPageOptions {
|
|
|
18
18
|
input(title: string, placeholder?: string): Promise<string | undefined>;
|
|
19
19
|
};
|
|
20
20
|
changed(): void;
|
|
21
|
-
/** Start a new
|
|
21
|
+
/** Start a new annotation as soon as the page opens (`/annotate:annotate [url]`), on this address when given. */
|
|
22
22
|
start?: {
|
|
23
23
|
url?: string;
|
|
24
24
|
};
|
|
@@ -36,7 +36,7 @@ export declare const NOTE_KINDS: {
|
|
|
36
36
|
};
|
|
37
37
|
export declare const NOTE_ACTIONS: {
|
|
38
38
|
readonly reveal: "Show it in the browser window";
|
|
39
|
-
readonly edit: "Edit the
|
|
39
|
+
readonly edit: "Edit the annotation";
|
|
40
40
|
readonly resolve: "Mark resolved";
|
|
41
41
|
readonly reopen: "Reopen";
|
|
42
42
|
readonly share: "Let this session's agent read it";
|
|
@@ -65,7 +65,7 @@ export declare class NotesPage implements TerminalPage {
|
|
|
65
65
|
rows(): ListRow<Row>[];
|
|
66
66
|
private attempt;
|
|
67
67
|
activateRow(row: Row): Promise<void>;
|
|
68
|
-
/** Opens the capture window, takes what the person points at, and saves their
|
|
68
|
+
/** Opens the capture window, takes what the person points at, and saves their annotation. */
|
|
69
69
|
newNote(startUrl?: string): Promise<void>;
|
|
70
70
|
private noteMenu;
|
|
71
71
|
handleMouse(event: TerminalMouseInput): import("@earendil-works/pi-tui").TuiMouseEventResult | undefined;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { truncateToWidth } from '@earendil-works/pi-tui';
|
|
2
2
|
import { FilterList } from "../list.js";
|
|
3
|
-
const TABS = [{ id: 'notes', title: '
|
|
3
|
+
const TABS = [{ id: 'notes', title: 'Web annotations' }];
|
|
4
4
|
export const NOTE_KINDS = { page: 'The whole page', text: 'Text I select in the window', element: 'An element I click in the window' };
|
|
5
5
|
export const NOTE_ACTIONS = {
|
|
6
6
|
reveal: 'Show it in the browser window',
|
|
7
|
-
edit: 'Edit the
|
|
7
|
+
edit: 'Edit the annotation',
|
|
8
8
|
resolve: 'Mark resolved',
|
|
9
9
|
reopen: 'Reopen',
|
|
10
10
|
share: 'Let this session\'s agent read it',
|
|
@@ -31,7 +31,7 @@ export class NotesPage {
|
|
|
31
31
|
constructor(options) {
|
|
32
32
|
this.options = options;
|
|
33
33
|
this.list = new FilterList({
|
|
34
|
-
theme: options.theme, search: 'always', noun: ['
|
|
34
|
+
theme: options.theme, search: 'always', noun: ['annotation', 'annotations'], empty: 'No web annotations yet',
|
|
35
35
|
onActivate: row => { if (row.data)
|
|
36
36
|
void this.activateRow(row.data); }
|
|
37
37
|
});
|
|
@@ -67,10 +67,10 @@ export class NotesPage {
|
|
|
67
67
|
return note.grants.some(grant => grant.principal.sessionId && grant.principal.sessionId === this.sessionId);
|
|
68
68
|
}
|
|
69
69
|
rows() {
|
|
70
|
-
const rows = [{ id: 'new', label: 'New
|
|
70
|
+
const rows = [{ id: 'new', label: 'New web annotation…', group: 'Actions', detail: 'opens a browser window', tone: 'accent', data: { kind: 'new' } }];
|
|
71
71
|
for (const note of this.notes ?? []) {
|
|
72
72
|
rows.push({
|
|
73
|
-
id: `note:${note.id}`, label: clip(note.body.replace(/\s+/g, ' '), 60), group: '
|
|
73
|
+
id: `note:${note.id}`, label: clip(note.body.replace(/\s+/g, ' '), 60), group: 'Annotations',
|
|
74
74
|
detail: `${describeTarget(note.target)} · ${note.status}${this.shared(note) ? ' · shared here' : ''}`,
|
|
75
75
|
tone: note.status === 'resolved' ? 'dim' : this.shared(note) ? 'success' : 'muted',
|
|
76
76
|
searchText: `${note.body} ${note.source.url} ${note.source.title} ${note.tags.join(' ')}`,
|
|
@@ -101,7 +101,7 @@ export class NotesPage {
|
|
|
101
101
|
return this.newNote();
|
|
102
102
|
return this.noteMenu(row.note);
|
|
103
103
|
}
|
|
104
|
-
/** Opens the capture window, takes what the person points at, and saves their
|
|
104
|
+
/** Opens the capture window, takes what the person points at, and saves their annotation. */
|
|
105
105
|
newNote(startUrl) {
|
|
106
106
|
const { call, ui } = this.options;
|
|
107
107
|
return this.attempt(async () => {
|
|
@@ -109,7 +109,7 @@ export class NotesPage {
|
|
|
109
109
|
if (!url)
|
|
110
110
|
return;
|
|
111
111
|
await call('terminal.annotations.open', { url });
|
|
112
|
-
const kind = await ui.select('What does the
|
|
112
|
+
const kind = await ui.select('What does the annotation point at?', Object.values(NOTE_KINDS));
|
|
113
113
|
if (!kind)
|
|
114
114
|
return;
|
|
115
115
|
let envelope;
|
|
@@ -127,18 +127,18 @@ export class NotesPage {
|
|
|
127
127
|
}
|
|
128
128
|
envelope = await call('terminal.annotations.capture', { captureId });
|
|
129
129
|
if (!envelope)
|
|
130
|
-
return 'Nothing was selected in the browser window, so no
|
|
130
|
+
return 'Nothing was selected in the browser window, so no annotation was made.';
|
|
131
131
|
}
|
|
132
|
-
const body = await ui.input('
|
|
132
|
+
const body = await ui.input('Comment', 'What should change or be remembered?');
|
|
133
133
|
if (!body?.trim())
|
|
134
|
-
return '
|
|
134
|
+
return 'An annotation needs a comment; nothing was saved.';
|
|
135
135
|
const tags = (await ui.input('Tags (optional, comma separated)', '') ?? '').split(',').map(tag => tag.trim()).filter(Boolean);
|
|
136
|
-
const share = await ui.confirm('Share it with this session?', 'This session\'s agent can then read the
|
|
136
|
+
const share = await ui.confirm('Share it with this session?', 'This session\'s agent can then read the annotation with page_notes_read. You can stop sharing it later.');
|
|
137
137
|
await call('terminal.annotations.create', {
|
|
138
138
|
source: { url: envelope.url, title: envelope.title, capturedAt: new Date().toISOString(), surface: 'terminal', representationHint: envelope.documentId },
|
|
139
139
|
target: envelope.target, body, tags, share
|
|
140
140
|
});
|
|
141
|
-
return share ? '
|
|
141
|
+
return share ? 'Annotation saved and shared with this session.' : 'Annotation saved. It stays private until you share it.';
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
144
|
async noteMenu(note) {
|
|
@@ -163,7 +163,7 @@ export class NotesPage {
|
|
|
163
163
|
if (!body?.trim() || body === note.body)
|
|
164
164
|
return;
|
|
165
165
|
await call('terminal.annotations.update', { id, expectedVersion: note.version, patch: { body } });
|
|
166
|
-
return '
|
|
166
|
+
return 'Annotation saved.';
|
|
167
167
|
});
|
|
168
168
|
if (choice === NOTE_ACTIONS.resolve || choice === NOTE_ACTIONS.reopen)
|
|
169
169
|
return this.attempt(async () => {
|
|
@@ -172,14 +172,14 @@ export class NotesPage {
|
|
|
172
172
|
if (choice === NOTE_ACTIONS.share || choice === NOTE_ACTIONS.unshare)
|
|
173
173
|
return this.attempt(async () => {
|
|
174
174
|
await call(choice === NOTE_ACTIONS.share ? 'terminal.annotations.share' : 'terminal.annotations.unshare', { id });
|
|
175
|
-
return choice === NOTE_ACTIONS.share ? 'This session\'s agent can read the
|
|
175
|
+
return choice === NOTE_ACTIONS.share ? 'This session\'s agent can read the annotation.' : 'The annotation is no longer shared with this session.';
|
|
176
176
|
});
|
|
177
177
|
if (choice === NOTE_ACTIONS.delete)
|
|
178
178
|
return this.attempt(async () => {
|
|
179
|
-
if (!await ui.confirm('Delete this
|
|
179
|
+
if (!await ui.confirm('Delete this annotation?', 'It cannot be restored.'))
|
|
180
180
|
return;
|
|
181
181
|
await call('terminal.annotations.delete', { id, expectedVersion: note.version });
|
|
182
|
-
return '
|
|
182
|
+
return 'Annotation deleted.';
|
|
183
183
|
});
|
|
184
184
|
}
|
|
185
185
|
handleMouse(event) {
|
|
@@ -193,9 +193,9 @@ export class NotesPage {
|
|
|
193
193
|
if (width < 1 || rows < 1)
|
|
194
194
|
return [];
|
|
195
195
|
if (this.error)
|
|
196
|
-
return [theme.fg('error', `Could not read
|
|
196
|
+
return [theme.fg('error', `Could not read web annotations: ${this.error}`)];
|
|
197
197
|
if (!this.notes)
|
|
198
|
-
return [theme.fg('dim', 'Reading
|
|
198
|
+
return [theme.fg('dim', 'Reading web annotations…')];
|
|
199
199
|
return this.list.render(width, rows).slice(0, rows).map(line => truncateToWidth(line, width, ''));
|
|
200
200
|
}
|
|
201
201
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Plugins page: what this session loads, switched on or off, with trust for plugin code.
|
|
3
|
-
* Enter opens what can be done with a row; nothing changes without a choice made here. A plugin
|
|
4
|
-
* with settings has a Settings row that opens them as a subpage; Esc goes back to the list.
|
|
5
|
-
*/
|
|
6
1
|
import type { Theme } from '@earendil-works/pi-coding-agent';
|
|
7
2
|
import { type MenuCall, type MenuUi } from "../../pluginMenu.js";
|
|
8
3
|
import type { TabInfo } from "../chrome.js";
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Plugins page: what this session loads, switched on or off, with trust for plugin code.
|
|
3
|
+
* Enter opens what can be done with a row; nothing changes without a choice made here. A plugin
|
|
4
|
+
* with settings has a Settings row that opens them as a subpage; Esc goes back to the list.
|
|
5
|
+
*/
|
|
6
|
+
import { describePlugin, surfaceBadge } from "../../../plugins/surfaces.js";
|
|
1
7
|
import { matchesKey, truncateToWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
2
8
|
import { BACK, applyPluginAction, menuRows, rowActions } from "../../pluginMenu.js";
|
|
3
9
|
import { safeText } from "../chrome.js";
|
|
@@ -119,7 +125,7 @@ export class PluginsPage {
|
|
|
119
125
|
const kind = row.whole ? row.note : row.note.replace(/ in .*$/, '');
|
|
120
126
|
return {
|
|
121
127
|
id: this.key(row), group: safeText(row.plugin.title), label: row.whole ? 'Whole plugin' : safeText(row.name),
|
|
122
|
-
detail: `${row.on ? 'on' : 'off'}${kind ? ` · ${kind}` : ''}`, tone: row.on ? 'success' : 'dim',
|
|
128
|
+
detail: `${row.on ? 'on' : 'off'}${kind ? ` · ${kind}` : ''}${row.whole && surfaceBadge(row.plugin.surfaces) ? ` · ${surfaceBadge(row.plugin.surfaces)}` : ''}`, tone: row.on ? 'success' : 'dim',
|
|
123
129
|
searchText: `${row.name} ${row.plugin.title} ${row.note}`
|
|
124
130
|
};
|
|
125
131
|
}
|
|
@@ -136,7 +142,7 @@ export class PluginsPage {
|
|
|
136
142
|
return;
|
|
137
143
|
this.busy = true;
|
|
138
144
|
try {
|
|
139
|
-
const action = await this.options.ui.select(`${row.plugin.title}: ${row.plugin
|
|
145
|
+
const action = await this.options.ui.select(`${row.plugin.title}: ${describePlugin(row.plugin)}`, rowActions(row, this.projectId));
|
|
140
146
|
if (action && action !== BACK && await applyPluginAction(this.options.ui, this.options.call, row, action))
|
|
141
147
|
this.restart = true;
|
|
142
148
|
}
|
|
@@ -220,7 +226,7 @@ export class PluginsPage {
|
|
|
220
226
|
const selected = selectedId?.startsWith(SETTINGS_ROW)
|
|
221
227
|
? this.plugins.find(candidate => `${SETTINGS_ROW}${candidate.id}` === selectedId)
|
|
222
228
|
: this.menu().find(candidate => this.key(candidate) === selectedId)?.plugin;
|
|
223
|
-
const detail = selected && rows >= 8 ? wrapTextWithAnsi(safeText(selected
|
|
229
|
+
const detail = selected && rows >= 8 ? wrapTextWithAnsi(safeText(describePlugin(selected)), width).slice(0, 3) : [];
|
|
224
230
|
const note = this.restart ? ['Saved. Changes apply when this session restarts.'] : [];
|
|
225
231
|
const extra = detail.length + note.length;
|
|
226
232
|
const lines = this.list.render(width, Math.max(2, rows - (extra ? extra + 1 : 0)));
|
|
@@ -1,12 +1,76 @@
|
|
|
1
1
|
import { matchesKey, stripTerminalSequences, truncateToWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
2
|
-
import { headerRow, neutralRule, padded } from "../chrome.js";
|
|
2
|
+
import { CHROME_PAD, headerRow, neutralRule, padded } from "../chrome.js";
|
|
3
3
|
import { FilterList } from "../list.js";
|
|
4
|
-
import { ACTIVE_SUBAGENT_STATUSES, terminalSubagentDuration
|
|
4
|
+
import { ACTIVE_SUBAGENT_STATUSES, terminalSubagentDuration } from "../../subagentMonitor.js";
|
|
5
|
+
import { profileColor, opentuiTheme } from "../../opentui/theme.js";
|
|
5
6
|
import { subagentTasks } from "../../opentui/subagentsWidget.js";
|
|
7
|
+
import { formatTokenCount } from "../status.js";
|
|
8
|
+
import { getWorkingSpinnerFrames, SPINNER_INTERVAL_MS, spinnerFrameAt } from "../workingStatus.js";
|
|
6
9
|
const TABS = [{ id: 'subagents', title: 'Subagents' }];
|
|
7
10
|
function clean(value) {
|
|
8
11
|
return stripTerminalSequences(value).replace(/[\x00-\x08\x0b-\x1f\x7f]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
9
12
|
}
|
|
13
|
+
function statusGlyph(item, now) {
|
|
14
|
+
if (item.status === 'running') {
|
|
15
|
+
const started = Date.parse(item.startedAt);
|
|
16
|
+
return spinnerFrameAt(now - (Number.isFinite(started) ? started : now), getWorkingSpinnerFrames(process.env.TERM), SPINNER_INTERVAL_MS);
|
|
17
|
+
}
|
|
18
|
+
switch (item.status) {
|
|
19
|
+
case 'queued': return '○';
|
|
20
|
+
case 'waiting_for_approval':
|
|
21
|
+
case 'waiting_for_input': return '●';
|
|
22
|
+
case 'completed': return '✓';
|
|
23
|
+
case 'failed': return '✕';
|
|
24
|
+
case 'interrupted': return '!';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function statusGlyphTone(item) {
|
|
28
|
+
if (item.status === 'completed')
|
|
29
|
+
return 'success';
|
|
30
|
+
if (item.status === 'failed')
|
|
31
|
+
return 'error';
|
|
32
|
+
if (item.status === 'waiting_for_approval' || item.status === 'waiting_for_input')
|
|
33
|
+
return 'accent';
|
|
34
|
+
if (item.status === 'running')
|
|
35
|
+
return 'warning';
|
|
36
|
+
return 'muted';
|
|
37
|
+
}
|
|
38
|
+
function effectiveModel(item) {
|
|
39
|
+
const model = item.usage?.model;
|
|
40
|
+
if (model)
|
|
41
|
+
return `${item.usage?.provider ? `${item.usage.provider}/` : ''}${model}`;
|
|
42
|
+
return item.model ?? '—';
|
|
43
|
+
}
|
|
44
|
+
function usageSummary(item) {
|
|
45
|
+
const usage = item.usage;
|
|
46
|
+
if (!usage)
|
|
47
|
+
return 'usage —';
|
|
48
|
+
const cache = [usage.cacheReadTokens, usage.cacheWriteTokens]
|
|
49
|
+
.filter((value) => typeof value === 'number' && value > 0)
|
|
50
|
+
.map(formatTokenCount);
|
|
51
|
+
const cost = typeof usage.costUsd === 'number'
|
|
52
|
+
? `${usage.costSource === 'estimate' ? '~' : ''}$${usage.costUsd.toFixed(usage.costUsd > 0 && usage.costUsd < 0.001 ? 5 : usage.costUsd < 0.01 ? 4 : 2).replace(/0+$/u, '').replace(/\.$/u, '')}`
|
|
53
|
+
: '';
|
|
54
|
+
const fields = [
|
|
55
|
+
typeof usage.inputTokens === 'number' ? `↑${formatTokenCount(usage.inputTokens)}` : '',
|
|
56
|
+
typeof usage.outputTokens === 'number' ? `↓${formatTokenCount(usage.outputTokens)}` : '',
|
|
57
|
+
cache.some(Boolean) ? `c${cache.join('/')}` : '',
|
|
58
|
+
cost,
|
|
59
|
+
typeof usage.contextTokens === 'number' ? `ctx${formatTokenCount(usage.contextTokens)}${typeof usage.contextWindow === 'number' ? `/${formatTokenCount(usage.contextWindow)}` : ''}` : '',
|
|
60
|
+
typeof usage.requests === 'number' ? `req${usage.requests}` : ''
|
|
61
|
+
].filter(Boolean);
|
|
62
|
+
return fields.length ? fields.join(' ') : 'usage —';
|
|
63
|
+
}
|
|
64
|
+
function rowSubtitle(item, width) {
|
|
65
|
+
const available = Math.max(1, width - 2);
|
|
66
|
+
const verbose = width >= 96;
|
|
67
|
+
const account = clean(item.accountLabel ?? '') || '—';
|
|
68
|
+
const model = truncateToWidth(effectiveModel(item), 24, '…');
|
|
69
|
+
const details = verbose
|
|
70
|
+
? [`acct ${account}`, `model ${model}`, `eff ${item.effort ?? '—'}`, usageSummary(item)].join(' · ')
|
|
71
|
+
: [`a:${account}`, `m:${model}`, `e:${item.effort ?? '—'}`, usageSummary(item)].join(' · ');
|
|
72
|
+
return truncateToWidth(details, available, '…');
|
|
73
|
+
}
|
|
10
74
|
export class SubagentsPage {
|
|
11
75
|
options;
|
|
12
76
|
presentation = 'fullscreen';
|
|
@@ -14,7 +78,8 @@ export class SubagentsPage {
|
|
|
14
78
|
constructor(options) {
|
|
15
79
|
this.options = options;
|
|
16
80
|
this.list = new FilterList({
|
|
17
|
-
theme: options.theme, search: 'always', noun: ['subagent', 'subagents'],
|
|
81
|
+
theme: options.theme, search: 'always', noun: ['subagent', 'subagents'], rowGap: 1,
|
|
82
|
+
empty: 'No subagents have started in the latest run'
|
|
18
83
|
});
|
|
19
84
|
}
|
|
20
85
|
get focused() { return this.list.focused; }
|
|
@@ -22,18 +87,33 @@ export class SubagentsPage {
|
|
|
22
87
|
tabs() { return TABS; }
|
|
23
88
|
activeTab() { return 'subagents'; }
|
|
24
89
|
hint() { return '↑↓ select · type to search'; }
|
|
25
|
-
rows() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
90
|
+
rows(width = 80, now = this.options.now?.() ?? Date.now()) {
|
|
91
|
+
const ordered = [...this.options.snapshot()].sort((left, right) => {
|
|
92
|
+
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);
|
|
94
|
+
});
|
|
95
|
+
return ordered.map(item => {
|
|
96
|
+
const profile = clean(item.profile?.name ?? '') || 'subagent';
|
|
97
|
+
const title = clean(item.title) || 'Subagent';
|
|
98
|
+
const color = item.profile?.color ? profileColor(opentuiTheme, item.profile.color) : null;
|
|
99
|
+
const glyph = statusGlyph(item, now);
|
|
100
|
+
const labelParts = [
|
|
101
|
+
{ text: `${glyph} `, ...(item.status === 'running' && color ? { color } : { tone: statusGlyphTone(item) }) },
|
|
102
|
+
{ text: profile, ...(color ? { color } : { tone: 'muted' }) },
|
|
103
|
+
{ text: ` ${title}` }
|
|
104
|
+
];
|
|
105
|
+
return {
|
|
106
|
+
id: item.id,
|
|
107
|
+
label: labelParts.map(part => part.text).join(''),
|
|
108
|
+
labelParts,
|
|
109
|
+
subtitle: rowSubtitle(item, width),
|
|
110
|
+
searchText: `${profile} ${title} ${item.goal ?? ''} ${item.status} ${item.activity} ${item.accountLabel ?? ''} ${effectiveModel(item)} ${item.effort ?? ''} ${usageSummary(item)}`,
|
|
111
|
+
data: item
|
|
112
|
+
};
|
|
113
|
+
});
|
|
34
114
|
}
|
|
35
115
|
render(width, rows) {
|
|
36
|
-
const items = this.rows();
|
|
116
|
+
const items = this.rows(width, this.options.now?.() ?? Date.now());
|
|
37
117
|
this.list.setRows(items);
|
|
38
118
|
const active = items.filter(item => item.data && ACTIVE_SUBAGENT_STATUSES.has(item.data.status)).length;
|
|
39
119
|
const selected = this.list.selected?.data;
|
|
@@ -43,14 +123,16 @@ export class SubagentsPage {
|
|
|
43
123
|
const output = [header, '', ...this.list.render(width, listRows), ''];
|
|
44
124
|
if (selected) {
|
|
45
125
|
const theme = this.options.theme();
|
|
46
|
-
const
|
|
126
|
+
const detailIndent = ' '.repeat(CHROME_PAD * 2);
|
|
127
|
+
const detailLines = (text) => wrapTextWithAnsi(theme.fg('text', clean(text)), Math.max(1, width - detailIndent.length))
|
|
128
|
+
.map(line => `${detailIndent}${line}`);
|
|
47
129
|
output.push(neutralRule(width, theme));
|
|
48
130
|
output.push(padded(theme.fg('muted', `Profile: ${clean(selected.profile?.name ?? '') || 'copy of the parent'}`), width));
|
|
49
131
|
output.push(padded(theme.fg('accent', 'Task'), width));
|
|
50
|
-
output.push(...
|
|
132
|
+
output.push(...detailLines(selected.goal || 'Subagent'));
|
|
51
133
|
output.push(padded(theme.fg('muted', `Status: ${[selected.status.replaceAll('_', ' '), subagentTasks(selected.checklist), terminalSubagentDuration(selected)].filter(Boolean).join(' · ')}`), width));
|
|
52
134
|
output.push(padded(theme.fg('accent', 'Current activity'), width));
|
|
53
|
-
output.push(...
|
|
135
|
+
output.push(...detailLines(selected.activity));
|
|
54
136
|
}
|
|
55
137
|
else {
|
|
56
138
|
output.push(padded(this.options.theme().fg('muted', 'The selected subagent’s task and current activity appear here.'), width));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Theme } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import type { UsagePeriod } from "../../../usage/types.js";
|
|
3
|
+
import type { TabInfo } from "../chrome.js";
|
|
4
|
+
import type { TerminalPage } from "../page.js";
|
|
5
|
+
type UsageTab = 'analysis' | 'limits';
|
|
6
|
+
interface UsagePageOptions {
|
|
7
|
+
theme(): Theme;
|
|
8
|
+
call(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
9
|
+
changed(): void;
|
|
10
|
+
initialTab?: UsageTab;
|
|
11
|
+
initialPeriod?: UsagePeriod;
|
|
12
|
+
}
|
|
13
|
+
export declare class UsagePage implements TerminalPage {
|
|
14
|
+
private readonly options;
|
|
15
|
+
presentation: "centered";
|
|
16
|
+
focused: boolean;
|
|
17
|
+
private tab;
|
|
18
|
+
private period;
|
|
19
|
+
private scope;
|
|
20
|
+
private summary?;
|
|
21
|
+
private limitsSummary?;
|
|
22
|
+
private limits?;
|
|
23
|
+
private busy;
|
|
24
|
+
private error?;
|
|
25
|
+
private refreshLimits;
|
|
26
|
+
private offset;
|
|
27
|
+
private scrollRows;
|
|
28
|
+
private maxOffset;
|
|
29
|
+
private disposed;
|
|
30
|
+
constructor(options: UsagePageOptions);
|
|
31
|
+
tabs(): readonly TabInfo[];
|
|
32
|
+
activeTab(): string;
|
|
33
|
+
selectTab(id: string): void;
|
|
34
|
+
hint(): string;
|
|
35
|
+
activate(): void;
|
|
36
|
+
dispose(): void;
|
|
37
|
+
private load;
|
|
38
|
+
handleInput(data: string): boolean;
|
|
39
|
+
private analysisContent;
|
|
40
|
+
private limitsContent;
|
|
41
|
+
render(width: number, rows: number): string[];
|
|
42
|
+
}
|
|
43
|
+
export {};
|