@xingular/harness 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/CHANGELOG.md +33 -5
  2. package/README.md +70 -19
  3. package/dist/cli.js +10321 -5785
  4. package/dist/sdk/agents/builtin.d.ts +2 -0
  5. package/dist/sdk/agents/builtin.js +34 -0
  6. package/dist/sdk/agents/narrow.d.ts +13 -0
  7. package/dist/sdk/agents/narrow.js +29 -0
  8. package/dist/sdk/agents/profiles.d.ts +80 -0
  9. package/dist/sdk/agents/profiles.js +221 -0
  10. package/dist/sdk/annotations/bridge.d.ts +42 -0
  11. package/dist/sdk/annotations/bridge.js +264 -0
  12. package/dist/sdk/annotations/capture.d.ts +116 -0
  13. package/dist/sdk/annotations/capture.js +308 -0
  14. package/dist/sdk/annotations/delivery.d.ts +72 -0
  15. package/dist/sdk/annotations/delivery.js +130 -0
  16. package/dist/sdk/annotations/inspect.d.ts +10 -0
  17. package/dist/sdk/annotations/inspect.js +33 -0
  18. package/dist/sdk/annotations/isolated.d.ts +6 -0
  19. package/dist/sdk/annotations/isolated.js +51 -0
  20. package/dist/sdk/annotations/pageScript.d.ts +28 -0
  21. package/dist/sdk/annotations/pageScript.js +570 -0
  22. package/dist/sdk/annotations/part.d.ts +10 -0
  23. package/dist/sdk/annotations/part.js +144 -0
  24. package/dist/sdk/annotations/queue.d.ts +4 -0
  25. package/dist/sdk/annotations/queue.js +40 -0
  26. package/dist/sdk/annotations/serverMethods.d.ts +3 -0
  27. package/dist/sdk/annotations/serverMethods.js +62 -0
  28. package/dist/sdk/annotations/store.d.ts +160 -0
  29. package/dist/sdk/annotations/store.js +386 -0
  30. package/dist/sdk/browser/index.d.ts +14 -3
  31. package/dist/sdk/browser/index.js +11 -4
  32. package/dist/sdk/browser/serverMethods.d.ts +3 -0
  33. package/dist/sdk/browser/serverMethods.js +7 -0
  34. package/dist/sdk/browser/session.d.ts +1 -0
  35. package/dist/sdk/browser/session.js +45 -9
  36. package/dist/sdk/cli.js +19 -11
  37. package/dist/sdk/core/bus.js +1 -1
  38. package/dist/sdk/core/env.d.ts +1 -1
  39. package/dist/sdk/core/env.js +1 -1
  40. package/dist/sdk/doctor/serverMethods.d.ts +3 -0
  41. package/dist/sdk/doctor/serverMethods.js +8 -0
  42. package/dist/sdk/harness.d.ts +15 -0
  43. package/dist/sdk/harness.js +44 -5
  44. package/dist/sdk/history/recall.js +8 -8
  45. package/dist/sdk/history/serverMethods.d.ts +3 -0
  46. package/dist/sdk/history/serverMethods.js +8 -0
  47. package/dist/sdk/hub/cli.d.ts +1 -1
  48. package/dist/sdk/hub/cli.js +1 -1
  49. package/dist/sdk/hub/schema.d.ts +1 -1
  50. package/dist/sdk/hub/serverMethods.d.ts +3 -0
  51. package/dist/sdk/hub/serverMethods.js +11 -0
  52. package/dist/sdk/index.d.ts +10 -0
  53. package/dist/sdk/index.js +7 -0
  54. package/dist/sdk/mcp/connections.js +14 -7
  55. package/dist/sdk/mcp/serverMethods.d.ts +3 -0
  56. package/dist/sdk/mcp/serverMethods.js +22 -0
  57. package/dist/sdk/plugins/builtin.d.ts +1 -0
  58. package/dist/sdk/plugins/builtin.js +128 -33
  59. package/dist/sdk/plugins/builtinRegistry.d.ts +53 -0
  60. package/dist/sdk/plugins/builtinRegistry.js +49 -0
  61. package/dist/sdk/plugins/cli.d.ts +1 -1
  62. package/dist/sdk/plugins/cli.js +4 -3
  63. package/dist/sdk/prompt/compile.d.ts +5 -2
  64. package/dist/sdk/prompt/compile.js +25 -8
  65. package/dist/sdk/refine/cli.d.ts +1 -1
  66. package/dist/sdk/refine/cli.js +2 -2
  67. package/dist/sdk/refine/serverMethods.d.ts +3 -0
  68. package/dist/sdk/refine/serverMethods.js +20 -0
  69. package/dist/sdk/server/methods.js +9 -86
  70. package/dist/sdk/server/params.d.ts +4 -0
  71. package/dist/sdk/server/params.js +11 -0
  72. package/dist/sdk/sessions/contracts.d.ts +17 -0
  73. package/dist/sdk/sessions/contracts.js +42 -3
  74. package/dist/sdk/sessions/planMode.d.ts +5 -2
  75. package/dist/sdk/sessions/planMode.js +12 -3
  76. package/dist/sdk/sessions/reasoningSummaries.d.ts +2 -2
  77. package/dist/sdk/sessions/reasoningSummaries.js +3 -3
  78. package/dist/sdk/sessions/registry.d.ts +1 -0
  79. package/dist/sdk/sessions/registry.js +4 -1
  80. package/dist/sdk/sessions/runIndex.js +7 -7
  81. package/dist/sdk/sessions/runStore.js +3 -1
  82. package/dist/sdk/sessions/runner.js +2 -2
  83. package/dist/sdk/sessions/sessions.d.ts +17 -5
  84. package/dist/sdk/sessions/sessions.js +79 -12
  85. package/dist/sdk/sessions/sideQuestionServerMethods.d.ts +3 -0
  86. package/dist/sdk/sessions/sideQuestionServerMethods.js +19 -0
  87. package/dist/sdk/sessions/sideQuestions.d.ts +66 -0
  88. package/dist/sdk/sessions/sideQuestions.js +290 -0
  89. package/dist/sdk/sessions/subagentServerMethods.d.ts +3 -0
  90. package/dist/sdk/sessions/subagentServerMethods.js +41 -0
  91. package/dist/sdk/sessions/subagentService.d.ts +13 -2
  92. package/dist/sdk/sessions/subagentService.js +34 -16
  93. package/dist/sdk/sessions/subagents.d.ts +9 -1
  94. package/dist/sdk/sessions/subagents.js +19 -0
  95. package/dist/sdk/terminal/browser.d.ts +62 -0
  96. package/dist/sdk/terminal/browser.js +41 -0
  97. package/dist/sdk/terminal/browserControl.d.ts +29 -0
  98. package/dist/sdk/terminal/browserControl.js +186 -0
  99. package/dist/sdk/terminal/commandHelp.d.ts +17 -0
  100. package/dist/sdk/terminal/commandHelp.js +20 -0
  101. package/dist/sdk/terminal/controller.d.ts +7 -1
  102. package/dist/sdk/terminal/controller.js +34 -2
  103. package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +28 -0
  104. package/dist/sdk/terminal/opentui/subagentsWidget.js +52 -0
  105. package/dist/sdk/terminal/piExtension.js +33 -48
  106. package/dist/sdk/terminal/socket.d.ts +6 -3
  107. package/dist/sdk/terminal/socket.js +40 -9
  108. package/dist/sdk/terminal/standalone.d.ts +2 -0
  109. package/dist/sdk/terminal/standalone.js +50 -10
  110. package/dist/sdk/terminal/subagentMonitor.d.ts +38 -0
  111. package/dist/sdk/terminal/subagentMonitor.js +121 -0
  112. package/dist/sdk/terminal/terminals.d.ts +42 -6
  113. package/dist/sdk/terminal/terminals.js +359 -18
  114. package/dist/sdk/terminal/trajectory.js +2 -2
  115. package/dist/sdk/terminal/tui/annotateCommand.d.ts +3 -0
  116. package/dist/sdk/terminal/tui/annotateCommand.js +22 -0
  117. package/dist/sdk/terminal/tui/commandPages.d.ts +5 -1
  118. package/dist/sdk/terminal/tui/commandPages.js +54 -6
  119. package/dist/sdk/terminal/tui/commands.d.ts +8 -4
  120. package/dist/sdk/terminal/tui/commands.js +181 -58
  121. package/dist/sdk/terminal/tui/dialogGeometry.d.ts +2 -2
  122. package/dist/sdk/terminal/tui/dialogGeometry.js +4 -2
  123. package/dist/sdk/terminal/tui/dialogs.d.ts +6 -1
  124. package/dist/sdk/terminal/tui/dialogs.js +9 -2
  125. package/dist/sdk/terminal/tui/modelPicker.js +1 -6
  126. package/dist/sdk/terminal/tui/page.d.ts +3 -0
  127. package/dist/sdk/terminal/tui/pageOverlay.js +17 -6
  128. package/dist/sdk/terminal/tui/pages/accountsPage.d.ts +26 -4
  129. package/dist/sdk/terminal/tui/pages/accountsPage.js +264 -36
  130. package/dist/sdk/terminal/tui/pages/agentsPage.d.ts +74 -0
  131. package/dist/sdk/terminal/tui/pages/agentsPage.js +190 -0
  132. package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +94 -0
  133. package/dist/sdk/terminal/tui/pages/annotatePage.js +341 -0
  134. package/dist/sdk/terminal/tui/pages/browserPage.d.ts +87 -0
  135. package/dist/sdk/terminal/tui/pages/browserPage.js +738 -0
  136. package/dist/sdk/terminal/tui/pages/doctorPage.d.ts +2 -0
  137. package/dist/sdk/terminal/tui/pages/doctorPage.js +103 -15
  138. package/dist/sdk/terminal/tui/pages/notesPage.d.ts +75 -0
  139. package/dist/sdk/terminal/tui/pages/notesPage.js +201 -0
  140. package/dist/sdk/terminal/tui/pages/settingsPage.d.ts +8 -0
  141. package/dist/sdk/terminal/tui/pages/settingsPage.js +16 -1
  142. package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +23 -0
  143. package/dist/sdk/terminal/tui/pages/subagentsPage.js +70 -0
  144. package/dist/sdk/terminal/tui/pages/trajectoryPage.d.ts +12 -5
  145. package/dist/sdk/terminal/tui/pages/trajectoryPage.js +156 -83
  146. package/dist/sdk/terminal/tui/pages/worktreesPage.d.ts +72 -0
  147. package/dist/sdk/terminal/tui/pages/worktreesPage.js +203 -0
  148. package/dist/sdk/terminal/tui/planMode.d.ts +1 -1
  149. package/dist/sdk/terminal/tui/planMode.js +1 -1
  150. package/dist/sdk/terminal/tui/pluginCommands.d.ts +14 -0
  151. package/dist/sdk/terminal/tui/pluginCommands.js +27 -0
  152. package/dist/sdk/terminal/tui/questions.d.ts +2 -0
  153. package/dist/sdk/terminal/tui/runtime.js +4 -2
  154. package/dist/sdk/terminal/tui/settingsModel.d.ts +2 -0
  155. package/dist/sdk/terminal/tui/settingsModel.js +32 -2
  156. package/dist/sdk/terminal/tui/sideQuestion.d.ts +26 -26
  157. package/dist/sdk/terminal/tui/sideQuestion.js +131 -98
  158. package/dist/sdk/terminal/tui/ui.d.ts +2 -0
  159. package/dist/sdk/terminal/tui/ui.js +1 -0
  160. package/dist/sdk/tools/askUser.js +26 -8
  161. package/dist/sdk/tools/checklist.js +2 -2
  162. package/dist/sdk/tools/harnessTools.d.ts +2 -0
  163. package/dist/sdk/tools/harnessTools.js +1 -1
  164. package/dist/sdk/tools/spawnSubagent.js +13 -4
  165. package/dist/sdk/web/part.d.ts +10 -0
  166. package/dist/sdk/web/part.js +6 -0
  167. package/dist/sdk/web/serverMethods.d.ts +3 -0
  168. package/dist/sdk/web/serverMethods.js +7 -0
  169. package/dist/sdk/wiki/serverMethods.d.ts +3 -0
  170. package/dist/sdk/wiki/serverMethods.js +26 -0
  171. package/dist/sdk/worktrees/cli.d.ts +11 -0
  172. package/dist/sdk/worktrees/cli.js +146 -0
  173. package/dist/sdk/worktrees/copy.d.ts +9 -0
  174. package/dist/sdk/worktrees/copy.js +111 -0
  175. package/dist/sdk/worktrees/git.d.ts +16 -0
  176. package/dist/sdk/worktrees/git.js +81 -0
  177. package/dist/sdk/worktrees/serverMethods.d.ts +3 -0
  178. package/dist/sdk/worktrees/serverMethods.js +30 -0
  179. package/dist/sdk/worktrees/service.d.ts +73 -0
  180. package/dist/sdk/worktrees/service.js +483 -0
  181. package/dist/sdk/worktrees/setup.d.ts +23 -0
  182. package/dist/sdk/worktrees/setup.js +65 -0
  183. package/dist/sdk/worktrees/types.d.ts +91 -0
  184. package/dist/sdk/worktrees/types.js +1 -0
  185. package/dist/sdk/world/cli.d.ts +2 -2
  186. package/dist/sdk/world/cli.js +6 -6
  187. package/dist/sdk/world/world.d.ts +3 -1
  188. package/dist/sdk/world/world.js +8 -9
  189. package/dist/terminal-opentui.js +14540 -7374
  190. package/dist/terminal-pi.js +285 -666
  191. package/docs/README.md +22 -2
  192. package/docs/architecture/00-overview.md +28 -16
  193. package/docs/architecture/02-sessions-and-runs.md +72 -15
  194. package/docs/architecture/03-variants.md +10 -2
  195. package/docs/architecture/04-prompt.md +26 -6
  196. package/docs/architecture/06-plugins-and-capabilities.md +39 -3
  197. package/docs/architecture/07-tools-and-gate.md +21 -6
  198. package/docs/architecture/09-accounts-and-connectors.md +8 -0
  199. package/docs/architecture/10-server-protocol.md +111 -2
  200. package/docs/architecture/11-terminal.md +185 -5
  201. package/docs/architecture/12-storage.md +15 -1
  202. package/docs/architecture/13-world-tools.md +28 -6
  203. package/docs/architecture/14-surfaces-and-parity.md +89 -0
  204. package/docs/architecture/README.md +15 -4
  205. package/docs/architecture/proposals/PROP-0006-run-checklist.md +4 -2
  206. package/docs/architecture/proposals/PROP-0013-resource-browser.md +58 -0
  207. package/docs/architecture/proposals/PROP-0014-fullscreen-browser-tabs.md +58 -0
  208. package/docs/architecture/proposals/PROP-0015-one-capability-surface.md +88 -0
  209. package/docs/architecture/proposals/PROP-0016-managed-worktrees.md +156 -0
  210. package/docs/architecture/proposals/PROP-0017-page-annotations.md +127 -0
  211. package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +179 -0
  212. package/docs/architecture/proposals/PROP-0019-contextual-side-questions.md +122 -0
  213. package/docs/architecture/proposals/PROP-0020-builtin-feature-plugins.md +105 -0
  214. package/docs/architecture/proposals/PROP-0021-command-namespaces-and-duplicates.md +99 -0
  215. package/docs/architecture/proposals/PROP-0022-plan-only-questions.md +102 -0
  216. package/docs/architecture/proposals/PROP-0023-xagent-cli-name.md +61 -0
  217. package/docs/architecture/proposals/PROP-0024-question-sketch-previews.md +130 -0
  218. package/docs/architecture/proposals/PROP-0025-contextual-question-wireframes.md +49 -0
  219. package/docs/architecture/proposals/PROP-0026-connected-sketch-display.md +94 -0
  220. package/docs/architecture/proposals/PROP-0027-xingular-agent-product-identity.md +60 -0
  221. package/docs/architecture/proposals/PROP-0028-normalize-question-wireframes.md +55 -0
  222. package/docs/architecture/proposals/PROP-0029-agent-profiles.md +124 -0
  223. package/docs/architecture/proposals/PROP-0030-subagent-title.md +86 -0
  224. package/docs/architecture/proposals/README.md +25 -0
  225. package/docs/decisions/ADR-0009-xharness-command.md +6 -5
  226. package/docs/decisions/ADR-0014-public-release-baseline.md +3 -2
  227. package/docs/decisions/ADR-0015-resource-browser.md +28 -0
  228. package/docs/decisions/ADR-0016-fullscreen-browser-tabs.md +31 -0
  229. package/docs/decisions/ADR-0017-one-capability-surface.md +34 -0
  230. package/docs/decisions/ADR-0018-builtin-feature-plugins.md +35 -0
  231. package/docs/decisions/ADR-0019-managed-git-worktrees.md +31 -0
  232. package/docs/decisions/ADR-0020-page-annotations.md +37 -0
  233. package/docs/decisions/ADR-0021-required-command-namespaces.md +40 -0
  234. package/docs/decisions/ADR-0022-plan-only-question-tools.md +34 -0
  235. package/docs/decisions/ADR-0023-contextual-side-question-runs.md +35 -0
  236. package/docs/decisions/ADR-0024-xagent-command.md +40 -0
  237. package/docs/decisions/ADR-0025-question-choice-sketches.md +49 -0
  238. package/docs/decisions/ADR-0026-contextual-question-wireframes.md +58 -0
  239. package/docs/decisions/ADR-0027-connected-sketch-display.md +60 -0
  240. package/docs/decisions/ADR-0028-xingular-agent-product-identity.md +56 -0
  241. package/docs/decisions/ADR-0029-normalized-question-wireframe-borders.md +33 -0
  242. package/docs/decisions/ADR-0030-agent-profiles.md +43 -0
  243. package/docs/doctor.md +10 -4
  244. package/docs/features/README.md +26 -0
  245. package/docs/features/interfaces/README.md +1 -1
  246. package/docs/future-work.md +48 -0
  247. package/docs/parity.md +78 -0
  248. package/docs/releases/1.0.0.md +2 -2
  249. package/docs/releases/4.0.0.md +46 -0
  250. package/docs/releases/README.md +20 -15
  251. package/docs/study/00-index.md +5 -0
  252. package/docs/study/autonomous-engineering-sources.md +277 -0
  253. package/docs/study/autonomy-sources.md +147 -0
  254. package/docs/study/proactive-autonomy.md +408 -0
  255. package/docs/study/tui-resource-browser.md +78 -0
  256. package/docs/terminal-tui.md +325 -93
  257. package/package.json +3 -3
