@xingular/harness 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +28 -25
  3. package/dist/cli.js +6675 -2501
  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 +15 -1
  11. package/dist/sdk/agents/planResearch.d.ts +5 -0
  12. package/dist/sdk/agents/planResearch.js +19 -0
  13. package/dist/sdk/agents/profiles.d.ts +2 -1
  14. package/dist/sdk/agents/profiles.js +9 -4
  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/cli.js +5 -4
  26. package/dist/sdk/commands/list.d.ts +3 -0
  27. package/dist/sdk/commands/list.js +75 -0
  28. package/dist/sdk/commands/registry.d.ts +250 -0
  29. package/dist/sdk/commands/registry.js +47 -0
  30. package/dist/sdk/commands/routes.d.ts +16 -0
  31. package/dist/sdk/commands/routes.js +33 -0
  32. package/dist/sdk/core/commandBudget.d.ts +23 -0
  33. package/dist/sdk/core/commandBudget.js +57 -0
  34. package/dist/sdk/core/index.js +17 -1
  35. package/dist/sdk/core/interactiveProfile.d.ts +46 -0
  36. package/dist/sdk/core/interactiveProfile.js +143 -0
  37. package/dist/sdk/core/workspace.d.ts +13 -0
  38. package/dist/sdk/core/workspace.js +88 -0
  39. package/dist/sdk/core/workspaceBashTool.d.ts +4 -0
  40. package/dist/sdk/core/workspaceBashTool.js +36 -0
  41. package/dist/sdk/core/workspaceSandbox.d.ts +31 -0
  42. package/dist/sdk/core/workspaceSandbox.js +320 -0
  43. package/dist/sdk/core/workspaceWriteOperations.d.ts +22 -0
  44. package/dist/sdk/core/workspaceWriteOperations.js +206 -0
  45. package/dist/sdk/doctor/types.d.ts +2 -2
  46. package/dist/sdk/harness.d.ts +7 -0
  47. package/dist/sdk/harness.js +42 -2
  48. package/dist/sdk/index.d.ts +6 -1
  49. package/dist/sdk/index.js +3 -0
  50. package/dist/sdk/pi/runtime.d.ts +2 -1
  51. package/dist/sdk/pi/runtime.js +2 -2
  52. package/dist/sdk/plugins/builtin.js +29 -12
  53. package/dist/sdk/plugins/builtinRegistry.d.ts +10 -3
  54. package/dist/sdk/plugins/builtinRegistry.js +30 -8
  55. package/dist/sdk/plugins/catalogue.js +2 -1
  56. package/dist/sdk/plugins/commandPrefix.d.ts +8 -0
  57. package/dist/sdk/plugins/commandPrefix.js +21 -0
  58. package/dist/sdk/plugins/firstParty.d.ts +1 -1
  59. package/dist/sdk/plugins/firstParty.js +8 -2
  60. package/dist/sdk/plugins/inventory.js +4 -3
  61. package/dist/sdk/plugins/manifest.d.ts +2 -0
  62. package/dist/sdk/plugins/manifest.js +2 -1
  63. package/dist/sdk/plugins/surfaces.d.ts +5 -0
  64. package/dist/sdk/plugins/surfaces.js +11 -0
  65. package/dist/sdk/plugins/types.d.ts +15 -0
  66. package/dist/sdk/prompt/compile.d.ts +14 -0
  67. package/dist/sdk/prompt/compile.js +65 -29
  68. package/dist/sdk/prompt/standard.d.ts +17 -0
  69. package/dist/sdk/prompt/standard.js +21 -0
  70. package/dist/sdk/server/methods.js +18 -0
  71. package/dist/sdk/server/remote.js +36 -11
  72. package/dist/sdk/sessions/compactionCheckpoint.d.ts +11 -0
  73. package/dist/sdk/sessions/compactionCheckpoint.js +79 -0
  74. package/dist/sdk/sessions/continuation.d.ts +3 -0
  75. package/dist/sdk/sessions/continuation.js +5 -1
  76. package/dist/sdk/sessions/contracts.d.ts +168 -0
  77. package/dist/sdk/sessions/contracts.js +59 -1
  78. package/dist/sdk/sessions/control.d.ts +72 -0
  79. package/dist/sdk/sessions/control.js +158 -0
  80. package/dist/sdk/sessions/initialBash.d.ts +7 -0
  81. package/dist/sdk/sessions/initialBash.js +7 -0
  82. package/dist/sdk/sessions/planMode.d.ts +14 -3
  83. package/dist/sdk/sessions/planMode.js +73 -16
  84. package/dist/sdk/sessions/registry.d.ts +1 -1
  85. package/dist/sdk/sessions/registry.js +2 -2
  86. package/dist/sdk/sessions/runStore.d.ts +14 -0
  87. package/dist/sdk/sessions/runStore.js +35 -0
  88. package/dist/sdk/sessions/runner.d.ts +40 -2
  89. package/dist/sdk/sessions/runner.js +161 -13
  90. package/dist/sdk/sessions/sessions.d.ts +23 -3
  91. package/dist/sdk/sessions/sessions.js +202 -17
  92. package/dist/sdk/sessions/subagentService.d.ts +9 -0
  93. package/dist/sdk/sessions/subagentService.js +63 -4
  94. package/dist/sdk/sessions/workSlice.d.ts +103 -0
  95. package/dist/sdk/sessions/workSlice.js +297 -0
  96. package/dist/sdk/sessions/workspaceBoundary.d.ts +15 -0
  97. package/dist/sdk/sessions/workspaceBoundary.js +46 -0
  98. package/dist/sdk/terminal/browserControl.js +2 -1
  99. package/dist/sdk/terminal/controller.d.ts +11 -3
  100. package/dist/sdk/terminal/controller.js +40 -13
  101. package/dist/sdk/terminal/opentui/theme.d.ts +43 -0
  102. package/dist/sdk/terminal/opentui/theme.js +87 -0
  103. package/dist/sdk/terminal/piExtension.d.ts +6 -1
  104. package/dist/sdk/terminal/piExtension.js +154 -54
  105. package/dist/sdk/terminal/standalone.d.ts +7 -0
  106. package/dist/sdk/terminal/standalone.js +72 -13
  107. package/dist/sdk/terminal/subagentMonitor.d.ts +11 -2
  108. package/dist/sdk/terminal/subagentMonitor.js +32 -1
  109. package/dist/sdk/terminal/terminals.d.ts +19 -2
  110. package/dist/sdk/terminal/terminals.js +245 -35
  111. package/dist/sdk/terminal/tui/annotateCommand.d.ts +1 -1
  112. package/dist/sdk/terminal/tui/annotateCommand.js +3 -3
  113. package/dist/sdk/terminal/tui/commandPages.d.ts +3 -1
  114. package/dist/sdk/terminal/tui/commandPages.js +3 -0
  115. package/dist/sdk/terminal/tui/commands.d.ts +2 -0
  116. package/dist/sdk/terminal/tui/commands.js +205 -124
  117. package/dist/sdk/terminal/tui/list.d.ts +14 -1
  118. package/dist/sdk/terminal/tui/list.js +96 -14
  119. package/dist/sdk/terminal/tui/pageOverlay.js +13 -4
  120. package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +1 -1
  121. package/dist/sdk/terminal/tui/pages/notesPage.d.ts +5 -5
  122. package/dist/sdk/terminal/tui/pages/notesPage.js +18 -18
  123. package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +0 -5
  124. package/dist/sdk/terminal/tui/pages/pluginsPage.js +9 -3
  125. package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +1 -0
  126. package/dist/sdk/terminal/tui/pages/subagentsPage.js +98 -16
  127. package/dist/sdk/terminal/tui/pages/usagePage.d.ts +43 -0
  128. package/dist/sdk/terminal/tui/pages/usagePage.js +370 -0
  129. package/dist/sdk/terminal/tui/piAdapter.d.ts +1 -2
  130. package/dist/sdk/terminal/tui/piAdapter.js +3 -5
  131. package/dist/sdk/terminal/tui/pluginCommands.d.ts +2 -0
  132. package/dist/sdk/terminal/tui/pluginCommands.js +13 -9
  133. package/dist/sdk/terminal/tui/runtime.js +23 -3
  134. package/dist/sdk/terminal/tui/sessionPicker.d.ts +40 -0
  135. package/dist/sdk/terminal/tui/sessionPicker.js +141 -0
  136. package/dist/sdk/tools/checklist.js +1 -0
  137. package/dist/sdk/tools/checkpoint.d.ts +27 -0
  138. package/dist/sdk/tools/checkpoint.js +154 -0
  139. package/dist/sdk/tools/getCapabilities.d.ts +31 -0
  140. package/dist/sdk/tools/getCapabilities.js +34 -0
  141. package/dist/sdk/tools/spawnSubagent.js +4 -3
  142. package/dist/sdk/usage/analytics.d.ts +20 -0
  143. package/dist/sdk/usage/analytics.js +151 -0
  144. package/dist/sdk/usage/ledger.d.ts +38 -0
  145. package/dist/sdk/usage/ledger.js +142 -0
  146. package/dist/sdk/usage/limits/adapters.d.ts +11 -0
  147. package/dist/sdk/usage/limits/adapters.js +349 -0
  148. package/dist/sdk/usage/limits/capture.d.ts +8 -0
  149. package/dist/sdk/usage/limits/capture.js +47 -0
  150. package/dist/sdk/usage/limits/http.d.ts +12 -0
  151. package/dist/sdk/usage/limits/http.js +51 -0
  152. package/dist/sdk/usage/limits/normalize.d.ts +15 -0
  153. package/dist/sdk/usage/limits/normalize.js +211 -0
  154. package/dist/sdk/usage/limits/service.d.ts +30 -0
  155. package/dist/sdk/usage/limits/service.js +176 -0
  156. package/dist/sdk/usage/limits/types.d.ts +65 -0
  157. package/dist/sdk/usage/limits/types.js +1 -0
  158. package/dist/sdk/usage/serverMethods.d.ts +3 -0
  159. package/dist/sdk/usage/serverMethods.js +22 -0
  160. package/dist/sdk/usage/service.d.ts +32 -0
  161. package/dist/sdk/usage/service.js +66 -0
  162. package/dist/sdk/usage/settings.d.ts +7 -0
  163. package/dist/sdk/usage/settings.js +28 -0
  164. package/dist/sdk/usage/types.d.ts +60 -0
  165. package/dist/sdk/usage/types.js +4 -0
  166. package/dist/terminal-opentui.js +5207 -1883
  167. package/dist/terminal-pi.js +1658 -271
  168. package/docs/architecture/02-sessions-and-runs.md +128 -26
  169. package/docs/architecture/04-prompt.md +50 -12
  170. package/docs/architecture/06-plugins-and-capabilities.md +24 -7
  171. package/docs/architecture/07-tools-and-gate.md +104 -24
  172. package/docs/architecture/08-memory-and-history.md +4 -2
  173. package/docs/architecture/09-accounts-and-connectors.md +31 -1
  174. package/docs/architecture/10-server-protocol.md +58 -3
  175. package/docs/architecture/11-terminal.md +105 -42
  176. package/docs/architecture/12-storage.md +30 -2
  177. package/docs/architecture/13-world-tools.md +22 -1
  178. package/docs/architecture/14-surfaces-and-parity.md +54 -7
  179. package/docs/architecture/README.md +3 -1
  180. package/docs/architecture/proposals/PROP-0017-page-annotations.md +6 -2
  181. package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +1 -1
  182. package/docs/architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md +54 -0
  183. package/docs/architecture/proposals/PROP-0032-planning-quality.md +196 -0
  184. package/docs/architecture/proposals/PROP-0033-compaction-plan-checkpoints.md +75 -0
  185. package/docs/architecture/proposals/PROP-0034-conversation-fast-mode.md +120 -0
  186. package/docs/architecture/proposals/PROP-0035-retain-subagent-status.md +47 -0
  187. package/docs/architecture/proposals/PROP-0036-first-class-harness-commands.md +59 -0
  188. package/docs/architecture/proposals/PROP-0037-workspace-write-boundary.md +63 -0
  189. package/docs/architecture/proposals/PROP-0038-annotate-open-shadow-roots.md +73 -0
  190. package/docs/architecture/proposals/PROP-0039-workspace-boundary-hardening.md +139 -0
  191. package/docs/architecture/proposals/PROP-0040-one-launch-path.md +87 -0
  192. package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +247 -0
  193. package/docs/architecture/proposals/PROP-0042-usage-and-provider-limits.md +127 -0
  194. package/docs/architecture/proposals/PROP-0043-read-only-plan-bash.md +74 -0
  195. package/docs/architecture/proposals/README.md +14 -0
  196. package/docs/decisions/ADR-0001-owned-terminal.md +1 -1
  197. package/docs/decisions/ADR-0021-required-command-namespaces.md +1 -1
  198. package/docs/decisions/ADR-0031-plugin-command-owner-namespaces.md +37 -0
  199. package/docs/decisions/ADR-0032-compaction-run-checkpoints.md +33 -0
  200. package/docs/decisions/ADR-0033-plan-mode-research-delegation.md +47 -0
  201. package/docs/decisions/ADR-0034-first-class-harness-terminal-commands.md +25 -0
  202. package/docs/decisions/ADR-0035-one-launch-path.md +40 -0
  203. package/docs/decisions/ADR-0036-workspace-write-operation-boundary.md +54 -0
  204. package/docs/decisions/ADR-0037-usage-ledger-and-provider-limits.md +44 -0
  205. package/docs/decisions/ADR-0038-plan-bash-os-read-only.md +29 -0
  206. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime-replication.json +420 -0
  207. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime.json +420 -0
  208. package/docs/experiments/EXP-0003-performance-audit/data/terminal-compact.json +376 -0
  209. package/docs/experiments/EXP-0003-performance-audit/data/terminal-history.json +422 -0
  210. package/docs/experiments/EXP-0003-performance-audit/data/terminal-question.json +95 -0
  211. package/docs/experiments/EXP-0003-performance-audit/data/terminal-wide.json +1036 -0
  212. package/docs/experiments/EXP-0003-performance-audit/plan.md +109 -0
  213. package/docs/experiments/EXP-0003-performance-audit/results.md +151 -0
  214. package/docs/handoff-2026-10-25-app-parity-and-solo-tabs.md +202 -0
  215. package/docs/parity.md +80 -24
  216. package/docs/releases/1.1.1.md +28 -0
  217. package/docs/releases/1.2.0.md +49 -0
  218. package/docs/releases/4.0.0.md +10 -9
  219. package/docs/releases/README.md +35 -30
  220. package/docs/study/00-index.md +3 -0
  221. package/docs/study/STUDY-0003-long-horizon-runs.md +210 -0
  222. package/docs/terminal-tui.md +100 -59
  223. package/package.json +4 -3
