@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.
Files changed (264) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +36 -28
  3. package/dist/cli.js +10837 -6159
  4. package/dist/sdk/accounts/accounts.d.ts +27 -3
  5. package/dist/sdk/accounts/accounts.js +78 -24
  6. package/dist/sdk/accounts/secrets.d.ts +12 -0
  7. package/dist/sdk/accounts/secrets.js +114 -7
  8. package/dist/sdk/accounts/store.d.ts +26 -1
  9. package/dist/sdk/accounts/store.js +1 -1
  10. package/dist/sdk/agents/builtin.js +18 -4
  11. package/dist/sdk/agents/planResearch.d.ts +27 -0
  12. package/dist/sdk/agents/planResearch.js +82 -0
  13. package/dist/sdk/agents/profiles.d.ts +5 -1
  14. package/dist/sdk/agents/profiles.js +21 -5
  15. package/dist/sdk/annotations/pageScript.js +349 -48
  16. package/dist/sdk/annotations/part.js +34 -23
  17. package/dist/sdk/annotations/store.d.ts +7 -0
  18. package/dist/sdk/annotations/store.js +13 -5
  19. package/dist/sdk/browser/backend.d.ts +16 -0
  20. package/dist/sdk/browser/backend.js +17 -0
  21. package/dist/sdk/browser/contracts.d.ts +19 -0
  22. package/dist/sdk/browser/contracts.js +116 -0
  23. package/dist/sdk/browser/tools.d.ts +1 -4
  24. package/dist/sdk/browser/tools.js +14 -91
  25. package/dist/sdk/capabilities/runSnapshot.js +10 -4
  26. package/dist/sdk/cli.js +5 -4
  27. package/dist/sdk/commands/list.d.ts +3 -0
  28. package/dist/sdk/commands/list.js +75 -0
  29. package/dist/sdk/commands/registry.d.ts +258 -0
  30. package/dist/sdk/commands/registry.js +48 -0
  31. package/dist/sdk/commands/routes.d.ts +16 -0
  32. package/dist/sdk/commands/routes.js +33 -0
  33. package/dist/sdk/core/commandBudget.d.ts +23 -0
  34. package/dist/sdk/core/commandBudget.js +57 -0
  35. package/dist/sdk/core/index.js +17 -1
  36. package/dist/sdk/core/interactiveProfile.d.ts +46 -0
  37. package/dist/sdk/core/interactiveProfile.js +143 -0
  38. package/dist/sdk/core/shell.d.ts +20 -0
  39. package/dist/sdk/core/shell.js +73 -0
  40. package/dist/sdk/core/workspace.d.ts +13 -0
  41. package/dist/sdk/core/workspace.js +88 -0
  42. package/dist/sdk/core/workspaceBashTool.d.ts +20 -0
  43. package/dist/sdk/core/workspaceBashTool.js +50 -0
  44. package/dist/sdk/core/workspaceSandbox.d.ts +41 -0
  45. package/dist/sdk/core/workspaceSandbox.js +360 -0
  46. package/dist/sdk/core/workspaceWriteOperations.d.ts +33 -0
  47. package/dist/sdk/core/workspaceWriteOperations.js +224 -0
  48. package/dist/sdk/doctor/checks.js +71 -12
  49. package/dist/sdk/doctor/types.d.ts +2 -2
  50. package/dist/sdk/harness.d.ts +7 -0
  51. package/dist/sdk/harness.js +42 -2
  52. package/dist/sdk/hub/hub.js +5 -3
  53. package/dist/sdk/index.d.ts +8 -3
  54. package/dist/sdk/index.js +4 -1
  55. package/dist/sdk/pi/runtime.d.ts +2 -1
  56. package/dist/sdk/pi/runtime.js +2 -2
  57. package/dist/sdk/plugins/builtin.js +32 -15
  58. package/dist/sdk/plugins/builtinRegistry.d.ts +10 -3
  59. package/dist/sdk/plugins/builtinRegistry.js +30 -8
  60. package/dist/sdk/plugins/catalogue.js +2 -1
  61. package/dist/sdk/plugins/commandPrefix.d.ts +8 -0
  62. package/dist/sdk/plugins/commandPrefix.js +21 -0
  63. package/dist/sdk/plugins/firstParty.d.ts +1 -1
  64. package/dist/sdk/plugins/firstParty.js +8 -2
  65. package/dist/sdk/plugins/install.d.ts +6 -2
  66. package/dist/sdk/plugins/install.js +29 -5
  67. package/dist/sdk/plugins/inventory.js +6 -5
  68. package/dist/sdk/plugins/manifest.d.ts +2 -0
  69. package/dist/sdk/plugins/manifest.js +2 -1
  70. package/dist/sdk/plugins/surfaces.d.ts +5 -0
  71. package/dist/sdk/plugins/surfaces.js +11 -0
  72. package/dist/sdk/plugins/types.d.ts +15 -0
  73. package/dist/sdk/prompt/compile.d.ts +14 -0
  74. package/dist/sdk/prompt/compile.js +65 -31
  75. package/dist/sdk/prompt/standard.d.ts +17 -0
  76. package/dist/sdk/prompt/standard.js +21 -0
  77. package/dist/sdk/server/methods.js +22 -2
  78. package/dist/sdk/server/remote.js +36 -11
  79. package/dist/sdk/sessions/compactionCheckpoint.d.ts +11 -0
  80. package/dist/sdk/sessions/compactionCheckpoint.js +79 -0
  81. package/dist/sdk/sessions/continuation.d.ts +7 -2
  82. package/dist/sdk/sessions/continuation.js +11 -6
  83. package/dist/sdk/sessions/contracts.d.ts +190 -4
  84. package/dist/sdk/sessions/contracts.js +71 -8
  85. package/dist/sdk/sessions/control.d.ts +72 -0
  86. package/dist/sdk/sessions/control.js +158 -0
  87. package/dist/sdk/sessions/initialBash.d.ts +7 -0
  88. package/dist/sdk/sessions/initialBash.js +7 -0
  89. package/dist/sdk/sessions/planMode.d.ts +14 -3
  90. package/dist/sdk/sessions/planMode.js +74 -17
  91. package/dist/sdk/sessions/registry.d.ts +1 -1
  92. package/dist/sdk/sessions/registry.js +2 -2
  93. package/dist/sdk/sessions/runStore.d.ts +14 -0
  94. package/dist/sdk/sessions/runStore.js +38 -1
  95. package/dist/sdk/sessions/runner.d.ts +40 -2
  96. package/dist/sdk/sessions/runner.js +161 -13
  97. package/dist/sdk/sessions/sessions.d.ts +23 -3
  98. package/dist/sdk/sessions/sessions.js +225 -22
  99. package/dist/sdk/sessions/subagentService.d.ts +24 -0
  100. package/dist/sdk/sessions/subagentService.js +225 -26
  101. package/dist/sdk/sessions/subagents.d.ts +10 -1
  102. package/dist/sdk/sessions/subagents.js +4 -0
  103. package/dist/sdk/sessions/workSlice.d.ts +106 -0
  104. package/dist/sdk/sessions/workSlice.js +329 -0
  105. package/dist/sdk/sessions/workspaceBoundary.d.ts +15 -0
  106. package/dist/sdk/sessions/workspaceBoundary.js +46 -0
  107. package/dist/sdk/terminal/browserControl.js +2 -1
  108. package/dist/sdk/terminal/controller.d.ts +31 -3
  109. package/dist/sdk/terminal/controller.js +81 -29
  110. package/dist/sdk/terminal/opentui/ansi.d.ts +25 -0
  111. package/dist/sdk/terminal/opentui/ansi.js +192 -0
  112. package/dist/sdk/terminal/opentui/model.d.ts +163 -0
  113. package/dist/sdk/terminal/opentui/model.js +482 -0
  114. package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +3 -3
  115. package/dist/sdk/terminal/opentui/subagentsWidget.js +7 -5
  116. package/dist/sdk/terminal/opentui/theme.d.ts +43 -0
  117. package/dist/sdk/terminal/opentui/theme.js +88 -0
  118. package/dist/sdk/terminal/piExtension.d.ts +6 -1
  119. package/dist/sdk/terminal/piExtension.js +177 -63
  120. package/dist/sdk/terminal/standalone.d.ts +7 -0
  121. package/dist/sdk/terminal/standalone.js +72 -13
  122. package/dist/sdk/terminal/subagentMonitor.d.ts +28 -6
  123. package/dist/sdk/terminal/subagentMonitor.js +61 -9
  124. package/dist/sdk/terminal/terminals.d.ts +26 -2
  125. package/dist/sdk/terminal/terminals.js +325 -43
  126. package/dist/sdk/terminal/trajectory.d.ts +6 -1
  127. package/dist/sdk/terminal/trajectory.js +8 -0
  128. package/dist/sdk/terminal/tui/annotateCommand.d.ts +1 -1
  129. package/dist/sdk/terminal/tui/annotateCommand.js +3 -3
  130. package/dist/sdk/terminal/tui/chrome.d.ts +4 -0
  131. package/dist/sdk/terminal/tui/chrome.js +13 -0
  132. package/dist/sdk/terminal/tui/commandPages.d.ts +3 -1
  133. package/dist/sdk/terminal/tui/commandPages.js +17 -4
  134. package/dist/sdk/terminal/tui/commands.d.ts +6 -0
  135. package/dist/sdk/terminal/tui/commands.js +213 -124
  136. package/dist/sdk/terminal/tui/list.d.ts +14 -1
  137. package/dist/sdk/terminal/tui/list.js +96 -14
  138. package/dist/sdk/terminal/tui/pageOverlay.js +21 -5
  139. package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +1 -1
  140. package/dist/sdk/terminal/tui/pages/browserPage.js +5 -4
  141. package/dist/sdk/terminal/tui/pages/hubPage.d.ts +14 -1
  142. package/dist/sdk/terminal/tui/pages/hubPage.js +192 -28
  143. package/dist/sdk/terminal/tui/pages/notesPage.d.ts +5 -5
  144. package/dist/sdk/terminal/tui/pages/notesPage.js +18 -18
  145. package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +8 -6
  146. package/dist/sdk/terminal/tui/pages/pluginsPage.js +206 -28
  147. package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +1 -0
  148. package/dist/sdk/terminal/tui/pages/subagentsPage.js +104 -19
  149. package/dist/sdk/terminal/tui/pages/usagePage.d.ts +43 -0
  150. package/dist/sdk/terminal/tui/pages/usagePage.js +370 -0
  151. package/dist/sdk/terminal/tui/piAdapter.d.ts +1 -2
  152. package/dist/sdk/terminal/tui/piAdapter.js +3 -5
  153. package/dist/sdk/terminal/tui/pluginCommands.d.ts +2 -0
  154. package/dist/sdk/terminal/tui/pluginCommands.js +13 -9
  155. package/dist/sdk/terminal/tui/runtime.js +23 -3
  156. package/dist/sdk/terminal/tui/sessionPicker.d.ts +40 -0
  157. package/dist/sdk/terminal/tui/sessionPicker.js +141 -0
  158. package/dist/sdk/tools/checkpoint.d.ts +27 -0
  159. package/dist/sdk/tools/checkpoint.js +154 -0
  160. package/dist/sdk/tools/getCapabilities.d.ts +31 -0
  161. package/dist/sdk/tools/getCapabilities.js +34 -0
  162. package/dist/sdk/tools/sessionName.d.ts +2 -0
  163. package/dist/sdk/tools/sessionName.js +5 -3
  164. package/dist/sdk/tools/spawnSubagent.js +11 -8
  165. package/dist/sdk/tools/todo.d.ts +15 -0
  166. package/dist/sdk/tools/todo.js +64 -0
  167. package/dist/sdk/usage/analytics.d.ts +20 -0
  168. package/dist/sdk/usage/analytics.js +151 -0
  169. package/dist/sdk/usage/ledger.d.ts +38 -0
  170. package/dist/sdk/usage/ledger.js +142 -0
  171. package/dist/sdk/usage/limits/adapters.d.ts +11 -0
  172. package/dist/sdk/usage/limits/adapters.js +349 -0
  173. package/dist/sdk/usage/limits/capture.d.ts +8 -0
  174. package/dist/sdk/usage/limits/capture.js +47 -0
  175. package/dist/sdk/usage/limits/http.d.ts +12 -0
  176. package/dist/sdk/usage/limits/http.js +51 -0
  177. package/dist/sdk/usage/limits/normalize.d.ts +15 -0
  178. package/dist/sdk/usage/limits/normalize.js +211 -0
  179. package/dist/sdk/usage/limits/service.d.ts +30 -0
  180. package/dist/sdk/usage/limits/service.js +176 -0
  181. package/dist/sdk/usage/limits/types.d.ts +65 -0
  182. package/dist/sdk/usage/limits/types.js +1 -0
  183. package/dist/sdk/usage/serverMethods.d.ts +3 -0
  184. package/dist/sdk/usage/serverMethods.js +22 -0
  185. package/dist/sdk/usage/service.d.ts +32 -0
  186. package/dist/sdk/usage/service.js +66 -0
  187. package/dist/sdk/usage/settings.d.ts +7 -0
  188. package/dist/sdk/usage/settings.js +28 -0
  189. package/dist/sdk/usage/types.d.ts +60 -0
  190. package/dist/sdk/usage/types.js +4 -0
  191. package/dist/terminal-opentui.js +9204 -4812
  192. package/dist/terminal-pi.js +1848 -272
  193. package/docs/architecture/00-overview.md +1 -1
  194. package/docs/architecture/01-extension-points.md +1 -1
  195. package/docs/architecture/02-sessions-and-runs.md +169 -36
  196. package/docs/architecture/04-prompt.md +55 -15
  197. package/docs/architecture/06-plugins-and-capabilities.md +36 -15
  198. package/docs/architecture/07-tools-and-gate.md +131 -25
  199. package/docs/architecture/08-memory-and-history.md +4 -2
  200. package/docs/architecture/09-accounts-and-connectors.md +39 -3
  201. package/docs/architecture/10-server-protocol.md +71 -9
  202. package/docs/architecture/11-terminal.md +148 -60
  203. package/docs/architecture/12-storage.md +30 -2
  204. package/docs/architecture/13-world-tools.md +22 -1
  205. package/docs/architecture/14-surfaces-and-parity.md +58 -8
  206. package/docs/architecture/README.md +20 -17
  207. package/docs/architecture/proposals/PROP-0006-run-checklist.md +2 -0
  208. package/docs/architecture/proposals/PROP-0017-page-annotations.md +6 -2
  209. package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +1 -1
  210. package/docs/architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md +54 -0
  211. package/docs/architecture/proposals/PROP-0032-planning-quality.md +196 -0
  212. package/docs/architecture/proposals/PROP-0033-compaction-plan-checkpoints.md +75 -0
  213. package/docs/architecture/proposals/PROP-0034-conversation-fast-mode.md +120 -0
  214. package/docs/architecture/proposals/PROP-0035-retain-subagent-status.md +47 -0
  215. package/docs/architecture/proposals/PROP-0036-first-class-harness-commands.md +59 -0
  216. package/docs/architecture/proposals/PROP-0037-workspace-write-boundary.md +63 -0
  217. package/docs/architecture/proposals/PROP-0038-annotate-open-shadow-roots.md +73 -0
  218. package/docs/architecture/proposals/PROP-0039-workspace-boundary-hardening.md +139 -0
  219. package/docs/architecture/proposals/PROP-0040-one-launch-path.md +87 -0
  220. package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +258 -0
  221. package/docs/architecture/proposals/PROP-0042-usage-and-provider-limits.md +127 -0
  222. package/docs/architecture/proposals/PROP-0043-read-only-plan-bash.md +74 -0
  223. package/docs/architecture/proposals/PROP-0044-work-slice-defaults.md +93 -0
  224. package/docs/architecture/proposals/PROP-0045-rename-checklist-to-todo.md +132 -0
  225. package/docs/architecture/proposals/PROP-0046-progress-aware-stall-guards.md +82 -0
  226. package/docs/architecture/proposals/PROP-0047-plugins-hub-separation.md +122 -0
  227. package/docs/architecture/proposals/README.md +24 -6
  228. package/docs/decisions/ADR-0001-owned-terminal.md +1 -1
  229. package/docs/decisions/ADR-0021-required-command-namespaces.md +1 -1
  230. package/docs/decisions/ADR-0031-plugin-command-owner-namespaces.md +37 -0
  231. package/docs/decisions/ADR-0032-compaction-run-checkpoints.md +33 -0
  232. package/docs/decisions/ADR-0033-plan-mode-research-delegation.md +47 -0
  233. package/docs/decisions/ADR-0034-first-class-harness-terminal-commands.md +25 -0
  234. package/docs/decisions/ADR-0035-one-launch-path.md +40 -0
  235. package/docs/decisions/ADR-0036-workspace-write-operation-boundary.md +54 -0
  236. package/docs/decisions/ADR-0037-usage-ledger-and-provider-limits.md +44 -0
  237. package/docs/decisions/ADR-0038-plan-bash-os-read-only.md +29 -0
  238. package/docs/decisions/ADR-0039-progress-aware-stall-guards.md +39 -0
  239. package/docs/decisions/ADR-0040-architecture-change-workflow.md +31 -0
  240. package/docs/decisions/ADR-0041-plugins-hub-separation.md +41 -0
  241. package/docs/decisions/ADR-0042-windows-shell-without-sandbox.md +40 -0
  242. package/docs/decisions/ADR-0043-parents-continue-stopped-subagents.md +49 -0
  243. package/docs/doctor.md +14 -1
  244. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime-replication.json +420 -0
  245. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime.json +420 -0
  246. package/docs/experiments/EXP-0003-performance-audit/data/terminal-compact.json +376 -0
  247. package/docs/experiments/EXP-0003-performance-audit/data/terminal-history.json +422 -0
  248. package/docs/experiments/EXP-0003-performance-audit/data/terminal-question.json +95 -0
  249. package/docs/experiments/EXP-0003-performance-audit/data/terminal-wide.json +1036 -0
  250. package/docs/experiments/EXP-0003-performance-audit/plan.md +109 -0
  251. package/docs/experiments/EXP-0003-performance-audit/results.md +151 -0
  252. package/docs/handoff-2026-10-25-app-parity-and-solo-tabs.md +202 -0
  253. package/docs/parity.md +83 -27
  254. package/docs/releases/1.1.1.md +28 -0
  255. package/docs/releases/1.2.0.md +49 -0
  256. package/docs/releases/1.3.0.md +21 -0
  257. package/docs/releases/4.0.0.md +10 -9
  258. package/docs/releases/README.md +35 -30
  259. package/docs/study/00-index.md +3 -0
  260. package/docs/study/STUDY-0003-long-horizon-runs.md +210 -0
  261. package/docs/terminal-tui.md +162 -82
  262. package/package.json +4 -3
  263. package/dist/sdk/tools/checklist.d.ts +0 -9
  264. package/dist/sdk/tools/checklist.js +0 -54