@@ -21,6 +21,8 @@ export declare class DoctorPage implements TerminalPage {
21
21
  private disposed;
22
22
  private busy;
23
23
  private offset;
24
+ private scrollRows;
25
+ private maxOffset;
24
26
  constructor(options: DoctorPageOptions);
25
27
  tabs(): readonly TabInfo[];
26
28
  activeTab(): string;
@@ -1,5 +1,35 @@
1
- import { matchesKey, stripTerminalSequences, wrapTextWithAnsi } from '@earendil-works/pi-tui';
1
+ import { matchesKey, stripTerminalSequences, truncateToWidth, visibleWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
2
2
  import { doctorReportSchema } from "../../../doctor/types.js";
3
+ const STATUSES = ['pass', 'warning', 'fail', 'disabled', 'blocked', 'unverified'];
4
+ const STATUS_TONE = {
5
+ pass: 'success', warning: 'warning', fail: 'error', disabled: 'dim', blocked: 'warning', unverified: 'muted'
6
+ };
7
+ function cleanText(value) {
8
+ return stripTerminalSequences(value).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, ' ').replace(/\t/g, ' ');
9
+ }
10
+ function oneLine(value) {
11
+ return cleanText(value).replace(/\s+/g, ' ').trim();
12
+ }
13
+ function padCell(value, width) {
14
+ const fitted = truncateToWidth(value, width, '');
15
+ return fitted + ' '.repeat(Math.max(0, width - visibleWidth(fitted)));
16
+ }
17
+ function tableRow(cells, widths, width) {
18
+ const wrapped = cells.map((cell, index) => {
19
+ const lines = wrapTextWithAnsi(cell, widths[index]);
20
+ return lines.length ? lines : [''];
21
+ });
22
+ const height = Math.max(...wrapped.map(lines => lines.length));
23
+ return Array.from({ length: height }, (_, row) => truncateToWidth(wrapped.map((lines, column) => padCell(lines[row] ?? '', widths[column])).join(' '), width, ''));
24
+ }
25
+ function appendDetails(lines, label, value, width, theme) {
26
+ if (!value?.trim())
27
+ return;
28
+ for (const paragraph of cleanText(value).split('\n')) {
29
+ const wrapped = wrapTextWithAnsi(`${label}: ${paragraph}`, Math.max(1, width - 2));
30
+ lines.push(...(wrapped.length ? wrapped : [`${label}:`]).map(line => theme.fg('muted', ` ${line}`)));
31
+ }
32
+ }
3
33
  export class DoctorPage {
4
34
  options;
5
35
  focused = false;
@@ -9,12 +39,14 @@ export class DoctorPage {
9
39
  disposed = false;
10
40
  busy = false;
11
41
  offset = 0;
42
+ scrollRows = 1;
43
+ maxOffset = 0;
12
44
  constructor(options) {
13
45
  this.options = options;
14
46
  }
15
47
  tabs() { return [{ id: 'doctor', title: 'Doctor' }]; }
16
48
  activeTab() { return 'doctor'; }
17
- hint() { return '↑/↓ scroll · r refresh · t retry finding · l live checks · c cancel · Esc close'; }
49
+ hint() { return '↑/↓ scroll · PgUp/PgDn/wheel page · Home/End · r refresh · t retry · l live · c cancel · Esc close'; }
18
50
  activate() { if (!this.report && !this.busy)
19
51
  void this.start(); }
20
52
  dispose() {
@@ -104,10 +136,19 @@ export class DoctorPage {
104
136
  await this.start('local', finding.id === 'doctor' ? [] : [finding.id]);
105
137
  }
106
138
  handleInput(data) {
139
+ const page = Math.max(1, this.scrollRows - 1);
107
140
  if (matchesKey(data, 'up'))
108
141
  this.offset = Math.max(0, this.offset - 1);
109
142
  else if (matchesKey(data, 'down'))
110
- this.offset++;
143
+ this.offset = Math.min(this.maxOffset, this.offset + 1);
144
+ else if (matchesKey(data, 'pageUp'))
145
+ this.offset = Math.max(0, this.offset - page);
146
+ else if (matchesKey(data, 'pageDown'))
147
+ this.offset = Math.min(this.maxOffset, this.offset + page);
148
+ else if (matchesKey(data, 'home'))
149
+ this.offset = 0;
150
+ else if (matchesKey(data, 'end'))
151
+ this.offset = this.maxOffset;
111
152
  else if (data === 'r')
112
153
  void this.start();
113
154
  else if (data === 'l')
@@ -128,18 +169,65 @@ export class DoctorPage {
128
169
  render(width, rows) {
129
170
  if (width < 1 || rows < 1)
130
171
  return [];
172
+ const theme = this.options.theme();
131
173
  const report = this.report;
132
- const counts = report?.findings.reduce((all, finding) => { all[finding.status] = (all[finding.status] ?? 0) + 1; return all; }, {}) ?? {};
133
- const lines = [
134
- this.error ?? (report ? `${report.mode} · ${report.state} · ${report.completed}/${report.total} checks` : 'Preparing diagnostics…'),
135
- report ? `Account ${report.context.accountId ?? 'none'} · Model ${report.context.model ?? 'none'}` : '',
136
- Object.entries(counts).map(([status, count]) => `${count} ${status}`).join(' · '),
137
- ...report?.findings.flatMap(finding => [
138
- '', `[${finding.status}] ${finding.category} · ${finding.title} (${finding.verification})`,
139
- finding.evidence, ...(finding.remedy ? [`Action: ${finding.remedy}`] : [])
140
- ]) ?? []
141
- ].flatMap(line => stripTerminalSequences(line).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, ' ').split('\n').flatMap(part => wrapTextWithAnsi(part, width)));
142
- this.offset = Math.max(0, Math.min(this.offset, lines.length - rows));
143
- return lines.slice(this.offset, this.offset + rows).map(line => this.options.theme().fg('text', line));
174
+ const counts = report?.findings.reduce((all, finding) => {
175
+ all[finding.status] = (all[finding.status] ?? 0) + 1;
176
+ return all;
177
+ }, {}) ?? {};
178
+ const stateTone = report?.state === 'completed' ? 'success' : report?.state === 'cancelled' ? 'warning' : 'accent';
179
+ const summary = report ? [
180
+ ...wrapTextWithAnsi(theme.bold(theme.fg(stateTone, `${report.mode} · ${report.state} · ${report.completed}/${report.total} checks`)), width),
181
+ ...wrapTextWithAnsi(theme.fg('muted', `Account ${oneLine(report.context.accountId ?? 'none')} · Model ${oneLine(report.context.model ?? 'none')}`), width),
182
+ ...wrapTextWithAnsi(STATUSES.filter(status => counts[status]).map(status => theme.fg(STATUS_TONE[status], `${counts[status]} ${status}`)).join(theme.fg('dim', ' · ')), width)
183
+ ] : [theme.fg(this.error ? 'error' : 'dim', this.error ?? 'Preparing diagnostics…')];
184
+ if (this.error && report)
185
+ summary.unshift(...wrapTextWithAnsi(theme.fg('error', this.error), width));
186
+ const content = [];
187
+ if (report?.findings.length) {
188
+ const statusWidth = 11;
189
+ const categoryWidth = 14;
190
+ const verificationWidth = 12;
191
+ const gapsWidth = 6;
192
+ const checkWidth = width - statusWidth - categoryWidth - verificationWidth - gapsWidth;
193
+ if (checkWidth >= 18) {
194
+ const widths = [statusWidth, checkWidth, categoryWidth, verificationWidth];
195
+ content.push(...tableRow([
196
+ theme.bold(theme.fg('dim', 'STATUS')),
197
+ theme.bold(theme.fg('dim', 'CHECK')),
198
+ theme.bold(theme.fg('dim', 'CATEGORY')),
199
+ theme.bold(theme.fg('dim', 'VERIFY'))
200
+ ], widths, width));
201
+ for (const [index, finding] of report.findings.entries()) {
202
+ content.push(...tableRow([
203
+ theme.bold(theme.fg(STATUS_TONE[finding.status], finding.status.toUpperCase())),
204
+ oneLine(finding.title), oneLine(finding.category), oneLine(finding.verification)
205
+ ], widths, width));
206
+ appendDetails(content, 'Evidence', finding.evidence, width, theme);
207
+ appendDetails(content, 'Next step', finding.remedy, width, theme);
208
+ if (index < report.findings.length - 1)
209
+ content.push('');
210
+ }
211
+ }
212
+ else {
213
+ for (const [index, finding] of report.findings.entries()) {
214
+ const heading = `${theme.bold(theme.fg(STATUS_TONE[finding.status], finding.status.toUpperCase()))} · ${oneLine(finding.title)}`;
215
+ content.push(...wrapTextWithAnsi(heading, width));
216
+ content.push(...wrapTextWithAnsi(theme.fg('dim', `${oneLine(finding.category)} · ${oneLine(finding.verification)}`), width));
217
+ appendDetails(content, 'Evidence', finding.evidence, width, theme);
218
+ appendDetails(content, 'Next step', finding.remedy, width, theme);
219
+ if (index < report.findings.length - 1)
220
+ content.push('');
221
+ }
222
+ }
223
+ }
224
+ else {
225
+ content.push(theme.fg('dim', report?.state === 'running' ? 'Waiting for findings…' : 'No findings to show.'));
226
+ }
227
+ const bodyRows = Math.max(0, rows - summary.length);
228
+ this.scrollRows = Math.max(1, bodyRows);
229
+ this.maxOffset = Math.max(0, content.length - bodyRows);
230
+ this.offset = Math.max(0, Math.min(this.offset, this.maxOffset));
231
+ return [...summary, ...content.slice(this.offset, this.offset + bodyRows)].slice(0, rows).map(line => truncateToWidth(line, width, ''));
144
232
  }
145
233
  }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The Page notes page (`/plugin:notes`, `/plugin:annotate [url]`): notes a person keeps about web pages. A new note
3
+ * is captured in a visible browser window: the whole page, text the person selects, or an element
4
+ * they click. A note is private until it is shared with this session, whose agent can then read it
5
+ * with `page_notes_read` (PROP-0017).
6
+ */
7
+ import type { Theme } from '@earendil-works/pi-coding-agent';
8
+ import type { MenuCall, MenuUi } from "../../pluginMenu.js";
9
+ import type { TabInfo } from "../chrome.js";
10
+ import { type ListRow } from "../list.js";
11
+ import type { TerminalMouseInput } from "../mouse.js";
12
+ import type { TerminalPage } from "../page.js";
13
+ import type { Note } from "../../../annotations/store.js";
14
+ export interface NotesPageOptions {
15
+ theme(): Theme;
16
+ call: MenuCall;
17
+ ui: MenuUi & {
18
+ input(title: string, placeholder?: string): Promise<string | undefined>;
19
+ };
20
+ changed(): void;
21
+ /** Start a new note as soon as the page opens (`/plugin:annotate [url]`), on this address when given. */
22
+ start?: {
23
+ url?: string;
24
+ };
25
+ }
26
+ type Row = {
27
+ kind: 'new';
28
+ } | {
29
+ kind: 'note';
30
+ note: Note;
31
+ };
32
+ export declare const NOTE_KINDS: {
33
+ readonly page: "The whole page";
34
+ readonly text: "Text I select in the window";
35
+ readonly element: "An element I click in the window";
36
+ };
37
+ export declare const NOTE_ACTIONS: {
38
+ readonly reveal: "Show it in the browser window";
39
+ readonly edit: "Edit the note";
40
+ readonly resolve: "Mark resolved";
41
+ readonly reopen: "Reopen";
42
+ readonly share: "Let this session's agent read it";
43
+ readonly unshare: "Stop sharing it with this session";
44
+ readonly delete: "Delete";
45
+ };
46
+ export declare class NotesPage implements TerminalPage {
47
+ private readonly options;
48
+ presentation: "centered";
49
+ private notes;
50
+ private sessionId;
51
+ private error;
52
+ private busy;
53
+ private disposed;
54
+ private readonly list;
55
+ constructor(options: NotesPageOptions);
56
+ get focused(): boolean;
57
+ set focused(value: boolean);
58
+ activate(): void;
59
+ dispose(): void;
60
+ tabs(): readonly TabInfo[];
61
+ activeTab(): string;
62
+ hint(): string;
63
+ reload(): Promise<void>;
64
+ private shared;
65
+ rows(): ListRow<Row>[];
66
+ private attempt;
67
+ activateRow(row: Row): Promise<void>;
68
+ /** Opens the capture window, takes what the person points at, and saves their note. */
69
+ newNote(startUrl?: string): Promise<void>;
70
+ private noteMenu;
71
+ handleMouse(event: TerminalMouseInput): import("@earendil-works/pi-tui").TuiMouseEventResult | undefined;
72
+ handleInput(data: string): boolean;
73
+ render(width: number, rows: number): string[];
74
+ }
75
+ export {};
@@ -0,0 +1,201 @@
1
+ import { truncateToWidth } from '@earendil-works/pi-tui';
2
+ import { FilterList } from "../list.js";
3
+ const TABS = [{ id: 'notes', title: 'Page notes' }];
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
+ export const NOTE_ACTIONS = {
6
+ reveal: 'Show it in the browser window',
7
+ edit: 'Edit the note',
8
+ resolve: 'Mark resolved',
9
+ reopen: 'Reopen',
10
+ share: 'Let this session\'s agent read it',
11
+ unshare: 'Stop sharing it with this session',
12
+ delete: 'Delete'
13
+ };
14
+ const clip = (text, size) => (text.length > size ? `${text.slice(0, size - 1)}…` : text);
15
+ function describeTarget(target) {
16
+ if (target.kind === 'text')
17
+ return `“${clip(target.exact.replace(/\s+/g, ' '), 60)}”`;
18
+ if (target.kind === 'element')
19
+ return `<${target.tag}> ${clip(target.name || target.text, 50)}`;
20
+ return 'whole page';
21
+ }
22
+ export class NotesPage {
23
+ options;
24
+ presentation = 'centered';
25
+ notes;
26
+ sessionId = null;
27
+ error;
28
+ busy = false;
29
+ disposed = false;
30
+ list;
31
+ constructor(options) {
32
+ this.options = options;
33
+ this.list = new FilterList({
34
+ theme: options.theme, search: 'always', noun: ['note', 'notes'], empty: 'No page notes yet',
35
+ onActivate: row => { if (row.data)
36
+ void this.activateRow(row.data); }
37
+ });
38
+ }
39
+ get focused() { return this.list.focused; }
40
+ set focused(value) { this.list.focused = value; }
41
+ activate() {
42
+ void this.reload().then(() => {
43
+ if (this.options.start)
44
+ void this.newNote(this.options.start.url);
45
+ });
46
+ }
47
+ dispose() { this.disposed = true; }
48
+ tabs() { return TABS; }
49
+ activeTab() { return 'notes'; }
50
+ hint() { return 'Enter choose · type to search · Esc close'; }
51
+ async reload() {
52
+ try {
53
+ const result = await this.options.call('terminal.annotations.list');
54
+ this.notes = result.notes;
55
+ this.sessionId = result.sessionId;
56
+ this.error = undefined;
57
+ }
58
+ catch (error) {
59
+ this.error = error instanceof Error ? error.message : String(error);
60
+ }
61
+ if (this.disposed)
62
+ return;
63
+ this.list.setRows(this.rows());
64
+ this.options.changed();
65
+ }
66
+ shared(note) {
67
+ return note.grants.some(grant => grant.principal.sessionId && grant.principal.sessionId === this.sessionId);
68
+ }
69
+ rows() {
70
+ const rows = [{ id: 'new', label: 'New note on a page…', group: 'Actions', detail: 'opens a browser window', tone: 'accent', data: { kind: 'new' } }];
71
+ for (const note of this.notes ?? []) {
72
+ rows.push({
73
+ id: `note:${note.id}`, label: clip(note.body.replace(/\s+/g, ' '), 60), group: 'Notes',
74
+ detail: `${describeTarget(note.target)} · ${note.status}${this.shared(note) ? ' · shared here' : ''}`,
75
+ tone: note.status === 'resolved' ? 'dim' : this.shared(note) ? 'success' : 'muted',
76
+ searchText: `${note.body} ${note.source.url} ${note.source.title} ${note.tags.join(' ')}`,
77
+ data: { kind: 'note', note }
78
+ });
79
+ }
80
+ return rows;
81
+ }
82
+ async attempt(work) {
83
+ if (this.busy)
84
+ return;
85
+ this.busy = true;
86
+ try {
87
+ const message = await work();
88
+ if (message)
89
+ this.options.ui.notify(message, 'info');
90
+ }
91
+ catch (error) {
92
+ this.options.ui.notify(error instanceof Error ? error.message : String(error), 'error');
93
+ }
94
+ finally {
95
+ this.busy = false;
96
+ }
97
+ await this.reload();
98
+ }
99
+ async activateRow(row) {
100
+ if (row.kind === 'new')
101
+ return this.newNote();
102
+ return this.noteMenu(row.note);
103
+ }
104
+ /** Opens the capture window, takes what the person points at, and saves their note. */
105
+ newNote(startUrl) {
106
+ const { call, ui } = this.options;
107
+ return this.attempt(async () => {
108
+ const url = startUrl ?? await ui.input('Page address', 'https://');
109
+ if (!url)
110
+ return;
111
+ await call('terminal.annotations.open', { url });
112
+ const kind = await ui.select('What does the note point at?', Object.values(NOTE_KINDS));
113
+ if (!kind)
114
+ return;
115
+ let envelope;
116
+ if (kind === NOTE_KINDS.page) {
117
+ const identity = await call('terminal.annotations.identity');
118
+ envelope = { ...identity, target: { kind: 'page' } };
119
+ }
120
+ else {
121
+ const element = kind === NOTE_KINDS.element;
122
+ const { captureId } = await call('terminal.annotations.begin', { kind: element ? 'element' : 'text' });
123
+ const ready = await ui.confirm(element ? 'Click the element in the browser window' : 'Select the text in the browser window', 'When it is selected, choose Yes to capture it. Choose No to cancel.');
124
+ if (!ready) {
125
+ await call('terminal.annotations.cancel', { captureId });
126
+ return;
127
+ }
128
+ envelope = await call('terminal.annotations.capture', { captureId });
129
+ if (!envelope)
130
+ return 'Nothing was selected in the browser window, so no note was made.';
131
+ }
132
+ const body = await ui.input('Note', 'What should be remembered about it?');
133
+ if (!body?.trim())
134
+ return 'A note needs some text; nothing was saved.';
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 note with page_notes_read. You can stop sharing it later.');
137
+ await call('terminal.annotations.create', {
138
+ source: { url: envelope.url, title: envelope.title, capturedAt: new Date().toISOString(), surface: 'terminal', representationHint: envelope.documentId },
139
+ target: envelope.target, body, tags, share
140
+ });
141
+ return share ? 'Note saved and shared with this session.' : 'Note saved. It stays private until you share it.';
142
+ });
143
+ }
144
+ async noteMenu(note) {
145
+ const { call, ui } = this.options;
146
+ const choices = [
147
+ NOTE_ACTIONS.reveal,
148
+ NOTE_ACTIONS.edit,
149
+ note.status === 'open' ? NOTE_ACTIONS.resolve : NOTE_ACTIONS.reopen,
150
+ this.shared(note) ? NOTE_ACTIONS.unshare : NOTE_ACTIONS.share,
151
+ NOTE_ACTIONS.delete
152
+ ];
153
+ const choice = await ui.select(`${clip(note.source.title || note.source.url, 60)} · ${describeTarget(note.target)}`, choices);
154
+ const id = note.id;
155
+ if (choice === NOTE_ACTIONS.reveal)
156
+ return this.attempt(async () => {
157
+ const result = await call('terminal.annotations.reveal', { id });
158
+ return result.state === 'matched' ? 'Marked in the browser window.' : `The target is ${result.state}${result.reason ? `: ${result.reason}` : ''}.`;
159
+ });
160
+ if (choice === NOTE_ACTIONS.edit)
161
+ return this.attempt(async () => {
162
+ const body = await ui.input('Note', note.body);
163
+ if (!body?.trim() || body === note.body)
164
+ return;
165
+ await call('terminal.annotations.update', { id, expectedVersion: note.version, patch: { body } });
166
+ return 'Note saved.';
167
+ });
168
+ if (choice === NOTE_ACTIONS.resolve || choice === NOTE_ACTIONS.reopen)
169
+ return this.attempt(async () => {
170
+ await call('terminal.annotations.update', { id, expectedVersion: note.version, patch: { status: choice === NOTE_ACTIONS.resolve ? 'resolved' : 'open' } });
171
+ });
172
+ if (choice === NOTE_ACTIONS.share || choice === NOTE_ACTIONS.unshare)
173
+ return this.attempt(async () => {
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 note.' : 'The note is no longer shared with this session.';
176
+ });
177
+ if (choice === NOTE_ACTIONS.delete)
178
+ return this.attempt(async () => {
179
+ if (!await ui.confirm('Delete this note?', 'It cannot be restored.'))
180
+ return;
181
+ await call('terminal.annotations.delete', { id, expectedVersion: note.version });
182
+ return 'Note deleted.';
183
+ });
184
+ }
185
+ handleMouse(event) {
186
+ return this.list.handleMouse(event);
187
+ }
188
+ handleInput(data) {
189
+ return this.list.handleInput(data);
190
+ }
191
+ render(width, rows) {
192
+ const theme = this.options.theme();
193
+ if (width < 1 || rows < 1)
194
+ return [];
195
+ if (this.error)
196
+ return [theme.fg('error', `Could not read page notes: ${this.error}`)];
197
+ if (!this.notes)
198
+ return [theme.fg('dim', 'Reading page notes…')];
199
+ return this.list.render(width, rows).slice(0, rows).map(line => truncateToWidth(line, width, ''));
200
+ }
201
+ }
@@ -16,6 +16,8 @@ export interface TerminalSettingItem {
16
16
  export interface SettingsPageOptions {
17
17
  theme: () => Theme;
18
18
  items(): TerminalSettingItem[];
19
+ /** Loads device-local settings that need a protected Harness call. */
20
+ load?(): Promise<void>;
19
21
  /** Changes the setting; the page reads the items again afterwards. */
20
22
  apply(id: string): Promise<void>;
21
23
  notify(message: string, type?: string): void;
@@ -23,10 +25,13 @@ export interface SettingsPageOptions {
23
25
  }
24
26
  export declare class SettingsPage implements TerminalPage {
25
27
  private readonly options;
28
+ readonly presentation = "fullscreen";
26
29
  private items;
27
30
  private category;
31
+ private disposed;
28
32
  private readonly list;
29
33
  constructor(options: SettingsPageOptions);
34
+ dispose(): void;
30
35
  set focused(value: boolean);
31
36
  get focused(): boolean;
32
37
  private categories;
@@ -46,6 +51,9 @@ export declare class SettingsPage implements TerminalPage {
46
51
  } | {
47
52
  readonly id: "context";
48
53
  readonly title: "Context";
54
+ } | {
55
+ readonly id: "browser";
56
+ readonly title: "Browser";
49
57
  })[];
50
58
  activeTab(): string;
51
59
  hint(): string;
@@ -3,13 +3,16 @@ import { safeText } from "../chrome.js";
3
3
  import { FilterList } from "../list.js";
4
4
  const CATEGORIES = [
5
5
  { id: 'appearance', title: 'Appearance' }, { id: 'model', title: 'Model' },
6
- { id: 'interaction', title: 'Interaction' }, { id: 'context', title: 'Context' }
6
+ { id: 'interaction', title: 'Interaction' }, { id: 'context', title: 'Context' },
7
+ { id: 'browser', title: 'Browser' }
7
8
  ];
8
9
  const tabOf = (item) => item.tab.toLowerCase();
9
10
  export class SettingsPage {
10
11
  options;
12
+ presentation = 'fullscreen';
11
13
  items = [];
12
14
  category = '';
15
+ disposed = false;
13
16
  list;
14
17
  constructor(options) {
15
18
  this.options = options;
@@ -21,6 +24,18 @@ export class SettingsPage {
21
24
  }
22
25
  });
23
26
  this.reload();
27
+ if (options.load) {
28
+ void options.load().then(() => {
29
+ if (!this.disposed)
30
+ this.reload();
31
+ }, error => {
32
+ if (!this.disposed)
33
+ options.notify(error instanceof Error ? error.message : String(error), 'error');
34
+ });
35
+ }
36
+ }
37
+ dispose() {
38
+ this.disposed = true;
24
39
  }
25
40
  set focused(value) { this.list.focused = value; }
26
41
  get focused() { return this.list.focused; }
@@ -0,0 +1,23 @@
1
+ import type { Theme } from '@earendil-works/pi-coding-agent';
2
+ import { type TabInfo } from "../chrome.js";
3
+ import type { TerminalPage } from "../page.js";
4
+ import { type TerminalSubagentActivity } from "../../subagentMonitor.js";
5
+ export interface SubagentsPageOptions {
6
+ theme(): Theme;
7
+ snapshot(): readonly TerminalSubagentActivity[];
8
+ changed(): void;
9
+ }
10
+ export declare class SubagentsPage implements TerminalPage {
11
+ private readonly options;
12
+ presentation: "fullscreen";
13
+ private readonly list;
14
+ constructor(options: SubagentsPageOptions);
15
+ get focused(): boolean;
16
+ set focused(value: boolean);
17
+ tabs(): readonly TabInfo[];
18
+ activeTab(): string;
19
+ hint(): string;
20
+ private rows;
21
+ render(width: number, rows: number): string[];
22
+ handleInput(data: string): boolean;
23
+ }
@@ -0,0 +1,70 @@
1
+ import { matchesKey, stripTerminalSequences, truncateToWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
2
+ import { headerRow, neutralRule, padded } from "../chrome.js";
3
+ import { FilterList } from "../list.js";
4
+ import { ACTIVE_SUBAGENT_STATUSES, terminalSubagentDuration, terminalSubagentTone } from "../../subagentMonitor.js";
5
+ import { subagentTasks } from "../../opentui/subagentsWidget.js";
6
+ const TABS = [{ id: 'subagents', title: 'Subagents' }];
7
+ function clean(value) {
8
+ return stripTerminalSequences(value).replace(/[\x00-\x08\x0b-\x1f\x7f]/g, ' ').replace(/\s+/g, ' ').trim();
9
+ }
10
+ export class SubagentsPage {
11
+ options;
12
+ presentation = 'fullscreen';
13
+ list;
14
+ constructor(options) {
15
+ this.options = options;
16
+ this.list = new FilterList({
17
+ theme: options.theme, search: 'always', noun: ['subagent', 'subagents'], empty: 'No subagents have started in this turn'
18
+ });
19
+ }
20
+ get focused() { return this.list.focused; }
21
+ set focused(value) { this.list.focused = value; }
22
+ tabs() { return TABS; }
23
+ activeTab() { return 'subagents'; }
24
+ hint() { return '↑↓ select · type to search'; }
25
+ rows() {
26
+ return this.options.snapshot().map(item => ({
27
+ id: item.id,
28
+ label: `${clean(item.profile?.name ?? '') || 'subagent'} · ${clean(item.title) || 'Subagent'}`,
29
+ detail: [item.status.replaceAll('_', ' '), subagentTasks(item.checklist), terminalSubagentDuration(item)].filter(Boolean).join(' · '),
30
+ tone: terminalSubagentTone(item.status),
31
+ searchText: `${item.profile?.name ?? ''} ${item.title} ${item.goal ?? ''} ${item.status} ${item.activity}`,
32
+ data: item
33
+ }));
34
+ }
35
+ render(width, rows) {
36
+ const items = this.rows();
37
+ this.list.setRows(items);
38
+ const active = items.filter(item => item.data && ACTIVE_SUBAGENT_STATUSES.has(item.data.status)).length;
39
+ const selected = this.list.selected?.data;
40
+ const header = headerRow('Subagents', `${active} active · ${items.length} total`, width, this.options.theme());
41
+ const bodyRows = Math.max(0, rows - 7);
42
+ const listRows = Math.max(1, Math.min(this.list.height, Math.floor(bodyRows * 0.6)));
43
+ const output = [header, '', ...this.list.render(width, listRows), ''];
44
+ if (selected) {
45
+ const theme = this.options.theme();
46
+ const textWidth = Math.max(1, width - 4);
47
+ output.push(neutralRule(width, theme));
48
+ output.push(padded(theme.fg('muted', `Profile: ${clean(selected.profile?.name ?? '') || 'copy of the parent'}`), width));
49
+ output.push(padded(theme.fg('accent', 'Task'), width));
50
+ output.push(...wrapTextWithAnsi(theme.fg('text', clean(selected.goal || 'Subagent')), textWidth));
51
+ output.push(padded(theme.fg('muted', `Status: ${[selected.status.replaceAll('_', ' '), subagentTasks(selected.checklist), terminalSubagentDuration(selected)].filter(Boolean).join(' · ')}`), width));
52
+ output.push(padded(theme.fg('accent', 'Current activity'), width));
53
+ output.push(...wrapTextWithAnsi(theme.fg('text', clean(selected.activity)), textWidth));
54
+ }
55
+ else {
56
+ output.push(padded(this.options.theme().fg('muted', 'The selected subagent’s task and current activity appear here.'), width));
57
+ }
58
+ return output.slice(0, Math.max(0, rows)).map(line => truncateToWidth(line, width, ''));
59
+ }
60
+ handleInput(data) {
61
+ this.list.setRows(this.rows());
62
+ if (matchesKey(data, 'enter'))
63
+ return this.list.handleInput(data);
64
+ if (this.list.handleInput(data)) {
65
+ this.options.changed();
66
+ return true;
67
+ }
68
+ return false;
69
+ }
70
+ }
@@ -5,7 +5,7 @@ import type { RunEvent } from "../../../sessions/contracts.js";
5
5
  import type { TerminalTrajectoryRun, TerminalTrajectorySnapshot } from "../../trajectory.js";
6
6
  import { type TabInfo } from "../chrome.js";
7
7
  import type { TerminalPage } from "../page.js";
8
- import { type TerminalMouseInput } from "../mouse.js";
8
+ import type { TerminalMouseInput } from "../mouse.js";
9
9
  export interface TrajectoryPageOptions {
10
10
  theme: () => Theme;
11
11
  runs(): Promise<TerminalTrajectoryRun[]>;
@@ -18,17 +18,20 @@ export interface TrajectoryPageOptions {
18
18
  }
19
19
  export declare class TrajectoryPage implements TerminalPage {
20
20
  private readonly options;
21
+ readonly presentation: "fullscreen";
21
22
  private runs;
22
23
  private section;
23
24
  private readonly runList;
25
+ private readonly eventList;
24
26
  private runIndex;
25
27
  private run;
26
28
  private events;
27
- private selected;
28
29
  private detailOffset;
29
- private eventRowsStart;
30
- private eventRowsFirst;
31
- private eventRowsCount;
30
+ private listStart;
31
+ private listEnd;
32
+ private listWidth;
33
+ private detailsStart;
34
+ private wideLayout;
32
35
  private loading;
33
36
  private loaded;
34
37
  private disposed;
@@ -49,10 +52,14 @@ export declare class TrajectoryPage implements TerminalPage {
49
52
  private refresh;
50
53
  /** `[` is the older run, `]` the newer one. */
51
54
  private runRows;
55
+ private eventRows;
56
+ private eventJson;
52
57
  private selectRun;
53
58
  private changeRun;
54
59
  hint(): string;
55
60
  handleMouse(event: TerminalMouseInput): TuiMouseEventResult | undefined;
56
61
  handleInput(data: string): boolean;
62
+ private runSummary;
63
+ private detailLines;
57
64
  render(width: number, rows: number): string[];
58
65
  }