@@ -55,7 +55,9 @@ rebuildable SQLite index.
55
55
  lessons; nothing is evicted.
56
56
  - The `wiki` pointer section says how many lessons there are when there are any.
57
57
  - **Index.** Every finished run is indexed when it ends; `open` and `rebuildIndex()` sync from
58
- `runs/`.
58
+ `runs/`. The same pass writes the **usage ledger**: one `usage_requests` row per model request
59
+ from the run's `usage.updated` events, and one `usage_indexed` marker per run (PROP-0042).
60
+ `/usage:analysis` queries only that ledger; it is derived and rebuilt from run events.
59
61
 
60
62
  ## Invariants
61
63
 
@@ -84,7 +86,7 @@ rebuildable SQLite index.
84
86
  <data root>/wiki/ home.md, library/, projects/<slug>/
85
87
  <data root>/wiki-history/ earlier versions of notes
86
88
  <data root>/legacy/ an earlier Wiki's memory blocks, facts, revisions, Inbox (moved once)
87
- <data root>/index.sqlite derived: pages, links, runs, recall passages (FTS5)
89
+ <data root>/index.sqlite derived: pages, links, runs, recall passages (FTS5), usage rows
88
90
  <data root>/projects.json read only: the application owns it
89
91
  ```
90
92
 
@@ -67,7 +67,7 @@ skills, prompts, rules and Pi packages.
67
67
  ## Data
68
68
 
69
69
  ```text
