@xingular/harness 1.1.1 → 1.3.1-unstable.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 +48 -0
- package/README.md +36 -28
- package/dist/cli.js +10837 -6159
- 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 +18 -4
- package/dist/sdk/agents/planResearch.d.ts +27 -0
- package/dist/sdk/agents/planResearch.js +82 -0
- package/dist/sdk/agents/profiles.d.ts +5 -1
- package/dist/sdk/agents/profiles.js +21 -5
- 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/capabilities/runSnapshot.js +10 -4
- 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 +258 -0
- package/dist/sdk/commands/registry.js +48 -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/shell.d.ts +20 -0
- package/dist/sdk/core/shell.js +73 -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 +20 -0
- package/dist/sdk/core/workspaceBashTool.js +50 -0
- package/dist/sdk/core/workspaceSandbox.d.ts +41 -0
- package/dist/sdk/core/workspaceSandbox.js +360 -0
- package/dist/sdk/core/workspaceWriteOperations.d.ts +33 -0
- package/dist/sdk/core/workspaceWriteOperations.js +224 -0
- package/dist/sdk/doctor/checks.js +71 -12
- 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/hub/hub.js +5 -3
- package/dist/sdk/index.d.ts +8 -3
- package/dist/sdk/index.js +4 -1
- package/dist/sdk/pi/runtime.d.ts +2 -1
- package/dist/sdk/pi/runtime.js +2 -2
- package/dist/sdk/plugins/builtin.js +32 -15
- 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/install.d.ts +6 -2
- package/dist/sdk/plugins/install.js +29 -5
- package/dist/sdk/plugins/inventory.js +6 -5
- 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 -31
- package/dist/sdk/prompt/standard.d.ts +17 -0
- package/dist/sdk/prompt/standard.js +21 -0
- package/dist/sdk/server/methods.js +22 -2
- 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 +7 -2
- package/dist/sdk/sessions/continuation.js +11 -6
- package/dist/sdk/sessions/contracts.d.ts +190 -4
- package/dist/sdk/sessions/contracts.js +71 -8
- 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 +74 -17
- 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 +38 -1
- 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 +225 -22
- package/dist/sdk/sessions/subagentService.d.ts +24 -0
- package/dist/sdk/sessions/subagentService.js +225 -26
- package/dist/sdk/sessions/subagents.d.ts +10 -1
- package/dist/sdk/sessions/subagents.js +4 -0
- package/dist/sdk/sessions/workSlice.d.ts +106 -0
- package/dist/sdk/sessions/workSlice.js +329 -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 +31 -3
- package/dist/sdk/terminal/controller.js +81 -29
- 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 +43 -0
- package/dist/sdk/terminal/opentui/theme.js +88 -0
- package/dist/sdk/terminal/piExtension.d.ts +6 -1
- package/dist/sdk/terminal/piExtension.js +177 -63
- package/dist/sdk/terminal/standalone.d.ts +7 -0
- package/dist/sdk/terminal/standalone.js +72 -13
- package/dist/sdk/terminal/subagentMonitor.d.ts +28 -6
- package/dist/sdk/terminal/subagentMonitor.js +61 -9
- package/dist/sdk/terminal/terminals.d.ts +26 -2
- package/dist/sdk/terminal/terminals.js +325 -43
- 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 +1 -1
- package/dist/sdk/terminal/tui/annotateCommand.js +3 -3
- 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 +3 -1
- package/dist/sdk/terminal/tui/commandPages.js +17 -4
- package/dist/sdk/terminal/tui/commands.d.ts +6 -0
- package/dist/sdk/terminal/tui/commands.js +213 -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 +21 -5
- package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +1 -1
- 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/notesPage.d.ts +5 -5
- package/dist/sdk/terminal/tui/pages/notesPage.js +18 -18
- package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +8 -6
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +206 -28
- package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +1 -0
- package/dist/sdk/terminal/tui/pages/subagentsPage.js +104 -19
- 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/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/sessionName.d.ts +2 -0
- package/dist/sdk/tools/sessionName.js +5 -3
- package/dist/sdk/tools/spawnSubagent.js +11 -8
- package/dist/sdk/tools/todo.d.ts +15 -0
- package/dist/sdk/tools/todo.js +64 -0
- 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 +9204 -4812
- package/dist/terminal-pi.js +1848 -272
- 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 +169 -36
- package/docs/architecture/04-prompt.md +55 -15
- package/docs/architecture/06-plugins-and-capabilities.md +36 -15
- package/docs/architecture/07-tools-and-gate.md +131 -25
- package/docs/architecture/08-memory-and-history.md +4 -2
- package/docs/architecture/09-accounts-and-connectors.md +39 -3
- package/docs/architecture/10-server-protocol.md +71 -9
- package/docs/architecture/11-terminal.md +148 -60
- 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 +58 -8
- package/docs/architecture/README.md +20 -17
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +2 -0
- 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 +258 -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/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/README.md +24 -6
- 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/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/doctor.md +14 -1
- 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 +83 -27
- package/docs/releases/1.1.1.md +28 -0
- package/docs/releases/1.2.0.md +49 -0
- package/docs/releases/1.3.0.md +21 -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 +162 -82
- package/package.json +4 -3
- package/dist/sdk/tools/checklist.d.ts +0 -9
- package/dist/sdk/tools/checklist.js +0 -54
|
@@ -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,35 +87,55 @@ 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
|
+
// The main agent is pinned first so its children read against it.
|
|
92
|
+
const ordered = [...this.options.snapshot()].sort((left, right) => {
|
|
93
|
+
const main = Number(right.relation === 'main') - Number(left.relation === 'main');
|
|
94
|
+
const active = Number(ACTIVE_SUBAGENT_STATUSES.has(right.status)) - Number(ACTIVE_SUBAGENT_STATUSES.has(left.status));
|
|
95
|
+
return main || active || left.startedAt.localeCompare(right.startedAt) || left.id.localeCompare(right.id);
|
|
96
|
+
});
|
|
97
|
+
return ordered.map(item => {
|
|
98
|
+
const profile = clean(item.profile?.name ?? '') || 'subagent';
|
|
99
|
+
const title = clean(item.title) || 'Subagent';
|
|
100
|
+
const color = item.profile?.color ? profileColor(opentuiTheme, item.profile.color) : null;
|
|
101
|
+
const glyph = statusGlyph(item, now);
|
|
102
|
+
const labelParts = [
|
|
103
|
+
{ text: `${glyph} `, ...(item.status === 'running' && color ? { color } : { tone: statusGlyphTone(item) }) },
|
|
104
|
+
{ text: profile, ...(color ? { color } : { tone: 'muted' }) },
|
|
105
|
+
{ text: ` ${title}` }
|
|
106
|
+
];
|
|
107
|
+
return {
|
|
108
|
+
id: item.id,
|
|
109
|
+
label: labelParts.map(part => part.text).join(''),
|
|
110
|
+
labelParts,
|
|
111
|
+
subtitle: rowSubtitle(item, width),
|
|
112
|
+
searchText: `${profile} ${title} ${item.goal ?? ''} ${item.status} ${item.activity} ${item.accountLabel ?? ''} ${effectiveModel(item)} ${item.effort ?? ''} ${usageSummary(item)}`,
|
|
113
|
+
data: item
|
|
114
|
+
};
|
|
115
|
+
});
|
|
34
116
|
}
|
|
35
117
|
render(width, rows) {
|
|
36
|
-
const items = this.rows();
|
|
118
|
+
const items = this.rows(width, this.options.now?.() ?? Date.now());
|
|
37
119
|
this.list.setRows(items);
|
|
38
|
-
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;
|
|
39
122
|
const selected = this.list.selected?.data;
|
|
40
|
-
const header = headerRow('Subagents', `${active} active · ${
|
|
123
|
+
const header = headerRow('Subagents', `${active} active · ${children.length} total`, width, this.options.theme());
|
|
41
124
|
const bodyRows = Math.max(0, rows - 7);
|
|
42
125
|
const listRows = Math.max(1, Math.min(this.list.height, Math.floor(bodyRows * 0.6)));
|
|
43
126
|
const output = [header, '', ...this.list.render(width, listRows), ''];
|
|
44
127
|
if (selected) {
|
|
45
128
|
const theme = this.options.theme();
|
|
46
|
-
const
|
|
129
|
+
const detailIndent = ' '.repeat(CHROME_PAD * 2);
|
|
130
|
+
const detailLines = (text) => wrapTextWithAnsi(theme.fg('text', clean(text)), Math.max(1, width - detailIndent.length))
|
|
131
|
+
.map(line => `${detailIndent}${line}`);
|
|
47
132
|
output.push(neutralRule(width, theme));
|
|
48
133
|
output.push(padded(theme.fg('muted', `Profile: ${clean(selected.profile?.name ?? '') || 'copy of the parent'}`), width));
|
|
49
134
|
output.push(padded(theme.fg('accent', 'Task'), width));
|
|
50
|
-
output.push(...
|
|
51
|
-
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));
|
|
52
137
|
output.push(padded(theme.fg('accent', 'Current activity'), width));
|
|
53
|
-
output.push(...
|
|
138
|
+
output.push(...detailLines(selected.activity));
|
|
54
139
|
}
|
|
55
140
|
else {
|
|
56
141
|
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 {};
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { matchesKey, stripTerminalSequences, truncateToWidth, visibleWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
2
|
+
const PERIODS = [
|
|
3
|
+
{ key: '1', period: 'today', label: 'today' },
|
|
4
|
+
{ key: '2', period: '7d', label: '7d' },
|
|
5
|
+
{ key: '3', period: '30d', label: '30d' },
|
|
6
|
+
{ key: '0', period: 'all', label: 'all time' }
|
|
7
|
+
];
|
|
8
|
+
const SCOPES = [
|
|
9
|
+
{ key: 'c', scope: 'account', label: 'account' },
|
|
10
|
+
{ key: 's', scope: 'session', label: 'session' },
|
|
11
|
+
{ key: 'p', scope: 'project', label: 'project' },
|
|
12
|
+
{ key: 'a', scope: 'all', label: 'all accounts' }
|
|
13
|
+
];
|
|
14
|
+
const BARS = '▁▂▃▄▅▆▇█';
|
|
15
|
+
function clean(value) {
|
|
16
|
+
return stripTerminalSequences(value).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, ' ').replace(/\t/g, ' ');
|
|
17
|
+
}
|
|
18
|
+
function oneLine(value) {
|
|
19
|
+
return clean(value).replace(/\s+/g, ' ').trim();
|
|
20
|
+
}
|
|
21
|
+
function count(value) {
|
|
22
|
+
if (!Number.isFinite(value))
|
|
23
|
+
return '0';
|
|
24
|
+
if (value >= 1_000_000_000)
|
|
25
|
+
return `${(value / 1_000_000_000).toFixed(1)}B`;
|
|
26
|
+
if (value >= 1_000_000)
|
|
27
|
+
return `${(value / 1_000_000).toFixed(1)}M`;
|
|
28
|
+
if (value >= 1_000)
|
|
29
|
+
return `${(value / 1_000).toFixed(1)}K`;
|
|
30
|
+
return String(Math.round(value));
|
|
31
|
+
}
|
|
32
|
+
function money(value) {
|
|
33
|
+
if (value === null)
|
|
34
|
+
return '—';
|
|
35
|
+
return value >= 1 ? `$${value.toFixed(2)}` : `$${value.toFixed(3)}`;
|
|
36
|
+
}
|
|
37
|
+
function duration(milliseconds) {
|
|
38
|
+
if (milliseconds === null)
|
|
39
|
+
return '—';
|
|
40
|
+
if (milliseconds < 1_000)
|
|
41
|
+
return `${Math.round(milliseconds)}ms`;
|
|
42
|
+
return `${(milliseconds / 1_000).toFixed(1)}s`;
|
|
43
|
+
}
|
|
44
|
+
function until(iso, now = Date.now()) {
|
|
45
|
+
if (!iso)
|
|
46
|
+
return '';
|
|
47
|
+
const at = Date.parse(iso);
|
|
48
|
+
if (Number.isNaN(at))
|
|
49
|
+
return '';
|
|
50
|
+
const delta = at - now;
|
|
51
|
+
if (delta <= 0)
|
|
52
|
+
return 'reset due';
|
|
53
|
+
const minutes = Math.round(delta / 60_000);
|
|
54
|
+
if (minutes < 60)
|
|
55
|
+
return `resets in ${minutes}m`;
|
|
56
|
+
const hours = Math.floor(minutes / 60);
|
|
57
|
+
if (hours < 48)
|
|
58
|
+
return `resets in ${hours}h ${minutes % 60}m`;
|
|
59
|
+
return `resets in ${Math.floor(hours / 24)}d`;
|
|
60
|
+
}
|
|
61
|
+
function ago(iso) {
|
|
62
|
+
const at = Date.parse(iso);
|
|
63
|
+
if (Number.isNaN(at))
|
|
64
|
+
return 'unknown time';
|
|
65
|
+
const minutes = Math.max(0, Math.round((Date.now() - at) / 60_000));
|
|
66
|
+
if (minutes < 1)
|
|
67
|
+
return 'just now';
|
|
68
|
+
if (minutes < 60)
|
|
69
|
+
return `${minutes}m ago`;
|
|
70
|
+
const hours = Math.round(minutes / 60);
|
|
71
|
+
return hours < 48 ? `${hours}h ago` : `${Math.round(hours / 24)}d ago`;
|
|
72
|
+
}
|
|
73
|
+
function usageLine(totals) {
|
|
74
|
+
const parts = [
|
|
75
|
+
`${count(totals.inputTokens)} in`,
|
|
76
|
+
`${count(totals.cacheReadTokens)} cached`,
|
|
77
|
+
`${count(totals.outputTokens)} out`,
|
|
78
|
+
`${count(totals.requests)} requests`,
|
|
79
|
+
money(totals.costUsd)
|
|
80
|
+
];
|
|
81
|
+
if (totals.unpricedRequests)
|
|
82
|
+
parts.push(`${totals.unpricedRequests} unpriced`);
|
|
83
|
+
return parts.join(' · ');
|
|
84
|
+
}
|
|
85
|
+
function share(value, total) {
|
|
86
|
+
if (!total)
|
|
87
|
+
return '0%';
|
|
88
|
+
return `${Math.round((value / total) * 100)}%`;
|
|
89
|
+
}
|
|
90
|
+
function tokensOf(row) {
|
|
91
|
+
return row.inputTokens + row.cacheReadTokens + row.cacheWriteTokens + row.outputTokens;
|
|
92
|
+
}
|
|
93
|
+
/** A quota-left bar: green with room, amber when it narrows, red when it runs out. */
|
|
94
|
+
function barTone(percent, rejected = false) {
|
|
95
|
+
if (rejected)
|
|
96
|
+
return 'error';
|
|
97
|
+
if (percent === null || percent === undefined)
|
|
98
|
+
return 'dim';
|
|
99
|
+
if (percent >= 50)
|
|
100
|
+
return 'success';
|
|
101
|
+
if (percent >= 20)
|
|
102
|
+
return 'warning';
|
|
103
|
+
return 'error';
|
|
104
|
+
}
|
|
105
|
+
function bar(percent, width, theme, rejected = false) {
|
|
106
|
+
const track = '░'.repeat(width);
|
|
107
|
+
if (percent === null || percent === undefined)
|
|
108
|
+
return `[${theme.fg('dim', track)}]`;
|
|
109
|
+
const filled = Math.max(0, Math.min(width, Math.round((Math.max(0, Math.min(100, percent)) / 100) * width)));
|
|
110
|
+
return `[${theme.fg(barTone(percent, rejected), '█'.repeat(filled))}${theme.fg('dim', '░'.repeat(Math.max(0, width - filled)))}]`;
|
|
111
|
+
}
|
|
112
|
+
/** A label on the left and a quiet value on the right of the same row. */
|
|
113
|
+
function spread(left, right, width) {
|
|
114
|
+
if (!right)
|
|
115
|
+
return left;
|
|
116
|
+
const rightText = truncateToWidth(right, Math.max(0, width - visibleWidth(left) - 2), '');
|
|
117
|
+
return left + ' '.repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(rightText))) + rightText;
|
|
118
|
+
}
|
|
119
|
+
export class UsagePage {
|
|
120
|
+
options;
|
|
121
|
+
presentation = 'centered';
|
|
122
|
+
focused = false;
|
|
123
|
+
tab;
|
|
124
|
+
period = '7d';
|
|
125
|
+
scope = 'account';
|
|
126
|
+
summary;
|
|
127
|
+
limitsSummary;
|
|
128
|
+
limits;
|
|
129
|
+
busy = false;
|
|
130
|
+
error;
|
|
131
|
+
refreshLimits;
|
|
132
|
+
offset = 0;
|
|
133
|
+
scrollRows = 1;
|
|
134
|
+
maxOffset = 0;
|
|
135
|
+
disposed = false;
|
|
136
|
+
constructor(options) {
|
|
137
|
+
this.options = options;
|
|
138
|
+
this.tab = options.initialTab ?? 'analysis';
|
|
139
|
+
this.period = options.initialPeriod ?? '7d';
|
|
140
|
+
this.refreshLimits = this.tab === 'limits';
|
|
141
|
+
}
|
|
142
|
+
tabs() {
|
|
143
|
+
return [{ id: 'analysis', title: 'Analysis' }, { id: 'limits', title: 'Limits' }];
|
|
144
|
+
}
|
|
145
|
+
activeTab() {
|
|
146
|
+
return this.tab;
|
|
147
|
+
}
|
|
148
|
+
selectTab(id) {
|
|
149
|
+
if ((id === 'analysis' || id === 'limits') && id !== this.tab) {
|
|
150
|
+
this.tab = id;
|
|
151
|
+
this.offset = 0;
|
|
152
|
+
this.options.changed();
|
|
153
|
+
if (id === 'limits' && !this.limits) {
|
|
154
|
+
this.refreshLimits = true;
|
|
155
|
+
void this.load();
|
|
156
|
+
}
|
|
157
|
+
else if (id === 'analysis' && !this.summary)
|
|
158
|
+
void this.load();
|
|
159
|
+
else if (id === 'limits' && this.refreshLimits)
|
|
160
|
+
void this.load();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
hint() {
|
|
164
|
+
return this.tab === 'analysis'
|
|
165
|
+
? '↑/↓ scroll · PgUp/PgDn page · 1 today 2 7d 3 30d 0 all · c account s session p project a all accounts · r refresh · Tab limits · Esc close'
|
|
166
|
+
: '↑/↓ scroll · PgUp/PgDn page · r read provider limits · Tab analysis · Esc close';
|
|
167
|
+
}
|
|
168
|
+
activate() {
|
|
169
|
+
if (!this.summary && !this.limits)
|
|
170
|
+
void this.load();
|
|
171
|
+
}
|
|
172
|
+
dispose() {
|
|
173
|
+
this.disposed = true;
|
|
174
|
+
}
|
|
175
|
+
async load() {
|
|
176
|
+
if (this.busy || this.disposed)
|
|
177
|
+
return;
|
|
178
|
+
this.busy = true;
|
|
179
|
+
this.error = undefined;
|
|
180
|
+
this.options.changed();
|
|
181
|
+
try {
|
|
182
|
+
if (this.tab === 'analysis') {
|
|
183
|
+
this.summary = await this.options.call('terminal.usage.summary', {
|
|
184
|
+
scope: this.scope,
|
|
185
|
+
period: this.period,
|
|
186
|
+
timezoneOffsetMinutes: -new Date().getTimezoneOffset()
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// The provider's windows and this app's own ledger answer different questions; show both.
|
|
191
|
+
const [limits, summary] = await Promise.all([
|
|
192
|
+
this.options.call('terminal.usage.limits', { refresh: this.refreshLimits }),
|
|
193
|
+
this.options.call('terminal.usage.summary', {
|
|
194
|
+
scope: 'account',
|
|
195
|
+
period: '7d',
|
|
196
|
+
timezoneOffsetMinutes: -new Date().getTimezoneOffset()
|
|
197
|
+
})
|
|
198
|
+
]);
|
|
199
|
+
this.limits = limits;
|
|
200
|
+
this.limitsSummary = summary;
|
|
201
|
+
this.refreshLimits = false;
|
|
202
|
+
}
|
|
203
|
+
this.offset = 0;
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
this.error = error instanceof Error ? error.message : 'Usage could not be read.';
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
this.busy = false;
|
|
210
|
+
if (!this.disposed)
|
|
211
|
+
this.options.changed();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
handleInput(data) {
|
|
215
|
+
const page = Math.max(1, this.scrollRows - 1);
|
|
216
|
+
if (matchesKey(data, 'up'))
|
|
217
|
+
this.offset = Math.max(0, this.offset - 1);
|
|
218
|
+
else if (matchesKey(data, 'down'))
|
|
219
|
+
this.offset = Math.min(this.maxOffset, this.offset + 1);
|
|
220
|
+
else if (matchesKey(data, 'pageUp'))
|
|
221
|
+
this.offset = Math.max(0, this.offset - page);
|
|
222
|
+
else if (matchesKey(data, 'pageDown'))
|
|
223
|
+
this.offset = Math.min(this.maxOffset, this.offset + page);
|
|
224
|
+
else if (matchesKey(data, 'home'))
|
|
225
|
+
this.offset = 0;
|
|
226
|
+
else if (matchesKey(data, 'end'))
|
|
227
|
+
this.offset = this.maxOffset;
|
|
228
|
+
else if (data === 'r') {
|
|
229
|
+
if (this.tab === 'limits')
|
|
230
|
+
this.refreshLimits = true;
|
|
231
|
+
void this.load();
|
|
232
|
+
}
|
|
233
|
+
else if (this.tab === 'analysis' && PERIODS.some((entry) => entry.key === data)) {
|
|
234
|
+
this.period = PERIODS.find((entry) => entry.key === data).period;
|
|
235
|
+
void this.load();
|
|
236
|
+
}
|
|
237
|
+
else if (this.tab === 'analysis' && SCOPES.some((entry) => entry.key === data)) {
|
|
238
|
+
this.scope = SCOPES.find((entry) => entry.key === data).scope;
|
|
239
|
+
void this.load();
|
|
240
|
+
}
|
|
241
|
+
else
|
|
242
|
+
return false;
|
|
243
|
+
this.options.changed();
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
analysisContent(width, theme) {
|
|
247
|
+
const summary = this.summary;
|
|
248
|
+
const header = [];
|
|
249
|
+
if (!summary) {
|
|
250
|
+
header.push(theme.fg(this.error ? 'error' : 'dim', this.error ?? (this.busy ? 'Reading usage…' : 'No usage to show.')));
|
|
251
|
+
return { header, body: [] };
|
|
252
|
+
}
|
|
253
|
+
const scope = SCOPES.find((entry) => entry.scope === summary.scope)?.label ?? summary.scope;
|
|
254
|
+
const period = PERIODS.find((entry) => entry.period === summary.period)?.label ?? summary.period;
|
|
255
|
+
header.push(theme.bold(theme.fg('accent', `${scope} · ${period}`)));
|
|
256
|
+
header.push(...wrapTextWithAnsi(theme.fg('muted', usageLine(summary.totals)), width));
|
|
257
|
+
const details = [
|
|
258
|
+
summary.totals.cacheHitPercent === null ? null : `${summary.totals.cacheHitPercent}% cache hits`,
|
|
259
|
+
`TTFT ${duration(summary.totals.avgTtftMs)}`,
|
|
260
|
+
`request ${duration(summary.totals.avgDurationMs)}`,
|
|
261
|
+
summary.totals.estimatedCostUsd ? `${money(summary.totals.estimatedCostUsd)} estimated` : null
|
|
262
|
+
].filter((entry) => Boolean(entry));
|
|
263
|
+
if (details.length)
|
|
264
|
+
header.push(...wrapTextWithAnsi(theme.fg('dim', details.join(' · ')), width));
|
|
265
|
+
if (this.error)
|
|
266
|
+
header.push(...wrapTextWithAnsi(theme.fg('error', this.error), width));
|
|
267
|
+
const body = [''];
|
|
268
|
+
const total = tokensOf(summary.totals);
|
|
269
|
+
const gap = () => {
|
|
270
|
+
if (body.length && body[body.length - 1] !== '')
|
|
271
|
+
body.push('');
|
|
272
|
+
};
|
|
273
|
+
if (summary.byDay.length) {
|
|
274
|
+
const max = Math.max(...summary.byDay.map((day) => day.inputTokens + day.cacheReadTokens + day.outputTokens), 1);
|
|
275
|
+
const spark = summary.byDay.slice(-14).map((day) => BARS[Math.min(BARS.length - 1, Math.round(((day.inputTokens + day.cacheReadTokens + day.outputTokens) / max) * (BARS.length - 1)))]).join('');
|
|
276
|
+
body.push(theme.fg('accent', spark));
|
|
277
|
+
for (const day of summary.byDay.slice(-3).reverse()) {
|
|
278
|
+
body.push(...wrapTextWithAnsi(theme.fg('muted', ` ${day.day} ${count(day.inputTokens + day.cacheReadTokens + day.outputTokens)} tokens · ${count(day.requests)} requests · ${money(day.costUsd)}`), width));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const section = (rows) => {
|
|
282
|
+
if (!rows.length)
|
|
283
|
+
return;
|
|
284
|
+
gap();
|
|
285
|
+
for (const row of rows) {
|
|
286
|
+
const line = ` ${oneLine(row.label)} ${share(tokensOf(row), total)} ${count(tokensOf(row))} tokens · ${count(row.requests)} requests · ${money(row.costUsd)}`;
|
|
287
|
+
body.push(...wrapTextWithAnsi(theme.fg('muted', line), width));
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
section(summary.byModel);
|
|
291
|
+
section(summary.byProvider);
|
|
292
|
+
section(summary.byAccount);
|
|
293
|
+
section(summary.bySession);
|
|
294
|
+
if (body.length === 1)
|
|
295
|
+
body.push(theme.fg('dim', 'No requests were recorded in this period.'));
|
|
296
|
+
while (body.length > 1 && body[body.length - 1] === '')
|
|
297
|
+
body.pop();
|
|
298
|
+
return { header, body };
|
|
299
|
+
}
|
|
300
|
+
limitsContent(width, theme) {
|
|
301
|
+
const report = this.limits;
|
|
302
|
+
const header = [];
|
|
303
|
+
if (!report) {
|
|
304
|
+
header.push(theme.fg(this.error ? 'error' : 'dim', this.error ?? (this.busy ? 'Reading provider limits…' : 'No limit readings yet.')));
|
|
305
|
+
return { header, body: [] };
|
|
306
|
+
}
|
|
307
|
+
if (this.error)
|
|
308
|
+
header.push(...wrapTextWithAnsi(theme.fg('error', this.error), width));
|
|
309
|
+
const body = [];
|
|
310
|
+
const barWidth = Math.max(8, Math.min(16, Math.floor(width / 6)));
|
|
311
|
+
for (const account of report.accounts) {
|
|
312
|
+
if (body.length)
|
|
313
|
+
body.push('');
|
|
314
|
+
body.push(theme.bold(oneLine(account.label)) + theme.fg('dim', `${account.plan ? ` · ${oneLine(account.plan)}` : ''} · ${account.health}`));
|
|
315
|
+
if (!account.providers.length) {
|
|
316
|
+
body.push(theme.fg('dim', ' No limit readings.'));
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
for (const provider of account.providers) {
|
|
320
|
+
body.push('');
|
|
321
|
+
const source = provider.source === 'provider' ? '' : provider.source;
|
|
322
|
+
const meta = [source, ago(provider.observedAt)].filter(Boolean).join(' · ');
|
|
323
|
+
const tone = provider.source === 'unavailable' ? 'warning' : 'muted';
|
|
324
|
+
body.push(...wrapTextWithAnsi(theme.fg(tone, ` ${provider.provider}${meta ? ` · ${meta}` : ''}${provider.reason ? ` · ${oneLine(provider.reason)}` : ''}`), width));
|
|
325
|
+
for (const limit of provider.limits) {
|
|
326
|
+
body.push('');
|
|
327
|
+
const reset = until(limit.resetsAt);
|
|
328
|
+
body.push(...wrapTextWithAnsi(spread(theme.bold(` ${oneLine(limit.label)}`), theme.fg('dim', reset), width), width));
|
|
329
|
+
const used = limit.usedPercent ?? null;
|
|
330
|
+
const remainingPercent = limit.remainingPercent
|
|
331
|
+
?? (used === null
|
|
332
|
+
? limit.remaining !== null && limit.remaining !== undefined && limit.limit !== null && limit.limit !== undefined && limit.limit > 0
|
|
333
|
+
? Math.max(0, Math.min(100, (limit.remaining / limit.limit) * 100))
|
|
334
|
+
: null
|
|
335
|
+
: Math.max(0, Math.min(100, 100 - used)));
|
|
336
|
+
const rejected = limit.status === 'rejected';
|
|
337
|
+
const tone = barTone(remainingPercent, rejected);
|
|
338
|
+
const absolute = limit.remaining !== null && limit.remaining !== undefined
|
|
339
|
+
? `${limit.unit === 'usd' ? money(limit.remaining) : limit.unit === 'currency' ? `${limit.remaining.toFixed(2)} ${limit.detail ?? ''}`.trim() : count(limit.remaining)} left`
|
|
340
|
+
: limit.limit !== null && limit.limit !== undefined
|
|
341
|
+
? `limit ${count(limit.limit)}${limit.unit && limit.unit !== 'percent' ? ` ${limit.unit}` : ''}`
|
|
342
|
+
: '';
|
|
343
|
+
const value = remainingPercent === null ? absolute || '—' : `${Math.round(remainingPercent)}% left`;
|
|
344
|
+
const suffix = remainingPercent !== null && absolute ? ` ${theme.fg('dim', absolute)}` : '';
|
|
345
|
+
body.push(...wrapTextWithAnsi(` ${bar(remainingPercent, barWidth, theme, rejected)} ${theme.fg(tone, value)}${suffix}`, width));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (!report.accounts.length)
|
|
350
|
+
body.push(theme.fg('dim', 'No accounts.'));
|
|
351
|
+
const local = this.limitsSummary?.totals;
|
|
352
|
+
if (local) {
|
|
353
|
+
if (body.length)
|
|
354
|
+
body.push('');
|
|
355
|
+
body.push(...wrapTextWithAnsi(theme.fg('dim', `7d · ${count(local.inputTokens + local.cacheReadTokens + local.outputTokens)} tokens · ${count(local.requests)} requests · ${money(local.costUsd)}`), width));
|
|
356
|
+
}
|
|
357
|
+
return { header, body };
|
|
358
|
+
}
|
|
359
|
+
render(width, rows) {
|
|
360
|
+
if (width < 1 || rows < 1)
|
|
361
|
+
return [];
|
|
362
|
+
const theme = this.options.theme();
|
|
363
|
+
const { header, body } = this.tab === 'analysis' ? this.analysisContent(width, theme) : this.limitsContent(width, theme);
|
|
364
|
+
const bodyRows = Math.max(0, rows - header.length);
|
|
365
|
+
this.scrollRows = Math.max(1, bodyRows);
|
|
366
|
+
this.maxOffset = Math.max(0, body.length - bodyRows);
|
|
367
|
+
this.offset = Math.max(0, Math.min(this.offset, this.maxOffset));
|
|
368
|
+
return [...header, ...body.slice(this.offset, this.offset + bodyRows)].slice(0, rows).map((line) => truncateToWidth(line, width, ''));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Theme, ToolRenderers } from '@earendil-works/pi-coding-agent';
|
|
2
2
|
export interface PiAdapter {
|
|
3
|
-
builtins: InlineExtension[];
|
|
4
3
|
setTheme(theme: Theme): void;
|
|
5
4
|
withBuiltInRenderers(toolName: string, definition: ToolRenderers | undefined): ToolRenderers | undefined;
|
|
6
5
|
readClipboardText(): Promise<string | null>;
|
|
@@ -12,18 +12,16 @@ export function loadPiAdapter() {
|
|
|
12
12
|
const pi = await loadPi();
|
|
13
13
|
assertPiAdapterVersion(pi.VERSION);
|
|
14
14
|
const root = pi.getPackageDir();
|
|
15
|
-
const [
|
|
16
|
-
import(pathToFileURL(join(root, 'dist/extensions/index.js')).href),
|
|
15
|
+
const [theme, renderers, clipboard, images] = await Promise.all([
|
|
17
16
|
import(pathToFileURL(join(root, 'dist/modes/interactive/theme/theme.js')).href),
|
|
18
17
|
import(pathToFileURL(join(root, 'dist/core/tools/renderers/index.js')).href),
|
|
19
18
|
import(pathToFileURL(join(root, 'dist/utils/clipboard.js')).href),
|
|
20
19
|
import(pathToFileURL(join(root, 'dist/utils/clipboard-image.js')).href)
|
|
21
20
|
]);
|
|
22
|
-
if (
|
|
23
|
-
throw new Error('The pinned Pi terminal adapter no longer exposes its required
|
|
21
|
+
if (typeof theme.setThemeInstance !== 'function' || typeof renderers.withBuiltInRenderers !== 'function' || typeof clipboard.readClipboardText !== 'function' || typeof clipboard.readClipboardFilePaths !== 'function' || typeof images.readClipboardImage !== 'function' || typeof images.extensionForImageMimeType !== 'function') {
|
|
22
|
+
throw new Error('The pinned Pi terminal adapter no longer exposes its required presentation hooks.');
|
|
24
23
|
}
|
|
25
24
|
adapter = {
|
|
26
|
-
builtins: extensions.builtInExtensions,
|
|
27
25
|
setTheme: theme.setThemeInstance,
|
|
28
26
|
withBuiltInRenderers: renderers.withBuiltInRenderers,
|
|
29
27
|
readClipboardText: clipboard.readClipboardText,
|
|
@@ -6,6 +6,8 @@ type PageOptions = {
|
|
|
6
6
|
runId?: string;
|
|
7
7
|
url?: string;
|
|
8
8
|
annotate?: AnnotateStart;
|
|
9
|
+
usageTab?: 'analysis' | 'limit';
|
|
10
|
+
usagePeriod?: 'today' | '7d' | '30d' | 'all';
|
|
9
11
|
};
|
|
10
12
|
type OpenPage = (id: CommandPageId, options?: PageOptions) => Promise<void>;
|
|
11
13
|
type Handler = (argument: string) => Promise<void>;
|
|
@@ -3,23 +3,27 @@ import { parseAnnotate } from "./annotateCommand.js";
|
|
|
3
3
|
/** Terminal adapters for commands declared by Harness-owned feature plugins. */
|
|
4
4
|
export function createBuiltinPluginCommandHandlers(host, openPage) {
|
|
5
5
|
const handlers = new Map([
|
|
6
|
-
['
|
|
7
|
-
['
|
|
8
|
-
['trajectory', async (argument) => openPage('trajectory', { runId: argument })],
|
|
9
|
-
['subagents', async () => openPage('subagents')],
|
|
10
|
-
['agents', async () => openPage('agents')],
|
|
11
|
-
['notes', async () => openPage('notes')],
|
|
12
|
-
['annotate', async (argument) => {
|
|
6
|
+
['annotations:notes', async () => openPage('notes')],
|
|
7
|
+
['annotations:annotate', async (argument) => {
|
|
13
8
|
const start = parseAnnotate(argument);
|
|
14
9
|
if (typeof start === 'string')
|
|
15
10
|
host.ui.notify(start, 'error');
|
|
16
11
|
else
|
|
17
12
|
await openPage('annotate', { annotate: start });
|
|
18
|
-
}]
|
|
13
|
+
}],
|
|
14
|
+
['usage:analysis', async (argument) => {
|
|
15
|
+
const period = argument.trim().toLowerCase();
|
|
16
|
+
if (period && !['today', '7d', '30d', 'all'].includes(period)) {
|
|
17
|
+
host.ui.notify('Use /usage:analysis today, 7d, 30d or all.', 'error');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
await openPage('usage', { usageTab: 'analysis', ...(period ? { usagePeriod: period } : {}) });
|
|
21
|
+
}],
|
|
22
|
+
['usage:limit', async () => openPage('usage', { usageTab: 'limit' })]
|
|
19
23
|
]);
|
|
20
24
|
const commands = BUILTIN_PLUGIN_REGISTRY.commands();
|
|
21
25
|
for (const command of commands) {
|
|
22
|
-
if (!handlers.has(command.name))
|
|
26
|
+
if (!handlers.has(`${command.pluginId}:${command.name}`))
|
|
23
27
|
throw new Error(`No terminal handler for built-in plugin command ${command.pluginId}:${command.name}.`);
|
|
24
28
|
}
|
|
25
29
|
return handlers;
|