@@ -6,7 +6,21 @@ import { createHash, randomUUID } from 'node:crypto';
6
6
  import { HarnessError } from "../core/errors.js";
7
7
  import { runStatusSchema } from "../sessions/contracts.js";
8
8
  import { briefTitle } from "../sessions/subagents.js";
9
- import { SubagentMonitor } from "./subagentMonitor.js";
9
+ import { MAIN_PROFILE, SubagentMonitor } from "./subagentMonitor.js";
10
+ function monitorRun(record, relation, profile, runtime) {
11
+ return {
12
+ id: record.id,
13
+ parentRunId: record.parentRunId,
14
+ relation,
15
+ title: record.title ?? (briefTitle(record.goal) || (relation === 'main' ? 'Main agent' : '')),
16
+ goal: record.goal,
17
+ status: runStatusSchema.parse(record.status),
18
+ startedAt: record.startedAt,
19
+ endedAt: record.endedAt,
20
+ profile: relation === 'main' ? MAIN_PROFILE : profile,
21
+ ...runtime
22
+ };
23
+ }
10
24
  const READY_TIMEOUT_MS = 90_000;
11
25
  const ACK_TIMEOUT_MS = 25_000;
12
26
  export class TerminalController {
@@ -14,6 +28,7 @@ export class TerminalController {
14
28
  run = null;
15
29
  pending = new Map();
16
30
  subagents = new SubagentMonitor();
31
+ subagentParentRunId = null;
17
32
  ready = false;
18
33
  disposed = false;
19
34
  dirty = false;
@@ -40,7 +55,7 @@ export class TerminalController {
40
55
  requestId(nativeId) {
41
56
  return createHash('sha256').update(`${this.hooks.sessionId}:${this.run?.runId}:${nativeId}`).digest('hex');
42
57
  }
43
- begin(nativeTurnId, text) {
58
+ begin(nativeTurnId, text, launch = {}) {
44
59
  if (this.startupError)
45
60
  throw new Error(this.startupError);
46
61
  this.dirty = false;
@@ -50,8 +65,9 @@ export class TerminalController {
50
65
  const delivery = this.delivery;
51
66
  if (delivery && text && delivery.text !== text)
52
67
  throw new Error('The native prompt did not match the reserved delivery. Inspect the terminal before continuing.');
53
- const run = this.hooks.begin({ reservedRunId: delivery?.runId ?? null, nativeTurnId, text });
68
+ const run = this.hooks.begin({ reservedRunId: delivery?.runId ?? null, nativeTurnId, text, ...launch });
54
69
  this.run = run;
70
+ this.subagentParentRunId = run.runId;
55
71
  this.subagents.begin(run.runId);
56
72
  run.onInterrupt(() => void this.commands.push({ type: 'interrupt' }));
57
73
  if (text)
@@ -60,10 +76,8 @@ export class TerminalController {
60
76
  this.delivery = null;
61
77
  return run;
62
78
  }
63
- finish(outcome, message) {
64
- const runId = this.run?.runId;
65
- this.run?.finish(outcome, message ? { code: 'terminal_attention', message } : undefined);
66
- this.subagents.clear(runId);
79
+ finish(outcome, message, reason) {
80
+ this.run?.finish(outcome, message ? { code: 'terminal_attention', message } : undefined, reason);
67
81
  this.run = null;
68
82
  this.seen.clear();
69
83
  this.pending.clear();
@@ -133,10 +147,11 @@ export class TerminalController {
133
147
  const payload = event.payload;
134
148
  this.pending.set(String(payload.questionId), { ...payload, type: 'question', requestId: payload.questionId, runId: event.runId });
135
149
  }
136
- else if (descendant && event.type === 'approval.requested') {
150
+ else if (event.type === 'approval.requested') {
137
151
  const payload = event.payload;
138
- if (!payload.automatic)
152
+ if (!payload.automatic && (descendant || payload.target === 'workspace-write')) {
139
153
  this.pending.set(String(payload.approvalId), { ...payload, type: 'approval', requestId: payload.approvalId, runId: event.runId });
154
+ }
140
155
  }
141
156
  else if (event.type === 'approval.resolved') {
142
157
  this.pending.delete(String(event.payload.approvalId));
@@ -146,25 +161,30 @@ export class TerminalController {
146
161
  }
147
162
  }
148
163
  /** A child event that belongs to the active terminal parent run. */
149
- observeSubagent(parentRunId, run, event, profile = null) {
164
+ observeSubagent(parentRunId, run, event, profile = null, runtime = {}) {
150
165
  if (this.disposed || this.activeRun?.runId !== parentRunId || run.relation !== 'subagent')
151
166
  return;
152
- this.subagents.observe(parentRunId, {
153
- id: run.id,
154
- parentRunId: run.parentRunId,
155
- relation: 'subagent',
156
- title: run.title ?? briefTitle(run.goal),
157
- goal: run.goal,
158
- status: runStatusSchema.parse(run.status),
159
- startedAt: run.startedAt,
160
- endedAt: run.endedAt,
161
- profile
162
- }, event);
167
+ this.subagents.observe(parentRunId, monitorRun(run, 'subagent', profile, runtime), event);
168
+ }
169
+ /** An event of the tracked parent run itself, shown as the `main` row; it outlives settlement. */
170
+ observeMain(run, event, runtime = {}) {
171
+ if (this.disposed || this.subagentParentRunId !== run.id || run.relation)
172
+ return;
173
+ this.subagents.observe(run.id, monitorRun(run, 'main', null, runtime), event);
163
174
  }
164
- /** Read-only child activity for the active run. */
175
+ /** Shows a saved parent run and its descendants, as they ended, until another run begins. */
176
+ restoreMonitor(parent, children) {
177
+ if (this.disposed || this.activeRun || this.delivery)
178
+ return;
179
+ this.subagentParentRunId = parent.record.id;
180
+ this.subagents.restore(parent.record.id, [
181
+ { run: monitorRun(parent.record, 'main', null, parent.runtime ?? {}), events: parent.events },
182
+ ...children.map((child) => ({ run: monitorRun(child.record, 'subagent', child.profile ?? null, child.runtime ?? {}), events: child.events }))
183
+ ]);
184
+ }
185
+ /** Read-only child activity for the latest parent run, retained until another run begins. */
165
186
  subagentSnapshot() {
166
- const run = this.activeRun;
167
- return run ? this.subagents.snapshot(run.runId) : [];
187
+ return this.subagentParentRunId ? this.subagents.snapshot(this.subagentParentRunId) : [];
168
188
  }
169
189
  /** The socket grants this interface only to the process that owns terminal input. */
170
190
  async control(call, data) {
@@ -185,6 +205,20 @@ export class TerminalController {
185
205
  throw new HarnessError('invalid_request', 'An active run and tool name are required.');
186
206
  return this.hooks.policy(run, data.tool, data.args ?? {});
187
207
  }
208
+ if (call === 'terminal.workspaceBoundaryBypass') {
209
+ const run = this.activeRun;
210
+ if (!run || data.tool !== 'bash')
211
+ throw new HarnessError('invalid_request', 'An active run and Bash command are required.');
212
+ const args = data.args && typeof data.args === 'object' && !Array.isArray(data.args) ? data.args : {};
213
+ return { bypass: await this.hooks.workspaceBoundaryBypass(run, 'bash', args) };
214
+ }
215
+ if (call === 'terminal.workspaceEscape') {
216
+ const run = this.activeRun;
217
+ if (!run || data.tool !== 'bash' || typeof data.reason !== 'string' || !data.reason)
218
+ throw new HarnessError('invalid_request', 'An active run, Bash command, and outside-scope request are required.');
219
+ const args = data.args && typeof data.args === 'object' && !Array.isArray(data.args) ? data.args : {};
220
+ return { approved: await this.hooks.workspaceEscape(run, 'bash', args, data.reason) };
221
+ }
188
222
  if (call === 'terminal.approval')
189
223
  return this.requestNativeApproval(data);
190
224
  if (call === 'terminal.answers' && this.hooks.answerQuestions) {
@@ -294,14 +328,24 @@ export class TerminalController {
294
328
  }
295
329
  this.ready = true;
296
330
  if (typeof data.nativeSessionId === 'string' && data.nativeSessionId) {
331
+ if (this.nativeId !== data.nativeSessionId) {
332
+ this.subagents.clear();
333
+ this.subagentParentRunId = null;
334
+ }
297
335
  this.nativeId = data.nativeSessionId;
298
336
  this.hooks.native(data.nativeSessionId);
299
337
  }
338
+ const owners = Array.isArray(data.owners) ? data.owners.filter((id) => typeof id === 'string') : [];
339
+ if (!this.activeRun && !this.delivery && this.hooks.hydrate)
340
+ this.hooks.hydrate(owners);
300
341
  return {};
301
342
  }
302
343
  if (event === 'begin') {
303
- const run = this.begin(String(data.turnId ?? randomUUID()), String(data.text ?? ''));
304
- return { runId: run.runId };
344
+ const run = this.begin(String(data.turnId ?? randomUUID()), String(data.text ?? ''), {
345
+ model: typeof data.model === 'string' && data.model ? data.model : null,
346
+ effort: typeof data.effort === 'string' && data.effort ? data.effort : null
347
+ });
348
+ return { runId: run.runId, workspaceRoot: this.hooks.workspaceRoot(run) };
305
349
  }
306
350
  if (event === 'shutdown') {
307
351
  this.ready = false;
@@ -309,7 +353,11 @@ export class TerminalController {
309
353
  return {};
310
354
  }
311
355
  if (event === 'settled') {
312
- this.finish(data.aborted ? 'interrupted' : data.error ? 'failed' : 'completed', data.error ? String(data.error) : undefined);
356
+ // A reply that stopped at its work-slice boundary is interrupted, not completed: the record
357
+ // says `slice_complete` (or `stalled`) so a host can continue it (PROP-0041).
358
+ const reason = typeof data.reason === 'string' ? data.reason : undefined;
359
+ const outcome = data.error ? 'failed' : data.aborted || reason ? 'interrupted' : 'completed';
360
+ this.finish(outcome, data.error ? String(data.error) : undefined, reason);
313
361
  return {};
314
362
  }
315
363
  const run = this.run;
@@ -354,8 +402,10 @@ export class TerminalController {
354
402
  }
355
403
  if (event === 'usage')
356
404
  run.emit('usage.updated', data);
357
- else if (event === 'checklist')
358
- run.emit('checklist.updated', { items: data.items });
405
+ else if (event === 'todo' || event === 'checklist')
406
+ run.emit('todo.updated', { items: data.items });
407
+ else if (event === 'checkpoint')
408
+ run.emit('checkpoint', data);
359
409
  else if (event === 'file')
360
410
  run.emit('file.changed', data);
361
411
  else if (event === 'message')
@@ -379,5 +429,7 @@ export class TerminalController {
379
429
  this.delivery?.reject(new Error('The terminal stopped before prompt acknowledgement.'));
380
430
  this.delivery = null;
381
431
  this.finish('interrupted');
432
+ this.subagents.clear();
433
+ this.subagentParentRunId = null;
382
434
  }
383
435
  }
@@ -0,0 +1,25 @@
1
+ export interface AnsiStyle {
2
+ fg?: string;
3
+ bg?: string;
4
+ bold?: boolean;
5
+ dim?: boolean;
6
+ italic?: boolean;
7
+ underline?: boolean;
8
+ inverse?: boolean;
9
+ }
10
+ export interface AnsiSpan {
11
+ text: string;
12
+ style: AnsiStyle;
13
+ }
14
+ export interface AnsiLine {
15
+ spans: AnsiSpan[];
16
+ }
17
+ export declare function ansi256Color(index: number): string;
18
+ /**
19
+ * Parses SGR styled lines. Non-style escape sequences, including Pi's cursor markers, are removed;
20
+ * an OSC 8 hyperlink keeps its visible label.
21
+ */
22
+ export declare function parseAnsi(text: string): AnsiLine[];
23
+ export declare function ansiPlain(text: string): string;
24
+ /** A short single-line form for labels and tool summaries. */
25
+ export declare function ansiLabel(text: string, limit?: number): string;
@@ -0,0 +1,192 @@
1
+ // Pi components and trusted extensions render ANSI text, not OpenTUI nodes. This module turns
2
+ // that text into the small style model OpenTUI can draw, and never executes anything in it.
3
+ const BASE_COLORS = [
4
+ '#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5',
5
+ '#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'
6
+ ];
7
+ const CUBE = [0, 95, 135, 175, 215, 255];
8
+ export function ansi256Color(index) {
9
+ if (!Number.isFinite(index))
10
+ return BASE_COLORS[7];
11
+ const value = Math.max(0, Math.min(255, Math.trunc(index)));
12
+ if (value < 16)
13
+ return BASE_COLORS[value];
14
+ if (value < 232) {
15
+ const n = value - 16;
16
+ const r = CUBE[Math.floor(n / 36) % 6];
17
+ const g = CUBE[Math.floor(n / 6) % 6];
18
+ const b = CUBE[n % 6];
19
+ return hex(r, g, b);
20
+ }
21
+ const grey = 8 + (value - 232) * 10;
22
+ return hex(grey, grey, grey);
23
+ }
24
+ function hex(r, g, b) {
25
+ const part = (value) => Math.max(0, Math.min(255, Math.trunc(value))).toString(16).padStart(2, '0');
26
+ return `#${part(r)}${part(g)}${part(b)}`;
27
+ }
28
+ function defaultStyle() {
29
+ return {};
30
+ }
31
+ function applySgr(style, params) {
32
+ const next = { ...style };
33
+ const parts = params === '' ? ['0'] : params.split(';');
34
+ for (let index = 0; index < parts.length; index += 1) {
35
+ const raw = parts[index];
36
+ const value = raw === '' ? 0 : Number.parseInt(raw, 10);
37
+ if (!Number.isFinite(value))
38
+ continue;
39
+ if (value === 0) {
40
+ for (const key of Object.keys(next))
41
+ delete next[key];
42
+ }
43
+ else if (value === 1)
44
+ next.bold = true;
45
+ else if (value === 2)
46
+ next.dim = true;
47
+ else if (value === 3)
48
+ next.italic = true;
49
+ else if (value === 4)
50
+ next.underline = true;
51
+ else if (value === 7)
52
+ next.inverse = true;
53
+ else if (value === 22) {
54
+ next.bold = false;
55
+ next.dim = false;
56
+ }
57
+ else if (value === 23)
58
+ next.italic = false;
59
+ else if (value === 24)
60
+ next.underline = false;
61
+ else if (value === 27)
62
+ next.inverse = false;
63
+ else if (value >= 30 && value <= 37)
64
+ next.fg = BASE_COLORS[value - 30];
65
+ else if (value >= 90 && value <= 97)
66
+ next.fg = BASE_COLORS[value - 90 + 8];
67
+ else if (value >= 40 && value <= 47)
68
+ next.bg = BASE_COLORS[value - 40];
69
+ else if (value >= 100 && value <= 107)
70
+ next.bg = BASE_COLORS[value - 100 + 8];
71
+ else if (value === 39)
72
+ delete next.fg;
73
+ else if (value === 49)
74
+ delete next.bg;
75
+ else if (value === 38 || value === 48) {
76
+ const target = value === 38 ? 'fg' : 'bg';
77
+ const mode = parts[index + 1];
78
+ if (mode === '5') {
79
+ const color = Number.parseInt(parts[index + 2] ?? '', 10);
80
+ if (Number.isFinite(color))
81
+ next[target] = ansi256Color(color);
82
+ index += 2;
83
+ }
84
+ else if (mode === '2') {
85
+ const r = Number.parseInt(parts[index + 2] ?? '', 10);
86
+ const g = Number.parseInt(parts[index + 3] ?? '', 10);
87
+ const b = Number.parseInt(parts[index + 4] ?? '', 10);
88
+ if ([r, g, b].every(Number.isFinite))
89
+ next[target] = hex(r, g, b);
90
+ index += 4;
91
+ }
92
+ }
93
+ }
94
+ return next;
95
+ }
96
+ /** Consumes a CSI sequence at `start`; returns the index after it. */
97
+ function csiEnd(text, start) {
98
+ let index = start + 2;
99
+ while (index < text.length) {
100
+ const code = text.charCodeAt(index);
101
+ if (code >= 0x40 && code <= 0x7e)
102
+ return index + 1;
103
+ index += 1;
104
+ }
105
+ return text.length;
106
+ }
107
+ /** Consumes an OSC or APC sequence at `start`; returns the index after its terminator. */
108
+ function stringSequenceEnd(text, start) {
109
+ let index = start + 2;
110
+ while (index < text.length) {
111
+ const code = text.charCodeAt(index);
112
+ if (code === 0x07)
113
+ return index + 1;
114
+ if (code === 0x1b && text.charCodeAt(index + 1) === 0x5c)
115
+ return index + 2;
116
+ index += 1;
117
+ }
118
+ return text.length;
119
+ }
120
+ function stripControl(text) {
121
+ return text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
122
+ }
123
+ /**
124
+ * Parses SGR styled lines. Non-style escape sequences, including Pi's cursor markers, are removed;
125
+ * an OSC 8 hyperlink keeps its visible label.
126
+ */
127
+ export function parseAnsi(text) {
128
+ const lines = [];
129
+ let spans = [];
130
+ let style = defaultStyle();
131
+ let buffer = '';
132
+ const flush = () => {
133
+ if (!buffer)
134
+ return;
135
+ spans.push({ text: stripControl(buffer), style: { ...style } });
136
+ buffer = '';
137
+ };
138
+ const newline = () => {
139
+ flush();
140
+ lines.push({ spans });
141
+ spans = [];
142
+ };
143
+ for (let index = 0; index < text.length;) {
144
+ const char = text[index];
145
+ if (char === '\n') {
146
+ newline();
147
+ index += 1;
148
+ continue;
149
+ }
150
+ if (char === '\r') {
151
+ index += 1;
152
+ continue;
153
+ }
154
+ if (char === '\t') {
155
+ buffer += ' ';
156
+ index += 1;
157
+ continue;
158
+ }
159
+ if (char !== '\x1b') {
160
+ buffer += char;
161
+ index += 1;
162
+ continue;
163
+ }
164
+ const next = text[index + 1];
165
+ if (next === '[') {
166
+ const end = csiEnd(text, index);
167
+ const body = text.slice(index + 2, end);
168
+ if (body.endsWith('m')) {
169
+ flush();
170
+ style = applySgr(style, body.slice(0, -1));
171
+ }
172
+ index = end;
173
+ continue;
174
+ }
175
+ if (next === ']' || next === '_' || next === 'P' || next === '^') {
176
+ index = stringSequenceEnd(text, index);
177
+ continue;
178
+ }
179
+ index += next === undefined ? 1 : 2;
180
+ }
181
+ flush();
182
+ lines.push({ spans });
183
+ return lines;
184
+ }
185
+ export function ansiPlain(text) {
186
+ return parseAnsi(text).map(line => line.spans.map(span => span.text).join('')).join('\n');
187
+ }
188
+ /** A short single-line form for labels and tool summaries. */
189
+ export function ansiLabel(text, limit = 120) {
190
+ const plain = ansiPlain(text).replace(/\s+/g, ' ').trim();
191
+ return plain.length > limit ? `${plain.slice(0, Math.max(0, limit - 1))}…` : plain;
192
+ }
@@ -0,0 +1,163 @@
1
+ import { type AgentSession } from '@earendil-works/pi-coding-agent';
2
+ import type { TerminalCommandHelpGroup } from "../commandHelp.js";
3
+ export type ToolStatus = 'running' | 'ok' | 'error' | 'pending';
4
+ export interface UserItem {
5
+ kind: 'user';
6
+ id: string;
7
+ text: string;
8
+ images: number;
9
+ timestamp: number;
10
+ }
11
+ export interface AssistantItem {
12
+ kind: 'assistant';
13
+ id: string;
14
+ text: string;
15
+ model?: string;
16
+ streaming: boolean;
17
+ timestamp: number;
18
+ }
19
+ export interface ThinkingItem {
20
+ kind: 'thinking';
21
+ id: string;
22
+ text: string;
23
+ streaming: boolean;
24
+ }
25
+ export interface ToolItem {
26
+ kind: 'tool';
27
+ id: string;
28
+ toolCallId: string;
29
+ name: string;
30
+ summary: string;
31
+ status: ToolStatus;
32
+ output: string;
33
+ durationMs?: number;
34
+ }
35
+ export interface ImageResultItem {
36
+ kind: 'image-result';
37
+ id: string;
38
+ text: string;
39
+ imageCount: number;
40
+ images: {
41
+ data: string;
42
+ mimeType: string;
43
+ }[];
44
+ showImages: boolean;
45
+ }
46
+ export interface NoticeItem {
47
+ kind: 'notice';
48
+ id: string;
49
+ text: string;
50
+ level: string;
51
+ }
52
+ export interface CommandHelpItem {
53
+ kind: 'command-help';
54
+ id: string;
55
+ groups: readonly TerminalCommandHelpGroup[];
56
+ }
57
+ export interface BashItem {
58
+ kind: 'bash';
59
+ id: string;
60
+ command: string;
61
+ timestamp: number;
62
+ output: string;
63
+ running: boolean;
64
+ exitCode?: number;
65
+ cancelled: boolean;
66
+ }
67
+ export interface CustomItem {
68
+ kind: 'custom';
69
+ id: string;
70
+ label: string;
71
+ text: string;
72
+ }
73
+ export interface SummaryItem {
74
+ kind: 'summary';
75
+ id: string;
76
+ label: string;
77
+ text: string;
78
+ }
79
+ export interface CompactionItem {
80
+ kind: 'compaction';
81
+ id: string;
82
+ summary: string;
83
+ tokensBefore?: number;
84
+ }
85
+ /** A `todo_write` call, shown as the list it wrote rather than as a tool row. */
86
+ export interface TodoTranscriptItem {
87
+ kind: 'todo';
88
+ id: string;
89
+ /** Set for a tool call; absent for a `/todo` snapshot. */
90
+ toolCallId?: string;
91
+ /** Set for a tool call; a command snapshot has no status mark. */
92
+ status?: ToolStatus;
93
+ origin: 'tool' | 'command';
94
+ items: readonly TodoEntry[];
95
+ }
96
+ export interface TodoEntry {
97
+ text: string;
98
+ status: 'pending' | 'in_progress' | 'completed';
99
+ }
100
+ /** A proposed plan block from a plan-mode reply, shown as a card. */
101
+ export interface PlanItem {
102
+ kind: 'plan';
103
+ id: string;
104
+ title: string;
105
+ body: string;
106
+ streaming: boolean;
107
+ }
108
+ export type TranscriptItem = UserItem | AssistantItem | ThinkingItem | ToolItem | ImageResultItem | NoticeItem | CommandHelpItem | BashItem | CustomItem | SummaryItem | CompactionItem | PlanItem | TodoTranscriptItem;
109
+ export interface TranscriptView {
110
+ items: readonly TranscriptItem[];
111
+ hasUserMessages: boolean;
112
+ hasMultilineNotice: boolean;
113
+ }
114
+ /** The items of a `todo_write` (or pre-rename `checklist_write`) call, or null when they are not a todo list. */
115
+ export declare function todoEntries(args: unknown): TodoEntry[] | null;
116
+ export declare class TranscriptModel {
117
+ private session?;
118
+ /** Items derived from a finished message, reused while its position and tool results stay the same. */
119
+ private messageItems;
120
+ private hideThinking;
121
+ private showImages;
122
+ private expanded;
123
+ private notices;
124
+ private nextNotice;
125
+ private liveBash?;
126
+ private cachedView?;
127
+ private readonly cachedItems;
128
+ /** What a compaction took out of Pi's message list, rebuilt from the session history on bind. */
129
+ private history?;
130
+ bind(session: AgentSession): void;
131
+ clear(): void;
132
+ invalidate(): void;
133
+ beginBash(command: string): void;
134
+ appendBashOutput(id: string, delta: string): boolean;
135
+ finishBash(): void;
136
+ clearNotices(): void;
137
+ setThinkingHidden(hidden: boolean): void;
138
+ setImagesShown(shown: boolean): void;
139
+ setExpanded(expanded: boolean): void;
140
+ get isExpanded(): boolean;
141
+ notice(text: string, level?: string): void;
142
+ commandHelp(groups: readonly TerminalCommandHelpGroup[]): void;
143
+ /** A `/todo` snapshot: the list at the moment the command ran, with no tool row. */
144
+ todo(items: readonly TodoEntry[]): void;
145
+ private addNotice;
146
+ /**
147
+ * Notices in the order they appear, each with the message index it goes before. Compaction
148
+ * replaces the message list with a shorter one, so a notice follows the message it was posted
149
+ * after; a notice whose message was summarized goes right after the compaction card.
150
+ */
151
+ private placedNotices;
152
+ view(): TranscriptView;
153
+ /**
154
+ * Pi's list after a compaction starts with the summary and the kept messages. The transcript
155
+ * shows the whole conversation instead: everything before the compaction from the session
156
+ * history, the summary, then what came after.
157
+ */
158
+ private displayMessages;
159
+ /** What a message's items depend on besides the message itself. */
160
+ private messageKey;
161
+ private messageItemsFor;
162
+ private stabilize;
163
+ }