70
- <state dir>/accounts/ registry, per-account Pi agent folders, _shared/pi/, audit log
70
+ <state dir>/accounts/ registry, per-account Pi agent folders, _shared/pi/, audit log, per-account state (health, plan, limits)
71
71
  <state dir>/secrets.json default SecretStore (owner-only)
72
72
  <state dir>/connections.json connected apps, no secrets
73
73
  <state dir>/hub/catalog.json catalog cache
@@ -89,6 +89,36 @@ at 64 tokens, and records usage through the normal run writer. It never substitu
89
89
  MCP live diagnostics connect and list tools on the selected scoped connector, without calling
90
90
  connector tools or starting sign-in. Disposable grants and clients are closed afterwards.
91
91
 
92
+ ## Provider limits (PROP-0042)
93
+
94
+ A limit reading says what a provider itself reported: a plan window, a credit balance, a rate
95
+ limit, or a configured organization limit. Three sources are used, in order:
96
+
97
+ 1. **Response headers** on the account's own model requests. Each account runtime's providers are
98
+ wrapped so `onResponse` sees status and headers; a terminal session's own runtime forwards only
99
+ the limit-bearing headers to the parent over the control-key-protected session socket.
100
+ `usage/limits/normalize.ts` understands `anthropic-ratelimit-*`, Anthropic unified plan windows,
101
+ `x-codex-*` and `x-ratelimit-*`, plus `Retry-After` on a 429. Raw headers are never stored.
102
+ 2. **The provider's own usage endpoint**, read with `Models.getAuth(provider)` so OAuth refresh
103
+ stays in Pi's serialized credential path: OpenRouter `GET /api/v1/key`, DeepSeek
104
+ `GET /user/balance`, Anthropic's organization `rate_limits` for an admin API key (configured
105
+ limits, not usage), ChatGPT/Codex `GET https://chatgpt.com/backend-api/wham/usage`, and
106
+ OpenCode Go `GET https://opencode.ai/zen/go/v1/usage`. A Codex request carries the account id
107
+ from the access token's JWT claim; an OpenCode Go request carries the account's API key.
108
+ The request is a bounded HTTPS GET that refuses redirects, caps its body, and never puts a
109
+ credential in an error. An endpoint reading replaces every limit for that provider; a header
110
+ reading only merges the windows it saw.
111
+ 3. **Unavailable**, with a reason. No percentage is invented; local usage is shown instead.
112
+
113
+ Undocumented endpoints require the usage plugin's `experimentalProbes` setting. Readings are
114
+ cached for `refreshMinutes` (default 10) and refreshed on a page open or an explicit refresh, never
115
+ by a background timer. They are stored in the account's existing state (`limits`,
116
+ `providerLimits`), never in a new file, and no credential is written. A reading carries both `usedPercent` and the additive
117
+ `remainingPercent`. The Limits page is a centered dialog: each window is a label and reset line
118
+ above a quota-left bar, green at 50% or more left, amber from 20–49%, and red below 20%. It adds
119
+ the local seven-day ledger without a label, so a provider's zero reading and this app's own use
120
+ are not confused.
121
+
92
122
  ## Terminal connection setup
93
123
 
94
124
  The unified browser exposes existing connection save/test/sign-in/sign-out/remove services through
@@ -33,7 +33,7 @@ harness → host { v: 1, id, call, params } host → harness { v: 1
33
33
  - `harness.methods` lists every method. Groups in the table: `accounts`, `harnesses`, `runs`,
34
34
  `connections`, `plugins`, `wiki`, `refine`, `capabilities`, `hub`, `terminal`, `approvals`,
35
35
  `harness`, `subagents`, `agents`, `sessions`, `sideQuestions`, `library`, `recall`, `context`,
36
- `worktrees`, `annotations`.
36
+ `worktrees`, `annotations`, `commands`, `usage`.
37
37
  - Host calls (only with the matching `--host-*` flag): `secrets.get|set|delete`,
38
38
  `policy.scope|authorize|approval|connections`, `tools.call`. The host registers tools with
39
39
  `tools.register` and names with `directory.set`, and describes its parts with
@@ -41,16 +41,69 @@ harness → host { v: 1, id, call, params } host → harness { v: 1
41
41
  - `refine.start` takes `{ task, k?, mode?, improver?, executor?, baseVersion?, retainWorkspaces?, id? }`;
42
42
  `refine.candidates` returns each execution with `workspace: string | null` (page 05, PROP-0002, PROP-0003).
43
43
  - `runs.checklist { runId }` returns the run's latest checklist, `[]` when it wrote none (PROP-0006).
44
+ - Run records include optional nullable `workspaceRoot`, the canonical root whose files a run may
45
+ modify by default (page 02). New Harness runs set it; older records may omit it. Hosts must
46
+ tolerate the additive field and must not nominate or widen the root.
44
47
  - `agents.list { cwd? }` returns `{ profiles, problems }`; `agents.get { cwd?, name }` one profile;
45
48
  `agents.create { cwd?, name, scope: 'global' | 'project', from? }` writes the template or a copy
46
49
  and refuses an existing file; `agents.remove { cwd?, name, scope }` deletes a person's own file.
47
50
  `subagents.spawn` takes `agent` as a profile name and returns `agent` and `unavailableTools`
48
51
  (PROP-0029). It takes an optional `title` (at most six words); without one the child's title is
49
52
  the first line of its brief (PROP-0030).
50
- - `runs.continuation { runId, sessionDir? }` returns a `Continuation`, and `sessions.run` accepts
51
- `continueFrom` (page 02, PROP-0007). `harness.status` reports `recovery.interruptedRunIds`.
53
+ - `runs.continuation { runId, sessionDir? }` returns a `Continuation` (its `checkpoint` is the last
54
+ work-slice handoff), and `sessions.run` accepts `continueFrom` (page 02, PROP-0007) and `workSlice`
55
+ (PROP-0041). `harness.status` reports `recovery.interruptedRunIds`.
56
+ - `workSlice { enabled?, maxToolCalls?, maxMinutes?, maxNewTokens?, maxSlices?, continue? }` on
57
+ `sessions.run` bounds that reply; absent uses 160 calls, 40 minutes, 320 000 new tokens, 8 slices and
58
+ automatic continuation. A token cap counts new input, cache writes and output, never the cached
59
+ prefix. `enabled: false` turns the guard off for that run; `XINGULAR_WORK_SLICE=off` in the
60
+ launching process turns it off for every run in it, and nothing is persisted. The policy is
61
+ carried into the continuation. A value outside its range is refused with `invalid_request` before
62
+ the run record is written.
63
+ - The run-event stream adds `checkpoint` (the bounded handoff) and `heartbeat` (steps, elapsed time,
64
+ last tool, compactions, slice, call budget). Hosts must accept both; an unknown event type is still
65
+ relayed in order (INV-RUN-3).
52
66
  - Errors carry a stable `code`, a message and an optional hint (`core/errors.ts`).
53
67
 
68
+ ## Usage (PROP-0042)
69
+
70
+ | Method | Input | Reply |
71
+ |---|---|---|
72
+ | `usage.summary` | `{ scope?: "session" \| "account" \| "project" \| "all", sessionId?, accountId?, projectId?, period?: "today" \| "7d" \| "30d" \| "all", timezoneOffsetMinutes? }` | `UsageSummary`: totals, cache hit rate, latency, day/model/provider/account/session breakdowns, unpriced count, ledger time |
73
+ | `usage.limits` | `{ accountId?, refresh?: boolean }` | `UsageLimitsReport`: per account and provider, the reading source, reason, observed time and limits |
74
+ | `usage.sync` | `{}` | `{ runs, indexed, lastIndexedAt }` |
75
+
76
+ The methods are contributed by the built-in `usage` plugin module and read only derived or stored
77
+ data; `refresh: true` is the only form that may contact a provider, once per due provider. The
78
+ terminal reaches the same service through protected `terminal.usage.summary|limits`, whose scope
79
+ comes from the terminal session, never from the request.
80
+
81
+ ## Commands and session controls (PROP-0040)
82
+
83
+ `commands.list { cwd?, accountId?, agentId?, projectId? }` returns `ListedCommand[]`: `name`,
84
+ optional `aliases`, `args`, `description`, `owner`, plus `group`, `source`
85
+ (`core | plugin | extension | prompt | skill`) and optional core `kind` (`session | page | local`).
86
+ Core metadata comes from `commands/registry.ts`; effective plugin contributions supply other routes.
87
+ Namespace/collision rules are shared; discovery neither runs a command nor proves surface acceptance.
88
+
89
+ For the methods below, `ref` means `{ sessionDir, sessionFile?, cwd? }`.
90
+
91
+ | Method | Input | Reply |
92
+ |---|---|---|
93
+ | `sessions.tree` | `ref` | `SessionTree`: file, leaf, entries and user-message fork points |
94
+ | `sessions.navigate` | `ref + { entryId }` | `{ leafId, editorText? }`; no summary/model request |
95
+ | `sessions.fork` | `ref + { entryId, targetDir }` | `{ sessionFile, editorText }` |
96
+ | `sessions.clone` | `ref + { targetDir }` | `{ sessionFile }` |
97
+ | `sessions.rename` | `ref + { name }` | `{ name }` |
98
+ | `sessions.lastReply` | `ref` | `{ text: string | null }` |
99
+ | `sessions.export` | `ref + { path }` | `{ path }`; absolute `.jsonl` branch export |
100
+ | `sessions.reload` | `{}` | `{ reloaded: true }`; reloads persistent hooks; structured turns load resources fresh |
101
+
102
+ `sessions.run` also accepts `command` for compact, navigation with optional summary, or HTML
103
+ export instead of prompt submission (page 02). These retain tracked run lifecycle and pins.
104
+ Saved-history helpers currently validate file scope but do not themselves fence active work;
105
+ host adapters must not infer concurrency safety from method availability.
106
+
54
107
  ## Invariants
55
108
 
56
109
  - **INV-SRV-1** `src/server/methods.ts` is the contract for every non-Node host. Adding a method
@@ -129,6 +182,8 @@ from preparation, never from the request.
129
182
  | `terminal.browser.connections.appClients` | `{}` | Existing available application OAuth catalog IDs |
130
183
  | `terminal.browser.connections.save` | `{ input: ConnectionInput }` | Existing safe `Connection` record |
131
184
  | `terminal.browser.connections.test\|signIn\|signOut\|remove` | `{ id }` | Existing connection service result |
185
+ | `terminal.usage.summary\|limits` | `{ scope, period, timezoneOffsetMinutes }` / `{ refresh? }` | Existing usage service result, scoped to this terminal's account and project |
186
+ | `terminal.usage.headers` | `{ provider, status, headers }` | `{}`; only limit-bearing headers, throttled, stored as an account limit reading |
132
187
 
133
188
  Protected `plugins.install { source }` and `plugins.remove { pluginId }` delegate to the package
134
189
  service. Optional matching callbacks extend `PluginControl`; hosts without them receive a clear
@@ -34,32 +34,56 @@ Screen: transcript above the composer, centred dialogs, full-screen resource bro
34
34
  `--conditions=browser` and `dist/terminal-opentui.js`; no global `pi` is needed.
35
35
  - The `piExtension` path is the final prepared extension argument; the runtime loads it as the
36
36
  final trusted inline factory.
37
+ - Each terminal `begin` returns the run's pinned `workspaceRoot` to that extension. Native file
38
+ writes use the same revalidated atomic helper and OS write scope as SDK sessions. Terminal Bash,
39
+ including initial `!` commands, defaults to the workspace sandbox and external-hard-link scan;
40
+ `!outside <command>` requests human-only approval before that exact command runs once; its full
41
+ escaped command is available in scrollable approval details, and an undersized terminal denies
42
+ the request. The eligible Full Permission switch is the only standing boundary bypass; remembered
43
+ per-tool grants do not widen it, and command output never triggers approval or replay.
37
44
  - The terminal compiles its prompt once (`compile` + `render`) and keeps that pack; each run
38
- records a copy (page 04).
39
- - Normal mode keeps `ask_user` and `ask_user_question` out of the active tool set. A plan request
40
- enables the registered question tools alongside its read-only set and restores the normal set on
41
- success, failure or interruption. See page 02 and the [terminal guide](../terminal-tui.md).
42
- - Standalone defaults: `~/.xingular-harness/{data,state}`; `npm run dev` uses
43
- `~/.xingular-harness-dev/{data,state}`.
45
+ records a copy (page 04). The shared compaction checkpoint extension reintroduces the active
46
+ request and latest checklist into post-compaction model context until the run settles.
47
+ - Normal mode keeps `ask_user` out of the active tool set. An attended plan request enables the
48
+ shared tool alongside its read-only set and restores the normal set on success, failure or
49
+ interruption. There is no terminal-only question alias (PROP-0040).
50
+ - A plan request keeps `bash` in its active set; each call runs in the sandbox's read-only scope
51
+ (PROP-0043). A user `!` command is refused while a plan request runs.
52
+ - Current standalone source defaults: `~/.xingular-harness/{data,state}`; `npm run dev` uses
53
+ `~/.xingular-harness-dev/{data,state}`. The standalone development launch uses a file SecretStore
54
+ under its resolved development state directory, even when its profile names a host backend; it
55
+ does not use or alter host credentials. `--variant <scope>` explicitly selects a variant head.
56
+ PROP-0040 approves a shared Harness-owned interactive profile, neutral default scope and
57
+ credential backend; adoption and runtime verification remain in progress. Until that lands,
58
+ matching roots/scopes is explicit and matching roots alone does not share host Keychain secrets.
59
+ - `Launch.standard` supplies the shared role and folder text. History, Wiki and skill model tools
60
+ use the session socket; enabled output-trim/budget-guard hooks load through `firstParty.list`.
61
+ Pi CLI built-ins (llama.cpp, codemode, tool-search and MCP) are not loaded as a terminal-only
62
+ resource path. Harness MCP remains available.
44
63
  - An empty conversation shows a centered welcome using `env.productName` (default `Xingular Agent`):
45
64
  the first letter of a multiword name is the orange block mark and its remaining words are letter-spaced
46
65
  in blue. The welcome adds a greeting, tagline and `/help` hint without replacing the composer or
47
66
  status bar. Explicit host product names use the same generic formatting.
48
67
  - `xagent --worktree [name]` (`-w`) starts in a new managed worktree of the current repository,
49
68
  or in the named one, and prunes older managed worktrees (PROP-0016). A session that names a
50
- worktree holds it for its whole life. `/worktree` switches folders by asking the host to restart
51
- the frontend there (`TerminalRelaunch.cwd`), as a new conversation: Pi keeps one history per
52
- working folder.
53
- - Slash-command aliases share one registry entry: `/session (resume)` switches saved sessions,
54
- `/history (tree)` navigates branches in the current session, and `/btw (side)` asks a side
69
+ worktree holds it for its whole life. `/worktree` opens the worktree page; selecting
70
+ one switches folders by asking the host to restart the frontend there (`TerminalRelaunch.cwd`),
71
+ as a new conversation: Pi keeps one history per working folder.
72
+ - Slash-command aliases share one registry entry: `/session (resume)` opens the full-screen, searchable
73
+ saved-session page with unnumbered title and metadata rows; `/history (tree)` navigates branches in
74
+ the current session, and `/btw (side)` asks a side
55
75
  question through the Harness `SideQuestions` service. It uses the same account/model/effort,
56
76
  mirrors the main run's redacted prompt and public evidence, starts a separate tool-free run, and
57
77
  leaves the main conversation untouched. The dock streams, scrolls and copies the complete answer.
58
78
  Aliases remain callable and searchable, but help and palette show only the canonical
59
- entry. Commands owned by required built-in plugins and core terminal commands are unprefixed.
60
- Optional built-in plugin and Pi extension commands use `/plugin:<command>`. A bare command or
61
- prompt owns its name; a colliding `/plugin:<command>` is omitted and rejected with a hint to use
62
- the bare command. Prompt templates and `/skill:<name>` otherwise keep their existing forms.
79
+ entry. `/usage:` and `/usage:analysis` open the usage page's Analysis tab, and `/usage:limit`
80
+ opens its Limits tab (PROP-0042); both are plugin-owned namespaced routes. The usage page is a
81
+ centered dialog: each window is a label and reset line above a quota-left bar coloured green at
82
+ 50% or more left, amber from 20–49%, and red below 20%. `commands.list`
83
+ publishes the effective routes; the browser-safe core catalogue is `commands/registry.ts`. First-class Harness commands are unprefixed and live in that registry,
84
+ independently of plugin command metadata. Every plugin-owned command uses
85
+ `/<owner-prefix>:<command>`; the namespace root is offered only by plugins that declare a default
86
+ command. Prompt templates and `/skill:<name>` keep their existing forms.
63
87
 
64
88
  ## Invariants
65
89
 
@@ -80,20 +104,37 @@ Screen: transcript above the composer, centred dialogs, full-screen resource bro
80
104
  revoked code needs review and a fresh terminal.
81
105
  - **INV-TUI-6** `sessions.run` stays the embedding and unattended path. The terminal keeps a
82
106
  persistent child instead of opening a session per turn.
83
- - **INV-TUI-7** Required built-in plugin commands use their unprefixed names; optional built-in
84
- plugin commands and Pi extension commands use `/plugin:<command>`. Completions and help show each
85
- command in its canonical namespace. Bare core commands, aliases, required built-in commands and
86
- loaded prompts reserve their names; a colliding prefixed command is hidden and rejected with a hint
87
- to use the bare command. Duplicate extension names are rejected with owner diagnostics. Old
88
- prefixed forms of required built-in commands are not aliases.
107
+ - **INV-TUI-7** Harness core commands use unprefixed names and are registered independently of
108
+ feature-plugin command metadata. This includes `/doctor`, `/worktree`, `/trajectory`, `/subagents`
109
+ and `/agents`; `/history` remains session-branch navigation, and `/mcp` remains a Connections
110
+ shortcut. Every plugin-owned terminal command uses `/<owner-prefix>:<command>`, for required and
111
+ optional built-ins and Pi extensions alike. Completions, help and dispatch use the same stable
112
+ owner prefix. Prefixes are unique to an owner; a prefix collision or duplicate command within an
113
+ owner is rejected with owner diagnostics. `/skill:<name>` remains reserved for skill invocation.
114
+ For one release, the former owner-qualified and `/plugin:<command>` forms of promoted Harness
115
+ commands remain hidden compatibility aliases; aliases are not listed in help or completion.
116
+ - **INV-TUI-8** Terminal sessions use the Harness-pinned workspace root and the same write
117
+ boundary as SDK sessions. Native file mutations use revalidated atomic replacement under an OS
118
+ scope; default Bash scans for external hard-link aliases and runs inside the sandbox. The terminal
119
+ cannot widen the boundary through its socket or native extension. `!outside <command>` requests
120
+ human-only approval before one execution; the terminal shows the complete escaped command and
121
+ denies the request if it cannot display details and choices together. stdout/stderr cannot trigger
122
+ approval or replay. Missing enforcement fails closed. Full Permission bypasses the boundary only
123
+ for eligible interactive
124
+ user runs and does not override hard policy denials, read-only or plan mode, unattended work, or
125
+ refinement restrictions.
89
126
 
90
127
  ## Source map
91
128
 
92
129
  `src/terminal/**`. Decisions: ADR-0001, [ADR-0004](../decisions/ADR-0004-fullscreen-terminal-layout.md)
93
- (superseded), ADR-0006, [ADR-0021](../decisions/ADR-0021-required-command-namespaces.md),
130
+ (superseded), ADR-0006, [ADR-0021](../decisions/ADR-0021-required-command-namespaces.md)
131
+ (superseded by [ADR-0031](../decisions/ADR-0031-plugin-command-owner-namespaces.md)),
94
132
  [ADR-0024](../decisions/ADR-0024-xagent-command.md),
95
133
  [ADR-0027](../decisions/ADR-0027-connected-sketch-display.md),
96
- [ADR-0028](../decisions/ADR-0028-xingular-agent-product-identity.md). Experiments:
134
+ [ADR-0028](../decisions/ADR-0028-xingular-agent-product-identity.md),
135
+ [ADR-0031](../decisions/ADR-0031-plugin-command-owner-namespaces.md),
136
+ [ADR-0034](../decisions/ADR-0034-first-class-harness-terminal-commands.md),
137
+ [ADR-0036](../decisions/ADR-0036-workspace-write-operation-boundary.md). Experiments:
97
138
  EXP-0001, EXP-0002 in `../experiments/`.
98
139
 
99
140
  ## Session name in the working line (PROP-0010)
@@ -166,7 +207,9 @@ Every choice uses one shared measured layout: a marker gutter, a one-third label
166
207
  gap, and the remaining description cell. Column boundaries align across all rows at every terminal
167
208
  width, including compact widths. Text wraps within its cell; a missing description keeps its cell
168
209
  and columns never stack. Recommendation text uses `(Rec.)` when the label column is fewer than 14
169
- cells. Below 60 terminal columns, the dock uses a compact hint.
210
+ cells. Below 60 terminal columns, the dock uses a compact hint. Multi-select choices use `✓`
211
+ when selected and `○` when unselected, matching checklist glyphs; single-choice options remain
212
+ numbered.
170
213
 
171
214
  The dock body has bounded height and vertical scrolling; question tabs and answer/action controls
172
215
  stay outside that scrolling region. PageUp/PageDown scroll the body and Ctrl+Home/End jump to its
@@ -200,21 +243,20 @@ contract is unchanged. See [PROP-0025](proposals/PROP-0025-contextual-question-w
200
243
  PTY verification is retained in `docs/evidence/terminal-wireframe-normalization/`; it establishes
201
244
  cell topology for bounded fixtures, not every emulator/font or live-model sketch quality.
202
245
 
203
- ## Page notes commands (PROP-0017)
246
+ ## Web annotations commands (PROP-0017)
204
247
 
205
- `/plugin:notes` lists the page notes kept in the data root, with search, and offers reveal, edit, resolve or
206
- reopen, share with this session or stop sharing, and delete. `/plugin:annotate [url]` starts a new note: it
207
- opens a visible capture browser (its own window and profile, separate from the agent's browser), and
208
- the person notes the whole page, selects text, or clicks an element there, then confirms in the
209
- terminal. A note is private until shared; this session's agent then reads it with `page_notes_list`
248
+ `/annotate:notes` lists the Web annotations kept in the data root, with search, and offers reveal, edit, resolve or
249
+ reopen, share with this session or stop sharing, and delete. `/annotate:` opens the default capture pane;
250
+ `/annotate:annotate [url]` is its explicit command form. It opens a visible capture browser (its own window and profile, separate from the agent's browser), where the person can annotate the whole page,
251
+ select text, or click an element there. An annotation is private until shared; this session's agent then reads it with `page_notes_list`
210
252
  and `page_notes_read`.
211
253
 
212
- `/plugin:annotate` is the annotate pane (PROP-0018): a full-screen page beside the window being inspected.
254
+ The annotate pane (PROP-0018) is a full-screen page beside the window being inspected.
213
255
 
214
256
  - **Ways to start it:**
215
- - `/plugin:annotate [url]`: the browser window
216
- - `/plugin:annotate --app <DevTools address | port> [name]`: an app's own windows over its DevTools port
217
- - `/plugin:annotate --bridge [name]`: app windows that load the page bridge script, which the pane shows
257
+ - `/annotate:` or `/annotate:annotate [url]`: the capture browser
258
+ - `/annotate:annotate --app <DevTools address | port> [name]`: an app's own windows over its DevTools port
259
+ - `/annotate:annotate --bridge [name]`: app windows that load the page bridge script, which the pane shows
218
260
  - **The inspector** (`i`) outlines components on hover. A click or a text drag opens a popover in the page with Create annotation, Ask agent to fix, and Proceed now.
219
261
  - **Sections:** Queue, Sent and fixed (with the agent's summary), and Saved.
220
262
  - **Other keys:** `s` sends the queue as one run of this session, confirming notes that came from a page-world script first; `x` takes a note out of the queue; `t` picks a window.
@@ -229,25 +271,46 @@ The gate treats it as read-only, plan mode allows it, and terminal children inhe
229
271
  transcript shows each write as a checklist block (`✓` done, `●` in progress (warning yellow), `○` pending) under a
230
272
  `Checklist · n of m done` header instead of a tool row; a failed or malformed call stays a tool row.
231
273
 
274
+ ## Work-slice checkpoint (PROP-0041)
275
+
276
+ Terminal replies carry the same work-slice guard as structured runs: 160 tool calls, 40 minutes or
277
+ 320 000 new tokens, then a refusal that asks for `checkpoint_write`. The extension registers the shared
278
+ tool and sends it as `checkpoint` telemetry, which the controller records as `checkpoint` on the
279
+ active run, so an App reading the same session sees the handoff. Terminal replies are not
280
+ auto-continued: at the boundary the person decides, which is what the terminal's own turn model
281
+ already does. The run record says `interrupted` with terminal reason `slice_complete` (or `stalled`
282
+ when the guard ended it), so a host can continue it; a checkpoint card in the transcript is not built
283
+ yet (PAR-004). Start the terminal with `XINGULAR_WORK_SLICE=off` to launch a reply with no
284
+ boundary at all; it is not persisted, so the next launch without it restores the guard.
285
+
232
286
  ## Subagent activity monitor
233
287
 
234
288
  During an active parent run, the terminal polls a private read-only monitor once per second. It
235
289
  projects the flat set of child and nested-child runs with task, status, elapsed time, the latest
236
290
  tool name, the child's agent profile (name and colour, PROP-0029), the title the parent gave it
237
- (PROP-0030), and counts from the child's own checklist. The child's own `session_name` is not
238
- projected; it names the child's session, not its row. Tool summaries, other
239
- arguments, output and checklist item text never enter the monitor. The latest completed tool name
240
- remains visible across one-second snapshots until new tool or response activity replaces it. The
241
- monitor exposes no controls to steer or stop children.
291
+ (PROP-0030), counts from the child's own checklist, the pinned account label, effective model and
292
+ effort, and cumulative usage totals. Model and effort come from the volatile launch selection; the
293
+ latest provider-reported model supersedes the launch model when usage is available. Usage is an
294
+ explicit projection of token, cache, cost, request-count and context-window totals. Per-request
295
+ payloads and reasoning summaries do not enter the monitor. The child's own `session_name` is not
296
+ projected; it names the child's session, not its row. Tool summaries, other arguments, output and
297
+ checklist item text never enter the monitor. The latest completed tool name remains visible across
298
+ one-second snapshots until new tool or response activity replaces it. The monitor exposes no
299
+ controls to steer or stop children.
242
300
 
243
301
  The live rows sit under the status line, after one blank row, with no heading. Each row reads, in
244
302
  order: the status mark (`○` queued, a running row that shows the working spinner's frame in the profile's colour (warning for a copy of the parent), `●` waiting in the accent,
245
303
  `✓` done, `✕` failed, `!` stopped), the profile name in its colour (`subagent`, muted, for a copy
246
304
  of the parent), the title the parent gave the child (PROP-0030), and `n of m tasks` when the child
247
305
  keeps a checklist. Active rows come first, at most three, then `+N more · /subagents`; a narrow
248
- terminal cuts the title first. `/subagents` opens the searchable full roster with the same fields.
249
- Finished rows remain until the parent run settles. The projection is volatile and scoped to that
250
- parent run; it is not a persisted event or public server method.
306
+ terminal cuts the title first. `/subagents` opens the searchable full roster, with a colored
307
+ profile and normal-color title on its first line, then muted account, model, effort and usage
308
+ metadata on its second line. One blank row separates each two-line entry. Narrow rows truncate this
309
+ lower-priority summary without hiding the title.
310
+ At parent settlement, the terminal makes a final monitor read and keeps the latest parent's rows
311
+ visible until the next parent run starts. The projection remains volatile and read-only, is scoped to
312
+ the latest parent run in the terminal session, and is cleared on session replacement or terminal
313
+ disposal. It is not a persisted event or public server method.
251
314
 
252
315
  ## Agent profiles (PROP-0029)
253
316
 
@@ -25,10 +25,12 @@ index.
25
25
  plugins/<name>/ packages kept with the data (06)
26
26
  trash/plugins/ removed packages (06)
27
27
  hub/ installed documents and provenance (09)
28
- runs/<id>/ run.json, events.jsonl, owner.json, subagents.json, context.json, caller.json, capabilities.json, mcp-context.json (02)
28
+ runs/<id>/ run.json (including optional legacy-compatible workspaceRoot), events.jsonl, owner.json, subagents.json, context.json, caller.json, capabilities.json, mcp-context.json (02)
29
+ # `checkpoint` and `heartbeat` are run events in the same events.jsonl; a bounded reply emits one
30
+ # heartbeat per 30 seconds (about 60 KB an hour) and no side file (PROP-0041).
29
31
  config/ plugin-attachments.json and settings the harness reads (06)
30
32
  agents/<name>.md a person's global agent profiles; agents/profiles.ts owns it (02, PROP-0029)
31
- index.sqlite derived index (FTS5) (08)
33
+ index.sqlite derived index (FTS5, recall, usage ledger) (08, PROP-0042)
32
34
  projects.json read only: the application owns it
33
35
 
34
36
  <state dir>/
@@ -48,6 +50,25 @@ index.
48
50
  harness.log standalone terminal diagnostics
49
51
  ```
50
52
 
53
+ The interactive profile is a credential-free, versioned selection file at
54
+ `~/.xingular-harness/profile.json`, owned by `src/core/interactiveProfile.ts`. It selects the
55
+ interactive defaults: data root, state directory, default variant scope, and a credential-backend
56
+ descriptor. With no selected profile, the defaults are `~/.xingular-harness/data`,
57
+ `~/.xingular-harness/state`, scope `default`, and the state directory's owner-only file secret
58
+ store. A host may provide a stable host-backed secret adapter instead; the profile stores only its
59
+ identifier, never credentials. If that adapter is unavailable, startup fails rather than falling
60
+ back to a different store.
61
+
62
+ Explicit roots or variant scopes take precedence over the selected profile; the selected profile
63
+ precedes caller-supplied legacy defaults. Explicit adoption creates the profile file without
64
+ replacing an existing selection and does not create, copy, merge, or migrate data roots or
65
+ credentials. Existing account, variant, and session pins remain authoritative for their saved
66
+ runs; the profile selects defaults for new interactive work. `scripts/dev.mjs` and a development
67
+ interactive profile use `~/.xingular-harness-dev/{data,state}` and a separate profile file, not the
68
+ production defaults. The standalone development launch always selects a file SecretStore under its
69
+ resolved development state directory, even if that profile names a host backend; it does not use or
70
+ alter host credentials.
71
+
51
72
  Side-question runs use the ordinary `runs/<id>/` records. Their temporary Pi native history is
52
73
  created under the operating system temporary directory and removed when the run ends; it is not
53
74
  retained in the data root or state directory.
@@ -68,6 +89,13 @@ retained in the data root or state directory.
68
89
  as it was when a migration fails, and the failure is reported in `Recovery.migrationError`.
69
90
  - **INV-STO-6** Writes of shared JSON files are atomic (`core/fs.ts::atomicWrite`, `writeJson`).
70
91
  - **INV-STO-7** Files holding trust or secrets are owner-only (`0600`).
92
+ - **INV-STO-8** `core/interactiveProfile.ts` owns the credential-free selection of interactive
93
+ roots, default variant scope, and credential-backend descriptor. Explicit roots/scopes win; a
94
+ selected profile wins over legacy defaults. Adoption never replaces a selection or merges roots,
95
+ and a missing host credential adapter never silently downgrades to a file store. The standalone
96
+ development launch explicitly selects its isolated file store; this is not a fallback for normal
97
+ interactive profiles. Profile defaults do not replace the account, variant, or other pins on saved
98
+ runs.
71
99
 
72
100
  ## Source map
73
101
 
@@ -18,6 +18,21 @@ synthetic page. Their input runs only after live-check confirmation, and their r
18
18
  released on completion, timeout or cancellation. Computer interaction needs a host-provided
19
19
  isolated surface; without one it is blocked.
20
20
 
21
+ ## Shared browser contract and native WebKit limits (PROP-0040)
22
+
23
+ `browser/contracts.ts` owns the backend-neutral names and input schemas for `browser_*` tools;
24
+ providers adapt those definitions rather than inventing host-specific selectors. The isolated Harness
25
+ backend supports accessibility snapshots and snapshot-scoped refs. A native WebKit host backend
26
+ currently supports navigation, tab management, visible-text reads and page-level scrolling without a
27
+ ref. It does not support accessibility snapshots, snapshot refs or ref-based click/type/select/scroll,
28
+ browser screenshots, browser waits or keyboard presses.
29
+
30
+ For an unsupported operation the native provider returns an explicit `browser_unsupported` refusal
31
+ with “Nothing was done”; it never treats a snapshot ref as a selector and never silently switches to
32
+ the isolated browser. The isolated Harness browser is an independent backend, not an automatic
33
+ fallback for the native provider. A host plugin's `surfaces` description labels that fallback and its
34
+ limits; it does not assert full browser parity.
35
+
21
36
  Plugin ownership and the Foundation boundary are recorded in PROP-0012, ADR-0014 and PROP-0020.
22
37
  World providers are assembled through `BuiltinPluginRegistry`; no eighth extension point is added.
23
38
 
@@ -39,7 +54,13 @@ terminal, or in a host's own web view running the same page helper (`annotations
39
54
  - A queued note is shared with its session.
40
55
  - `annotations/delivery.ts` sends a batch as one instruction, and sends notes the person asked for while the session was busy when its run ends.
41
56
  - The agent reports a fix with `page_notes_mark_fixed`, the only page-note tool that writes. It never resolves a note or changes grants.
42
- - **Targets.** Besides the browser window, capture can use an app's own windows:
57
+ - **Targets.** Element targets prefer semantic role/name/text. A visually empty element may still be
58
+ annotated when it has a specific CSS selector (ID, test ID, classes, and a structural path when
59
+ needed); bare tag selectors are not enough. Reveal checks for one live match and preserves any
60
+ captured semantic/context evidence, otherwise reporting ambiguous or unresolved. Text annotations
61
+ continue to use quote plus context, not screen coordinates.
62
+ - Besides the browser window, capture can use an app's own windows:
43
63
  - **DevTools port:** an app started with a DevTools port, on a loopback address only. It uses the app's existing windows, routes nothing, and never closes the app.
44
64
  - **Page bridge:** a loopback server whose script an app loads while it is developed (`annotations/bridge.ts`), for web views without a DevTools port.
65
+ - **Web components (PROP-0038).** The helper walks the composed tree: open shadow roots and slotted content can be picked, selected and revealed, and such targets carry `tree: 'composed'`. Targets without it resolve by the document-only rules as before. Closed shadow roots and frames stay out of reach, and the inspector says why it refuses something.
45
66
  - **Trust.** In Chromium targets the helper runs in an isolated script world, so the page cannot forge a comment. A send from the bridge, or from a host web view without isolation, waits for the person to confirm it.
@@ -27,14 +27,39 @@ terminal command or page, a host screen.
27
27
 
28
28
  - A capability is reachable through server methods (page 10) and, where a model uses it, through a
29
29
  tool that passes the gate (page 07). Both front ends use the same methods; neither has a private
30
- path to Harness state.
31
- - Required built-in plugin commands and Foundation commands use unprefixed names. Optional built-in
32
- plugin commands and Pi extension commands use `/plugin:<command>`. A bare command, alias or loaded
33
- prompt owns its name; a colliding prefixed command is hidden and rejected. Built-in plugin
34
- categories are documented in page 06 and are not public plugin metadata.
30
+ path to Harness state. Foundation context behavior, including bounded checkpoint restoration after
31
+ compaction (PROP-0033), is shared by terminal and embedded sessions; a host does not reimplement it.
32
+ - Workspace write scope is Harness-owned on both surfaces. Each run pins a canonical root after
33
+ worktree selection; reads retain OS-permission scope, while native file writes and shell
34
+ filesystem effects default to that root; the filesystem root itself is not a valid workspace.
35
+ Native mutations revalidate their canonical target and atomically replace the file under an OS
36
+ write scope. Default Bash scans for external hard-link
37
+ aliases and runs under an OS sandbox that prevents its own processes from creating cross-boundary
38
+ hard links; on macOS the sandbox denies hard-link creation entirely, including within the
39
+ workspace. `workspaceWriteScope: 'outside'` (terminal `!outside <command>`) requests a human-only
40
+ approval before one execution, showing the complete escaped command in scrollable terminal details
41
+ and denying if the terminal is too small. Output never triggers approval or retry. Hosts may
42
+ tighten or deny policy but cannot silently waive the boundary. Only the eligible Full Permission
43
+ switch may answer automatically under its existing restrictions. A same-user peer process outside
44
+ Harness is not controlled by this rule. `workspaceRoot` is additive and legacy run records may
45
+ omit it.
46
+ - Harness core terminal commands use unprefixed names and are registered independently of feature
47
+ plugin command metadata. This includes `/doctor`, `/worktree`, `/trajectory`, `/subagents` and
48
+ `/agents`; `/history` remains session-branch navigation, and `/mcp` remains a Connections shortcut.
49
+ Every plugin-owned terminal command uses `/<owner-prefix>:<command>`, for required and optional
50
+ built-ins and Pi extensions alike. Built-in prefixes are stable registry metadata; Pi package
51
+ extensions use a normalized package name or the package's `harness.commandPrefix`, and loose
52
+ extensions use their normalized extension name. Prefix collisions and duplicate commands within
53
+ one owner are rejected with owner diagnostics. Built-in plugin modules may declare a default
54
+ command for their namespace root. The former owner-qualified and `/plugin:<command>` forms of
55
+ promoted Harness commands remain hidden compatibility aliases for one release. Prompt templates
56
+ and `/skill:<name>` keep their existing forms. Built-in plugin categories remain documented in
57
+ page 06, not public metadata.
35
58
  - A capability may be limited to a run mode when Harness resolves the callable set; the register
36
59
  records that restriction for both surfaces. `ask_user` is available only in attended plan runs,
37
- and the terminal's `ask_user_question` alias follows the same restriction.
60
+ with no terminal-only question alias (PROP-0040). Plan-mode delegation
61
+ is limited to named profiles whose effective tool and plugin allowlists qualify for read-only
62
+ research; both terminal and host use the same gate and child-capability narrowing.
38
63
  - Question choice sketches use one optional validated event contract: up to 80 printable ASCII
39
64
  columns by 24 LF-separated lines (1,943 bytes maximum; page 02). Both surfaces use the same
40
65
  literal ASCII source, persisted unchanged, in one shared preview with access to overflow; neither
@@ -56,6 +81,27 @@ terminal command or page, a host screen.
56
81
  - Side questions use one Harness service on both surfaces. Each caller supplies its visible
57
82
  transcript projection; Harness adds the parent run's effective prompt and public events, then
58
83
  starts an independent tool-free run.
84
+ - PROP-0040 defines one shared launch/command contract: `Launch.standard`, `commands.list`,
85
+ saved-history controls and tracked `SessionRunInput.command` runs. Hosts present these rather
86
+ than approximating navigation with a fork. A listed command or handler is not proof of acceptance.
87
+ The current host `/session` picker reads a host-owned session catalog rather than a shared Harness
88
+ catalog (PAR-008), and read-only command pages do not stand in for unsupported management methods.
89
+ The register records command routes individually; parity remains open until each route passes
90
+ acceptance.
91
+ - Read-only `get_capabilities` is implemented in structured sessions and the terminal. Structured
92
+ sessions report tools filtered by the run's frozen capability set and actual registered schemas;
93
+ the terminal reports Pi's active tools and schemas, applying its plan-mode allowed set when active.
94
+ It is in the shared plan-mode allowlist and grants no permission. Hosts use this Harness tool rather
95
+ than defining a duplicate.
96
+ - `browser/contracts.ts` owns shared backend-neutral browser schemas. A native WebKit host backend
97
+ still lacks accessibility snapshots and snapshot-ref interactions, browser screenshots, waits and
98
+ keyboard input; unsupported calls return an explicit refusal before effects and do not trigger an
99
+ automatic backend switch (page 13, PAR-015).
100
+ - Shared interactive defaults, profile adoption, credentials and a cross-surface session catalog
101
+ remain in progress under PROP-0040. Existing pins and explicit profile choices win; adoption must
102
+ not merge stores, copy credentials or silently downgrade the secret backend.
103
+ - `PluginDescriptor.surfaces` / `Plugin.surfaces` optionally describe a host preference, terminal
104
+ fallback/unavailability and reason. This labels add-ons, not plugin categories or permissions.
59
105
  - The register of capabilities and their surfaces is [`docs/parity.md`](../parity.md). It is
60
106
  descriptive; the invariants below are binding.
61
107
  - A host keeps its own register of host-only surfaces. The Harness names no host surface.
@@ -86,4 +132,5 @@ None of its own. Each capability stores its data as its own page says.
86
132
  `src/harness.ts` (services), `src/world/world.ts` (world parts), `src/plugins/builtin.ts`
87
133
  (first-party descriptors), `src/terminal/` (terminal surfaces), `src/server/methods.ts` (methods).
88
134
  Decision: [PROP-0015](proposals/PROP-0015-one-capability-surface.md),
89
- [ADR-0017](../decisions/ADR-0017-one-capability-surface.md).
135
+ [ADR-0017](../decisions/ADR-0017-one-capability-surface.md),
136
+ [ADR-0036](../decisions/ADR-0036-workspace-write-operation-boundary.md).
@@ -1,6 +1,6 @@
1
1
  # Harness architecture specification
2
2
 
3
- **Status:** binding. **As of:** 2026-10-24, checked against `src/` on branch `release/1.0.0`.
3
+ **Status:** binding. **As of:** 2026-10-26, checked against `src/` on branch `release/1.0.0`.
4
4
 
5
5
  This folder is the core specification of `@xingular/harness`. Code must conform to it. A change
6
6
  that would alter what it says needs a proposal the user approves first (see
@@ -123,3 +123,5 @@ proposal, or a correction to the other document).
123
123
  | D-14 | 12-storage layout | Page 12 lists browser settings under `<state dir>/browser/`, while `browser/config.ts` stores configuration in `<state dir>/browser.json`; browser profiles and downloads use the `browser/` directory. The layout needs an owner decision before either side is changed. |
124
124
  | D-15 | Terminal sketch verification status — resolved 2026-10-24 by PROP-0028 | Pages 11 and 14 and `terminal-tui.md` said connected-sketch implementation verification was pending, while retained PTY evidence and PAR-014 recorded it as verified. Updated the docs to distinguish deterministic fixture verification from owner visual acceptance, every-emulator rendering and live-model sketch quality. |
125
125
  | D-16 | Terminal slash-command highlighting — resolved 2026-10-24 | `terminal-tui.md` specified accent coloring for recognized leading commands while typing, but the OpenTUI composer only styled attachment labels. User-message bubbles also lacked command-token styling. The renderer now uses the active command registry for both surfaces. |
126
+ | D-17 | `PROP-0039`, `ADR-0036`, `INV-GATE-3/8` | Implementation is in progress in the 1.2.0 worktree: native writes revalidate canonical targets and atomically replace files under an OS write scope; default Bash fails closed on external hard-link aliases and runs in a sandbox that prevents new cross-boundary links; outside-scope Bash requires approval before one execution and command output never triggers approval or replay. The filesystem root is rejected as a workspace, and approval details expose the complete command. Launcher selection uses fixed system paths. Same-user peer processes remain outside this boundary. Focused macOS tests, full Harness CI, App compatibility checks, and PTY verification at 40x20, 80x24, and 100x30 pass. Linux sandbox integration remains unverified locally because this Darwin host has no `bwrap` and Colima is stopped; both CI workflows now install bubblewrap and `scripts/ci.sh` fails if it is absent, but an Ubuntu run is still required. No candidate, tag, or publication exists. |
127
+ | D-18 | `core/index.ts` migration 5 | The `run_stats` table has no writer in the Harness. PROP-0042's `usage_requests` ledger is the analytics source; whether to drop the older table is open. |
@@ -3,7 +3,7 @@ id: PROP-0017
3
3
  title: Page annotations as a Harness capability, captured in the headed browser or a host's web view
4
4
  status: Implemented
5
5
  created: 2026-10-23
6
- updated: 2026-10-23
6
+ updated: 2026-10-25
7
7
  affects: [INV-STO-3, INV-SRV-1, 07-tools-and-gate.md, 10-server-protocol.md, 11-terminal.md, 12-storage.md, 13-world-tools.md, 14-surfaces-and-parity.md]
8
8
  ---
9
9
 
@@ -41,7 +41,11 @@ grants, history }`, the format of the App store, so existing notes import unchan
41
41
  - `source`: `{ url, title, capturedAt }` plus an optional opaque `surface` string from the capturing
42
42
  front end.
43
43
  - `target`: `page`, `text { exact, prefix, suffix, start, end }` (Unicode code points over
44
- normalised page text), or `element { tag, role, name, text, context, selector }`.
44
+ normalised page text), or `element { tag, role, name, text, context, selector }`. Element capture
45
+ prefers role/name/text; when an otherwise empty visual element has no semantic label, it may use
46
+ a specific CSS selector (ID, test ID, classes, and a structural path when needed), never a bare
47
+ tag. Resolution uses that selector only when it identifies one allowed element and the captured
48
+ semantics/context still agree; stale or duplicate targets remain unresolved or ambiguous.
45
49
  - `status`: `open` or `resolved`. `version` increases on every change; update and delete name the
46
50
  version they expect (compare-and-swap). The last 100 change receipts are kept, without earlier
47
51
  bodies. At most 10,000 notes.
@@ -51,7 +51,7 @@ A note gains an optional `delivery`:
51
51
  - ⌘/Ctrl+Enter is Proceed now; with Shift it is queue.
52
52
  - Everything is drawn in a closed shadow root.
53
53
  - The page never receives the press or click on what is inspected, nor anything typed in the popover.
54
- - Inputs, secrets, `[data-sensitive]` elements, frames, shadow roots and custom elements are never picked, as in PROP-0017.
54
+ - Inputs, secrets, `[data-sensitive]` elements, frames, shadow roots and custom elements are never picked, as in PROP-0017. (Open shadow roots and readable custom elements became pickable in [PROP-0038](PROP-0038-annotate-open-shadow-roots.md).)
55
55
  - The inspector stays on after each comment.
56
56
  - A new operation, `next`, returns `{ active, documentId, url, title, result }`, one comment at a time.
57
57
  - `toast` shows a short status in the page.