@xingular/harness 1.3.1-unstable.2 → 1.3.1-unstable.3

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 (76) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +1 -1
  3. package/dist/cli.js +1217 -701
  4. package/dist/sdk/computer/screenshot.d.ts +32 -0
  5. package/dist/sdk/computer/screenshot.js +35 -0
  6. package/dist/sdk/context/attribution.d.ts +18 -0
  7. package/dist/sdk/context/attribution.js +295 -0
  8. package/dist/sdk/context/types.d.ts +94 -0
  9. package/dist/sdk/context/types.js +10 -0
  10. package/dist/sdk/index.d.ts +2 -1
  11. package/dist/sdk/index.js +2 -1
  12. package/dist/sdk/plugins/builtin.js +1 -0
  13. package/dist/sdk/prompt/compile.d.ts +42 -2
  14. package/dist/sdk/prompt/compile.js +87 -15
  15. package/dist/sdk/server/methods.js +3 -0
  16. package/dist/sdk/sessions/contracts.d.ts +4 -0
  17. package/dist/sdk/sessions/contracts.js +2 -0
  18. package/dist/sdk/sessions/runner.d.ts +10 -0
  19. package/dist/sdk/sessions/runner.js +17 -0
  20. package/dist/sdk/sessions/subagentService.d.ts +11 -1
  21. package/dist/sdk/sessions/subagentService.js +120 -38
  22. package/dist/sdk/sessions/subagents.d.ts +31 -0
  23. package/dist/sdk/sessions/subagents.js +45 -0
  24. package/dist/sdk/terminal/client.d.ts +1 -1
  25. package/dist/sdk/terminal/client.js +13 -6
  26. package/dist/sdk/terminal/control.d.ts +1 -1
  27. package/dist/sdk/terminal/control.js +2 -2
  28. package/dist/sdk/terminal/controller.d.ts +9 -0
  29. package/dist/sdk/terminal/controller.js +48 -11
  30. package/dist/sdk/terminal/piExtension.js +35 -1
  31. package/dist/sdk/terminal/terminals.d.ts +2 -0
  32. package/dist/sdk/terminal/terminals.js +11 -0
  33. package/dist/sdk/terminal/tui/commands.d.ts +1 -2
  34. package/dist/sdk/terminal/tui/commands.js +1 -4
  35. package/dist/sdk/terminal/tui/dialogs.d.ts +3 -2
  36. package/dist/sdk/terminal/tui/dialogs.js +4 -3
  37. package/dist/sdk/terminal/tui/keybindings.d.ts +1 -1
  38. package/dist/sdk/terminal/tui/keybindings.js +1 -1
  39. package/dist/sdk/terminal/tui/modeChoices.d.ts +3 -2
  40. package/dist/sdk/terminal/tui/modeChoices.js +4 -4
  41. package/dist/sdk/terminal/tui/mouseReport.d.ts +30 -0
  42. package/dist/sdk/terminal/tui/mouseReport.js +80 -0
  43. package/dist/sdk/terminal/tui/pages/annotatePage.js +3 -2
  44. package/dist/sdk/terminal/tui/pages/browserPage.d.ts +1 -1
  45. package/dist/sdk/terminal/tui/pages/contextPage.d.ts +54 -0
  46. package/dist/sdk/terminal/tui/pages/contextPage.js +393 -0
  47. package/dist/sdk/terminal/tui/pages/trajectoryPage.js +5 -4
  48. package/dist/sdk/terminal/tui/runtime.js +8 -2
  49. package/dist/sdk/terminal/tui/search.js +3 -2
  50. package/dist/sdk/terminal/tui/skillInvocation.d.ts +21 -2
  51. package/dist/sdk/terminal/tui/skillInvocation.js +31 -11
  52. package/dist/sdk/tools/defaultSkills.d.ts +19 -0
  53. package/dist/sdk/tools/defaultSkills.js +27 -1
  54. package/dist/sdk/tools/dispatch.js +8 -9
  55. package/dist/sdk/tools/spawnSubagent.d.ts +1 -1
  56. package/dist/sdk/tools/spawnSubagent.js +4 -2
  57. package/dist/terminal-opentui.js +570 -240
  58. package/dist/terminal-pi.js +142 -28
  59. package/docs/README.md +1 -0
  60. package/docs/architecture/02-sessions-and-runs.md +9 -0
  61. package/docs/architecture/04-prompt.md +13 -4
  62. package/docs/architecture/07-tools-and-gate.md +1 -1
  63. package/docs/architecture/11-terminal.md +12 -2
  64. package/docs/architecture/13-world-tools.md +17 -3
  65. package/docs/architecture/14-surfaces-and-parity.md +3 -3
  66. package/docs/architecture/README.md +1 -0
  67. package/docs/architecture/proposals/PROP-0049-context-attribution.md +382 -0
  68. package/docs/architecture/proposals/README.md +1 -0
  69. package/docs/decisions/ADR-0048-required-skills-from-a-message.md +42 -0
  70. package/docs/decisions/ADR-0050-approved-window-screenshots.md +19 -0
  71. package/docs/decisions/ADR-0051-terminal-decision-wait.md +10 -0
  72. package/docs/decisions/ADR-0052-per-profile-subagent-budgets.md +25 -0
  73. package/docs/parity.md +6 -6
  74. package/docs/releases/1.3.1-unstable.3.md +54 -0
  75. package/docs/terminal-tui.md +28 -15
  76. package/package.json +1 -1
@@ -0,0 +1,382 @@
1
+ ---
2
+ id: PROP-0049
3
+ title: Context attribution as a built-in capability
4
+ status: Proposed
5
+ created: 2026-10-28
6
+ updated: 2026-10-28
7
+ affects: [INV-PRM-6, INV-SRV-1, INV-PAR-1, INV-PAR-2, INV-RUN-7, 04-prompt.md, 10-server-protocol.md, 11-terminal.md, 14-surfaces-and-parity.md, parity.md]
8
+ ---
9
+
10
+ # PROP-0049: Context attribution as a built-in capability
11
+
12
+ ## Problem
13
+
14
+ A person can see that a run is expensive but not where the context goes. The harness already
15
+ records every fact needed to answer it, and no surface shows the answer:
16
+
17
+ - `runs/<id>/context.json` holds the pack the run was given: each section's `title`, `size`
18
+ (characters), `budget`, `cut` and `sources`, plus `toolGroups` and the variant fingerprint
19
+ (`src/prompt/compile.ts`).
20
+ - `runs/<id>/events.jsonl` holds `context.snapshot` — the verbatim `systemPrompt`, the
21
+ `contextUpdate`, and every tool's `{name, description, parameters}` — and `usage.updated`, whose
22
+ `contextTokens` is the provider-measured size of the latest request and `contextWindow` the
23
+ model's limit (`src/sessions/contracts.ts`, emitted by `src/sessions/runner.ts`).
24
+ - `Pi` computes a live `{ tokens, contextWindow, percent }` for the session
25
+ (`getContextUsage()`), and its estimator is `ceil(chars / 4)`
26
+ (`node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js`).
27
+
28
+ What is missing is the attribution: a row per contributor, tokens, share of the window, and a
29
+ stack that reconciles to the measured total. The App shows a reply's context pack as a collapsed
30
+ `<details>` list of character counts (`App/src/modes/shared/ContextPackView.tsx`), and a single
31
+ `Meter` for "context at the end" (`App/src/modes/shared/SessionContextInspector.tsx`). The terminal
32
+ declares `model.contextWindow` on its status band and never renders it
33
+ (`src/terminal/tui/status.ts`). `usage.summary` carries no context fields at all
34
+ (`src/usage/types.ts`).
35
+
36
+ Attribution is something a person uses, so it belongs in the Harness with a terminal surface
37
+ before an App screen (`INV-PAR-1`, `INV-PAR-2`).
38
+
39
+ ## What other harnesses show
40
+
41
+ Evidence is from the checkout in `external/` for open repositories, and from official docs for
42
+ Claude Code, Codex CLI and Gemini CLI. "Third-party" marks a claim read from a user report rather
43
+ than a vendor document.
44
+
45
+ | Harness | Where it shows | Rows / categories | % of window | Visual | Measured vs estimated | Thresholds | Actions from the view |
46
+ |---|---|---|---|---|---|---|---|
47
+ | **Claude Code** | `/context` command in the transcript; ring in the desktop panel; scriptable status line | `System prompt`, `System tools`, `MCP tools`, `Custom agents` (with source), `Memory files`, `Skills`, `Messages`; plus `Autocompact buffer` and `Free space` | Per row and in the header `598.5k / 1M (60%)` | "Colored grid" (docs); a table of tokens + % in the panel (**third-party**, [issue #94436](https://github.com/anthropics/claude-code/issues/94436)) | Estimated per row; header from measured | Ring blue → yellow → red; over-limit banner naming `/compact` | Text suggestions only ("trim CLAUDE.md · 49k per request"); `/usage` can apply some |
48
+ | **Pi** (the engine under this harness) | TUI footer segment | One aggregate: `45.3%/200K (auto)` plus `↑in ↓out R<cache> W<cache> CH<hit>% $cost` (`dist/modes/interactive/components/footer.js:133-200`) | Yes, one decimal | Text, coloured | `getContextUsage()` = measured usage anchored, tail estimated at chars/4 | `>70` warning, `>90` error; `?/window` when unknown | `/compact` |
49
+ | **opencode** | Sidebar block + prompt footer (`external/opencode/packages/tui/src/feature-plugins/sidebar/context.tsx:20-51`, `prompt/footer.tsx:38-55`) | One aggregate: `N tokens`, `N% used`, `$cost` | Rounded integer | Plain text, no bar, no colour | Last provider usage only (`util/session.ts:46-61`) | None | `<leader>c` / `/compact`; auto-compact reserves 15% or 16k |
50
+ | **oh-my-pi (omp)** | Status-line segment system (`external/omp/packages/tui/src/status-line/footer.ts:171-260`) | One aggregate: `45.3%/200K`, per-direction counters, billing | One decimal | Coloured text, segment priorities | Provider-anchored, tail estimated; strict/approximate/upperbound tokenizer modes | Percent **or** absolute tokens, whichever first: 50%/150k, 70%/270k, 90%/500k (`chrome/context-thresholds.ts:6-11`) | `/compact` modes, `/shake` (drop heavy tool results), `/handoff` |
51
+ | **DeepSeek Harness** | Web chat statistics dock; per-turn disclosure (`external/deepseek-harness/packages/client/ui-chat`) | `systemTokens`, `toolsTokens`, `messageTokens` — exactly three (`packages/llm/token-meter/README.md`) | Separate `contextPressure.pressureTokens`; the division is client-side | Not confirmed | Explicit: provider usage reused only on an identical request envelope, else estimated; rows are "approximate composition, never a total" | Compaction at `min(W×0.8, W−O−65536)` | `/compact`; tool-result pruning |
52
+ | **Kimi CLI** | Status bar, right-aligned (`external/kimi-cli/src/kimi_cli/ui/shell/prompt.py:2251`, `soul/__init__.py:80`) | One aggregate: `context: 42.0% (4.2k/10.0k)` | Percent first | Plain text | Last provider usage (lags one request) | Ratio 85% **or** 50k reserve (`soul/compaction.py:59`) | `/compact`, `/clear` |
53
+ | **Orca** | 16px SVG ring in the composer row, click/hover opens a popover (`external/orca/src/.../NativeChatContextUsageRing.tsx`) | Passes the underlying CLI's own categories through verbatim, with a `deferred` flag for on-demand context | Row % only, one decimal; ring clamps, stored value does not | Ring + `Progress` bar in the popover | Three states: `report`, `estimate`, `unknown`; window held separately from used | `CRITICAL_PERCENTAGE = 90` turns the ring destructive | None — read-only |
54
+ | **Codex CLI** | `/status` prints configuration and "remaining context capacity"; `/statusline` picks footer fields (`developers.openai.com/codex/cli/slash-commands`) | No breakdown | Remaining capacity | Configurable footer items: model / context / limits / git / tokens / session | Not documented | Not documented | `/compact` |
55
+ | **Gemini CLI** | `/stats` full-screen report (`external`-free; `docs/reference/commands.md:438-450`) | `session` (duration, tool calls), `model` (token counts, quota), `tools` (per-tool) | Not in the documented output | Text tables | Not documented | Not documented | `/compress` |
56
+
57
+ Distinctive ideas worth keeping, by harness: **Claude Code** — a stack closed by `Autocompact
58
+ buffer` + `Free space`, and the remedy named inside the view. **Pi** — `?` when the window is
59
+ unknown rather than `0%`. **opencode** — one `formatContextUsage` helper shared by every surface, so
60
+ they cannot disagree. **omp** — warn on percent *or* absolute tokens (small windows warn at the
61
+ same absolute size) and a declared drop order for narrow terminals. **DeepSeek** — three separate
62
+ projections (billing, occupancy, composition), never mixed into one number. **Kimi** — percent
63
+ first, `used/total` in parentheses, readable at 30 columns. **Orca** — show the provider's own
64
+ category names instead of inventing a taxonomy, and mark deferred context as deferred. **Codex** —
65
+ the person chooses which fields the always-on strip carries.
66
+
67
+ ## Affected rules
68
+
69
+ - **INV-PRM-6** Each run's pack is recorded beside the run; failing to record it never fails the
70
+ session. Attribution reads that record and adds per-section rendered sizes to it. Recording
71
+ stays best-effort.
72
+ - **INV-SRV-1** `src/server/methods.ts` is the contract for every non-Node host. `context.attribution`
73
+ is additive: no existing method, param or result shape changes.
74
+ - **INV-RUN-7** Token accounting keeps its meaning. `usage.updated.contextTokens` stays the
75
+ provider's figure for the latest request; attribution never rewrites it, and marks every row it
76
+ derived as estimated.
77
+ - **INV-PAR-1 / INV-PAR-2** The capability lands in the Harness with a terminal surface, and the App
78
+ presents the same rows from the same method.
79
+ - **INV-PRM-1** The prompt is still assembled only in `src/prompt/compile.ts`. Attribution measures
80
+ what that compiler produced; it never assembles text.
81
+
82
+ ## Proposal
83
+
84
+ ### 1. The measurement model
85
+
86
+ Three facts are read, and nothing new is written per request:
87
+
88
+ | Fact | Source | Kind |
89
+ |---|---|---|
90
+ | `systemPrompt`, `contextUpdate`, `tools[]` | `context.snapshot` event | recorded verbatim |
91
+ | `contextTokens`, `contextWindow` | latest `usage.updated` event | **measured** by the provider |
92
+ | sections, `toolGroups`, variant, `delivery` | `runs/<id>/context.json` | recorded at compile time |
93
+
94
+ One constant, `CHARS_PER_TOKEN = 4`, the same ratio Pi, opencode, DeepSeek and Kimi use, so a row
95
+ in this view and the terminal's status band never disagree by more than rounding.
96
+
97
+ `compile.render()` records the rendered size of each section (including the `## Title` framing and
98
+ the gaps between sections) and, inside a section, the characters contributed by each `Source`. That
99
+ is the only change to recorded data: it removes the need to re-parse a prompt later, which cannot
100
+ be done safely because a variant section's body may itself contain `## ` headings.
101
+
102
+ Rows are built in this order, and the last one is a subtraction rather than an estimate:
103
+
104
+ 1. **System prompt sections** — each `Section`'s rendered characters ÷ 4, attributed to the group
105
+ that owns it.
106
+ 2. **Tool schemas** — `JSON.stringify({name, description, parameters})` ÷ 4 for each recorded tool,
107
+ grouped by the pack's `toolGroups`; a tool in no group becomes `Other tools`.
108
+ 3. **This request's update** — the recorded `contextUpdate` ÷ 4.
109
+ 4. **Conversation** — `contextTokens − tokens(1..3)`, floored at zero. When the subtraction would go
110
+ negative the prefix rows are scaled down and the view says the prefix estimate exceeded the
111
+ measured total; it never shows a negative row.
112
+ 5. **Compaction reserve** — Pi's `settings.compaction.reserveTokens` (default 16 384) when
113
+ auto-compaction is on.
114
+ 6. **Free space** — `contextWindow − contextTokens − reserve`, so the stack sums to the window with the
115
+ reserve shown as its own row (the reserve is headroom the next request may not use, not free space).
116
+
117
+ The rows therefore always add up to the window, and the number in the header is always the
118
+ provider's. A run with no `usage.updated` yet shows estimated rows and marks the header
119
+ `estimate only`; a session whose window is unknown shows `/ ?` like Pi, never `0%`.
120
+
121
+ ### 2. The row taxonomy: foundation, variant, application, conversation
122
+
123
+ This is where the harness can say something no other harness can. Rows group by *who owns the
124
+ text*, and variant rows say whether they cost every request:
125
+
126
+ | Group | Members | Owner |
127
+ |---|---|---|
128
+ | **Foundation** | Harness identity, tool guidance, tool schemas (History, Wiki, Native, World, Progress, Session, Annotations), Wiki pointer, session state, references, reporting rules | released harness |
129
+ | **Variant c1–c8** | Instructions (c1), Guardrails (c2), Memory (c3), Tool guidance (c4), subagent index (c5), skills index (c6), scripts index (c7) | `variant/`, refinable |
130
+ | **Your application** | Identity, role, working folder, skills, plugin descriptions, connected apps, annotations | the host's sections and tool groups |
131
+ | **Conversation** | Messages and tool results (measured residual), context updates | the run |
132
+ | **Reserved and free** | Compaction reserve, free space | Pi, the model |
133
+
134
+ Two facts make the variant rows actionable: `alwaysOnMax` caps always-on variant text at 24 000
135
+ characters (`src/variant/surface.ts`), and only components with `reach: 'always'` occupy the prompt
136
+ on every request. The view therefore shows a **budget meter per variant row** (`3.6K of 24K`) and a
137
+ separate **on demand** list — skills, subagents, scripts — which are not in this request but will
138
+ cost about *n* tokens when loaded. Claude Code shows a `Skills` row without that distinction; Orca
139
+ has the `deferred` flag but no per-row cost. Showing both is the improvement.
140
+
141
+ The foundation/variant split also gives the view its one obvious action: a variant row deeper than
142
+ the cap, or plainly oversized, links to the Harness screen where it is edited or refined.
143
+
144
+ ### 3. Surfaces
145
+
146
+ **Harness (reference terminal).** A `/context` full-screen page built like `/usage`:
147
+ `src/terminal/tui/pages/contextPage.ts`, registered in `commandPages.ts` as a command page, reading
148
+ `context.attribution`. It also fills the gap in `src/terminal/tui/status.ts`, whose band declares
149
+ `model.contextWindow` and never paints it: the band gains `45.3%/200K` in the same place Pi puts it.
150
+
151
+ **Server.** One additive method:
152
+
153
+ ```
154
+ context.attribution { runId }
155
+ → {
156
+ runId, model, provider, window, used, share, // numbers the provider reported
157
+ source: 'measured' | 'estimate', // which of the above is trusted
158
+ compaction: { enabled, reserveTokens, thresholdTokens } | null,
159
+ rows: [{ id, title, group, tokens, chars, share, always, estimated, cut?,
160
+ budget?, sources?, tools?, note? }],
161
+ onDemand: [{ id, title, tokens, items }],
162
+ totals: { foundation, variant, application, conversation, reserved },
163
+ recordedAt
164
+ }
165
+ ```
166
+
167
+ `context.runPack` keeps returning the text; the App already calls it from
168
+ `App/src/bridge/commands.ts:642`, and the new page uses it to show a row's actual prompt slice.
169
+
170
+ **App.** A full-width page, not a sidebar tab — the sidebar is ~360–420 px wide, and a 6-column
171
+ accounting sheet needs the main pane:
172
+
173
+ - `ContextAttributionPage` in the main pane, opened from (a) the Context view's header, (b) a
174
+ compact meter in the composer footer, (c) the command palette. It uses the same host pattern as
175
+ agent configuration: a header strip with a close control
176
+ (`App/src/modes/solo/SoloWorkspace.tsx:263-265`).
177
+ - The compact meter reuses `Meter` (`variant="ring"`) from `@xingular/ui` and opens the page.
178
+ - The stack and its legend reuse `SegmentedBar` from `@xingular/ui/ui/charts`, whose `Segment` type
179
+ is already `{ key, label, value, color, display, share, title, recorded }` — exactly this page.
180
+ - A cache strip uses `CacheUsageBadge` and `formatTokens` from
181
+ `App/src/modes/shared/usage/usageModel.ts`.
182
+
183
+ ### 4. Sketches
184
+
185
+ A rendered version of these sketches, in the dark theme with the shared tokens, is
186
+ [`outputs/context-attribution-sketch.html`](../../../../outputs/context-attribution-sketch.html)
187
+ (open it in a browser; four tabs, switchable). All three options use one dataset, and the rows add
188
+ to the provider's measured total.
189
+
190
+ **Option A — accounting sheet (Claude Code's shape, this harness's rows).** Recommended.
191
+
192
+ ```
193
+ ┌─ Context ────────────────────────────────────────────────────────────────────────────────┐
194
+ │ claude-sonnet-4-6 · 1M window · run r-8f21 auto-compact at 983.6K [x] │
195
+ │ │
196
+ │ 183.4K / 1M · 18.3% measured · provider-reported │
197
+ │ ████████▓▓▓▓▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
198
+ │ ├ foundation 13.1K ┤├ variant 18.4K ┤├ app 9.8K ┤├ conversation 142.1K ┤ │
199
+ │ │
200
+ │ ALWAYS IN CONTEXT TOKENS SHARE OF BUDGET │
201
+ │ v Foundation · harness 13.1K 1.3% │
202
+ │ Harness identity 29 0.0% 29 / 1.0K chars │
203
+ │ Tool guidance 1.9K 0.2% 1.9K / 8.0K chars │
204
+ │ Tool schemas · 34 tools 9.4K 0.9% │
205
+ │ History 3 · Wiki 6 · Native 6 · World 5 · Connections 7 · Other 7 │
206
+ │ Wiki pointer 1.1K 0.1% 1.1K / 2.5K chars │
207
+ │ Reporting rules 712 0.1% 712 / 2.0K chars │
208
+ │ Session state · references 0 0.0% no update this request │
209
+ │ v Variant · c3 · 9a1f 18.4K 1.8% │
210
+ │ Instructions (c1) 8.2K 0.8% 8.2K / 24K chars │
211
+ │ Guardrails (c2) 4.1K 0.4% │
212
+ │ Memory (c3) 3.6K 0.4% │
213
+ │ Tool guidance (c4) 2.5K 0.3% │
214
+ │ v Your application 9.8K 1.0% │
215
+ │ Identity · Role · Working folder 2.4K 0.2% │
216
+ │ Skills index · 12 skills 1.1K 0.1% │
217
+ │ Connected apps · 7 tools 6.3K 0.6% │
218
+ │ v Conversation 142.1K 14.2% measured by subtraction │
219
+ │ Messages and tool results 142.1K 14.2% │
220
+ │ │
221
+ │ RESERVED AND FREE │
222
+ │ Compaction reserve 16.4K 1.6% Pi, auto-compaction │
223
+ │ Free space 800.2K 80.0% │
224
+ │ │
225
+ │ ON DEMAND · not in this request when loaded │
226
+ │ Skills c6 · 12 available ~ 4.2K per skill │
227
+ │ Subagents c5 · 3 available own context each │
228
+ │ │
229
+ │ Rows are estimated from recorded character counts (÷ 4) and reconciled to │
230
+ │ the provider measured 183.4K; Conversation is the remainder, so the rows add │
231
+ │ to the whole. │
232
+ └──────────────────────────────────────────────────────────────────────────────────────────┘
233
+ ```
234
+
235
+ **Option B — ledger plus detail pane.** The same rows on the left; selecting one shows what it is
236
+ on the right: the prompt text, the section's sources and revision, and the actions that apply.
237
+
238
+ ```
239
+ ┌───────────────────────────────────────────────┬──────────────────────────────────────────┐
240
+ │ CONTEXT ██████████░░░░░░░░░░ 18.3% │ Instructions (c1) │
241
+ │ 183.4K of 1M · sonnet-4-6 · 34 tools │ ───────────────────────────────────── │
242
+ │ │ 8.2K tokens · 0.8% of the window │
243
+ │FOUNDATION 13.1K 1.3%│ always in context · 8.2K / 24K chars │
244
+ │ Harness identity 29 0.0%│ variant c3 · fingerprint 9a1f │
245
+ │ Tool schemas 9.4K 0.9%│ source systemprompt.md │
246
+ │ Tool guidance 1.9K 0.2%│ +----------------------------------+ │
247
+ │ Wiki pointer 1.1K 0.1%│ | You are the person's assistant, | │
248
+ │ Reporting 712 0.1%│ | working with them directly in | │
249
+ │VARIANT · c3 18.4K 1.8%│ | this session: research, writing | │
250
+ │ Instructions c1 8.2K 0.8%│ | and coding in its working folder | │
251
+ │ Guardrails c2 4.1K 0.4%│ +----------------------------------+ │
252
+ │ Memory c3 3.6K 0.4%│ [Open in Harness] [Refine this] │
253
+ │ Tool guidance c4 2.5K 0.3%│ │
254
+ │YOUR APPLICATION 9.8K 1.0%│ │
255
+ │CONVERSATION 142.1K 14.2%│ │
256
+ │RESERVED 16.4K 1.6%│ │
257
+ │FREE 800.2K 80.0%│ │
258
+ └───────────────────────────────────────────────┴──────────────────────────────────────────┘
259
+ ```
260
+
261
+ **Option C — growth over the session.** A second tab for the same page: the stack at each request,
262
+ so "what grew" is visible. Compaction markers come from `compaction.ended` (`tokensBefore`,
263
+ `tokensAfter`), which the run already records.
264
+
265
+ ```
266
+ ┌ Growth ───────────────────────────────────────────────────────────────────────────────────┐
267
+ │ 1M ┤│
268
+ │ │ ╭── 44.2K│
269
+ │ 20% ┤ ╭──╮ ╭──╮ ╭───╮ ╭────╮ ╭─────╯│
270
+ │ │ ╭──╮ │ │ │ │ │ │ │ │ │ │
271
+ │ 0% ┼──────┴──┴────┴──┴───┴──┴───┴───┴───┴────┴───┴─────┬─────────────── requests →│
272
+ │ r1 r2 r3 r4 r5 r6 r7 r8 ⧗ compacted 112K → 31K│
273
+ │ │
274
+ │ foundation ▓ variant ▒ application ░ conversation █ each segment a group│
275
+ └──────────────────────────────────────────────────────────────────────────────────────────┘
276
+ ```
277
+
278
+ **The compact meter** — one always-visible readout, everywhere, opening the page:
279
+
280
+ ```
281
+ App composer footer Terminal status band
282
+ ┌────────────────────────────────────┐ ~/project (main) · account · sonnet-4-6
283
+ │ ▸ model sonnet-4-6 ◔ 18% ↑ ⏎ │ ↑12.1k ↓3.4k R84.9k 18.3%/1M
284
+ └────────────────────────────────────┘ └─ click / /context ─────────────────
285
+ ring colour: 18% data-1 · 80% warning · 95% destructive
286
+ ```
287
+
288
+ **The terminal page** (`/context`), for reference:
289
+
290
+ ```
291
+ Context sonnet-4-6 · 1M 18.3% used · measured
292
+ ████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
293
+ foundation 13.1K 1.3% ██
294
+ variant 18.4K 1.8% █
295
+ your app 9.8K 1.0% ▌
296
+ conversation 142.1K 14.2% ██████
297
+ reserved 16.4K 1.6% █
298
+ free 800.2K 80.0% (dim)
299
+ j/k move · enter open · v variant · c compact · r refresh · q close
300
+ ```
301
+
302
+ ### 5. Honesty rules the view must keep
303
+
304
+ Taken from the mistakes of the harnesses above, and from `INV-RUN-7`:
305
+
306
+ 1. The header number is the provider's, or the word `estimate`.
307
+ 2. `Conversation` is a subtraction, never a guess, and is labelled `measured by subtraction`.
308
+ 3. Free space is `window − measured − reserve`, never `window − listed rows` — the Claude Code #94436
309
+ report is exactly that bug.
310
+ 4. A row that could not be measured is `not recorded`, not `0`.
311
+ 5. An unknown window is `/?`, never `0%`.
312
+ 6. Rows are `approx` in the copy, and the page states the ratio once.
313
+ 7. No number is recomputed from a provider's figure in a way that changes its meaning; a scaled row
314
+ is marked scaled.
315
+
316
+ ### 6. What this proposal does not do
317
+
318
+ - No tokenizer. `chars / 4` is the house constant; a real tokenizer would need the provider's
319
+ encoding per model and would still not reconcile with the measured total.
320
+ - No new storage. Everything is read from files the harness already writes, plus the rendered sizes
321
+ added to the pack (`INV-PRM-6`, `INV-MEM-5` untouched).
322
+ - No new writer for run records or the ledger.
323
+ - No cost attribution per row in the first surface. Cost per request exists (`pricing.ts`) and can
324
+ be added as a column later without changing the model.
325
+
326
+ ## Alternatives
327
+
328
+ - **Do nothing.** The App already shows a pack's character counts and one meter. The gap is the
329
+ share of the window per contributor, the free space, and the variant's always-on cost — the three
330
+ questions a person actually asks.
331
+ - **A tab in the existing sidebar Context view.** Rejected: the sidebar is too narrow for a six
332
+ column table, and the request is explicitly a full-width page.
333
+ - **Client-side computation in the App.** Rejected by `INV-PAR-1`: the terminal needs the same rows,
334
+ and two implementations of the subtraction rule would drift.
335
+ - **Reuse `usage.summary`.** Rejected: that is a period aggregate over the ledger. Attribution is
336
+ one request's composition, and `UsageTotals` has no context fields.
337
+ - **Full tokenizer.** Rejected: see above; it would also make the view disagree with Pi's own
338
+ footer, which is the number a person already sees.
339
+
340
+ ## Consequences
341
+
342
+ - **Applications.** One new method, additive. A host that shows the page reads
343
+ `context.attribution`; a host that does not is unaffected.
344
+ - **Stored data.** `runs/<id>/context.json` gains a `rendered` size per section (and per source).
345
+ Older packs lack it; the view falls back to `size` and marks the section `approximate`.
346
+ - **Protocol.** One new method name and result shape; no `PROTOCOL_VERSION` change.
347
+ - **Spec.** `04-prompt.md` gains the recorded rendered sizes; `11-terminal.md` gains `/context`;
348
+ `14-surfaces-and-parity.md` and `parity.md` gain the row.
349
+
350
+ ## Migration
351
+
352
+ Nothing to migrate. Packs recorded before this change stay readable; the only difference is the
353
+ `approximate` marker on rows whose rendered size was not recorded. The capability can be removed by
354
+ deleting the page, the method and the extra pack field; no data depends on it.
355
+
356
+ ## Checks
357
+
358
+ - Harness: `npm run typecheck`, `npm test`, `npm run prove`.
359
+ - New unit tests: the reconciliation (rows sum to `contextTokens`), the negative-prefix case, a pack
360
+ with no `usage.updated`, a pack with no `delivery`, and a run with unknown `contextWindow`.
361
+ - Terminal: a page test in the style of `usagePage.test.ts`.
362
+ - App: `npm run typecheck`, `npm test`, `npm run check:layers`, `npm run check:ui`, and
363
+ `cd src-tauri && cargo test`.
364
+ - A visual check of both surfaces against one real run, comparing the header figure with the
365
+ terminal status band and with Pi's own footer.
366
+
367
+ ## Open questions
368
+
369
+ 1. **Placement.** Full-width page in the main pane (recommended), a new workspace view tab that
370
+ renders full width, or a tab beside `Usage` in the sidebar? The sketches assume the first.
371
+ 2. **Shape.** Option A (accounting sheet), A plus Option B's detail pane on wide windows, or all
372
+ three as tabs of one page?
373
+ 3. **Rows.** Group by owner (foundation / variant / application / conversation), as proposed, or by
374
+ kind (`System prompt`, `Tools`, `Skills`, `Memory`, `Messages`) as Claude Code does? Owner
375
+ grouping is what makes the variant actionable; kind grouping is what a person coming from Claude
376
+ Code expects.
377
+ 4. **Cost per row.** Show `cost per request` beside tokens from the first version, or keep the view
378
+ token-only?
379
+ 5. **Live updates.** Refresh on every `usage.updated` while a reply streams, or on open and on
380
+ `completed` only?
381
+ 6. **The compact meter.** Add the ring to the App composer footer, the terminal status band, or
382
+ both? Pi's footer already shows `45.3%/200K` and this harness's band shows neither.
@@ -78,6 +78,7 @@ it to the index and update its status as the work progresses.
78
78
  | [PROP-0046](PROP-0046-progress-aware-stall-guards.md) | Progress-aware stall guards and bounded plan research children | Implemented; ADR-0039 | INV-RUN-13, 02-sessions-and-runs.md, PROP-0041, terminal-tui.md, ADR-0039 | 2026-10-27 |
79
79
  | [PROP-0047](PROP-0047-plugins-hub-separation.md) | Separate local plugin management from Community Hub discovery | Implemented; ADR-0041 | INV-MCP-4, INV-TUI-2/4, INV-PAR-1/2, 06, 09, 10, 11, 14, terminal-tui.md, parity.md | 2026-10-27 |
80
80
  | [PROP-0048](PROP-0048-simple-annotate.md) | One annotate page, two actions, and easy app attach | Implemented; ADR-0044 | PROP-0018, 07, 10, 11, 13, terminal-tui.md, parity.md | 2026-10-27 |
81
+ | [PROP-0049](PROP-0049-context-attribution.md) | Context attribution: what fills the context window, by foundation, variant and application | Proposed | INV-PRM-6, INV-SRV-1, INV-PAR-1/2, INV-RUN-7, 04, 10, 11, 14, parity.md | 2026-10-28 |
81
82
 
82
83
  Numbers are four digits, start at `0001`, and are never reused.
83
84
 
@@ -0,0 +1,42 @@
1
+ ---
2
+ id: ADR-0048
3
+ title: A skill named anywhere in a message is required for that request
4
+ status: Accepted
5
+ created: 2026-11-20
6
+ ---
7
+
8
+ # ADR-0048: A skill named anywhere in a message is required for that request
9
+
10
+ ## Decision
11
+
12
+ - `$name` is a skill invocation at any word start in a message, not only as its first token. The
13
+ composer's palette opens at the caret and replaces only the token being typed; the rest of the
14
+ draft and its caret stay in place.
15
+ - Every `$name` that names a skill the session can load makes that skill required for the request.
16
+ The named skills travel as `Launch.requiredSkills`, and the prompt compiler renders them as the
17
+ dynamic `required-skills` section: the skill's text from the pinned variant, the directive to
18
+ follow it, and an honest line for a name the variant does not have. The shipped
19
+ `plugin-authoring` skill fills in when the variant has none.
20
+ - The section belongs to the request. `sessionLaunch` resends it like `references`, even when it is
21
+ identical to the previous snapshot.
22
+ - The terminal builds the same text with `requiredSkillsText` from Pi's loaded skills and appends it
23
+ as a hidden `xingular-required-skills` custom message. It no longer rewrites a leading `$name` to
24
+ `/skill:name`; the person's message reaches the model unchanged and the requirement is enforced at
25
+ every position.
26
+ - A required skill changes no tool and no permission: the run's callable set and the gate are
27
+ untouched. It is a prompt-level requirement, not a capability narrowing.
28
+
29
+ ## Rationale
30
+
31
+ Pi expands only a leading `/skill:name`, so a mid-message skill was plain text and had no effect.
32
+ Moving the requirement into the request-scoped prompt text makes the same promise at every position,
33
+ keeps the stable prefix (and therefore the provider cache) intact, and gives the terminal and a host
34
+ one shared wording through `requiredSkillsText`.
35
+
36
+ ## Consequences
37
+
38
+ A leading `$name` no longer depends on Pi's `/skill:name` expansion. The skill's text is delivered
39
+ once per request that names it, so repeating the same skill in several turns repeats its text in
40
+ native history; the model sees the most recent copy. The App's composer already inserts `$` chips
41
+ anywhere; its reference path now sends skills as `requiredSkills` rather than as pinned notes.
42
+ Skills that a variant does not contain are reported as unavailable rather than silently dropped.
@@ -0,0 +1,19 @@
1
+ # ADR-0050: Approved-window screenshot contract
2
+
3
+ ## Decision
4
+
5
+ A host may supply `computer_screenshot` through the existing tool-provider extension point using
6
+ the Harness contract in `src/computer/screenshot.ts`. The backend must capture one currently
7
+ approved window and exclude the desktop, other windows and overlays, including overlapping ones.
8
+ If it cannot guarantee that scope it refuses without returning pixels; it never silently captures a
9
+ display instead. The adapter is opt-in, goes through the ordinary tool gate, and does not make
10
+ capture plan-mode safe. A host that also offers coordinate-based interactions interprets their
11
+ coordinates relative to the returned image pixels.
12
+
13
+ ## Consequences
14
+
15
+ Browser screenshots remain a separate capability with their own tab isolation. The contract cannot
16
+ verify native backend isolation; each host must implement and test it. The existing macOS display
17
+ capture has not adopted this contract and is not evidence of approved-window isolation. The
18
+ standalone terminal has no such backend. The contract adds no automatic provider registration or
19
+ new extension point.
@@ -0,0 +1,10 @@
1
+ # ADR-0051: Revisioned terminal decision waits
2
+
3
+ The terminal needs timely approval and question updates while avoiding a control request every
4
+ 100 ms in every idle session. The controller owns the pending inbox, so it exposes a private,
5
+ control-key-protected revisioned snapshot with a bounded wait. Every inbox mutation wakes waiters;
6
+ the frontend resynchronizes on reconnect. The existing immediate snapshot remains available.
7
+
8
+ The wait uses a separate socket request, so it does not block other terminal control calls.
9
+ Waits time out after 20 seconds and are woken on controller disposal. Client shutdown cancels its
10
+ request. No persisted state or public server protocol changes.
@@ -0,0 +1,25 @@
1
+ # ADR-0052: Per-profile and total subagent budgets
2
+
3
+ A run could start any number of children: `maxDepth` bounded nesting only. Run `90906235`
4
+ ("Implement the plan: Safe screenshot capability", 2026-10-28) started 11 children in nine
5
+ minutes for a five-step plan — three spawns before the parent had read a file, five children that
6
+ returned no change, and two tasks retried as fresh children. The shape of delegation was left to
7
+ the prompt.
8
+
9
+ A run now starts only a bounded number of distinct child conversations. Foundation defaults: four
10
+ children in total, two `worker`, three `explorer`, one of any other named profile and one with no
11
+ profile. A host overrides them through `SessionRunInput.subagents` (`maxChildren`, `maxByProfile`,
12
+ `maxOther`, `maxUnprofiled`), so the terminal and the App receive the same defaults; every value
13
+ is an integer clamped to 0–50.
14
+
15
+ `Subagents.spawn` counts the run's recorded children in `subagents.json`, the children of the
16
+ parent runs it was continued from (`resumedFromRunId`), and slots reserved by concurrent calls. It
17
+ deduplicates a continuation to its first link, so continuing a stopped child uses no new slot,
18
+ while a child whose startup failed still counts. A refused spawn returns an `invalid_request`
19
+ tool result telling the model to do the work itself; it creates no run. `continue` bypasses the
20
+ check because it is the same child.
21
+
22
+ The budget is per run, not per conversation, so a long chat is not starved. It lives in the
23
+ foundation because it bounds what every application built on the harness may spend, and changing
24
+ it needs a release. A host that legitimately needs more children raises the limits for its
25
+ session; the spawn tool's description states the effective numbers.
package/docs/parity.md CHANGED
@@ -30,19 +30,19 @@ implemented is not a parity claim; acceptance is tracked per command.
30
30
  | Approvals and standing approvals | `sessions/approvals.ts` | `approvals.*` | decision dock, `/permissions` | approvals dock, settings | parity | — |
31
31
  | Opening a link | `core/links.ts` | `links.open`; `HarnessOptions.linkOpener` (ADR-0047) | hover shows the hand pointer and recolours the link, Ctrl+click (the primary click on macOS, whose ⌘ the mouse protocol cannot report) opens web, `mailto:`, file and folder targets with the system handler, a refused link posts a notice | ⌘-click on macOS and Ctrl+click elsewhere opens with the system handler, a plain click keeps the sidebar and falls back to the system for what it cannot show, `a:hover` uses `--link-hover` | implemented; acceptance not certified | — |
32
32
  | Questions (`ask_user`) | `tools/askUser.ts` | shared `ask_user` tool | plan-mode dock: strict aligned columns, bounded body scroll, 80×24 source sketch viewport; connected structural projection with high-confidence boundary normalization (PROP-0026, PROP-0028) | plan-mode form accepts the same 80×24 ASCII source contract with literal preview and accessible overflow; no typography change required | parity; terminal projection verified in isolated fixtures | — |
33
- | Subagents | `sessions/subagentService.ts` | `spawn_subagent` (requires `title`, PROP-0030; `continue` resumes a stopped child, ADR-0043), `subagents.*`; plan mode allows only named eligible read-only research profiles | `/subagents` page with the `main` row pinned first, and live rows under the status line (mark, profile, parent-set title, `n of m tasks`), both rebuilt from saved runs when a conversation reopens; plan-mode research delegation uses the same Harness gate | reply trajectory; plan-mode research delegation uses the same Harness gate | parity | — |
33
+ | Subagents | `sessions/subagentService.ts` | `spawn_subagent` (requires `title`, PROP-0030; `continue` resumes a stopped child, ADR-0043), `subagents.*`; plan mode allows only named eligible read-only research profiles; per-profile and total child budgets enforced in `Subagents.spawn` (ADR-0052), with the same refusal result in both surfaces | `/subagents` page with the `main` row pinned first, and live rows under the status line (mark, profile, parent-set title, `n of m tasks`), both rebuilt from saved runs when a conversation reopens; plan-mode research delegation uses the same Harness gate | reply trajectory; plan-mode research delegation uses the same Harness gate | parity | — |
34
34
  | Agent profiles | `agents/profiles.ts` | `spawn_subagent { agent }`, `agents.list/get/create/remove`; terminal `terminal.agents.*`; package profiles through `Loading.profilePaths` | `/agents` page: browse, create, edit in `$EDITOR`, copy, delete; package profiles listed under "From a plugin" and switched on `/plugins` | Solo `/agents` lists profiles; profile details and management are not wired | partial | PAR-010 |
35
35
  | Run todo list | `tools/todo.ts` | `todo_write`, `runs.todo` (`runs.checklist` deprecated); terminal `todo` telemetry | registered in terminal sessions and their subagents; each write shows as a todo block in the transcript; `/todo` shows the current list; a subagent row shows its `n of m tasks` | `/todo` shows the current list on demand; each write is not rendered inline | terminal done; App shows it on demand | PAR-001 |
36
36
  | Continuation of a stopped run | `sessions/continuation.ts` | `continueFrom`, `runs.continuation` | not offered (`/resume` reopens history only) | not offered | planned | PAR-003 |
37
37
  | Bounded replies and checkpoints (PROP-0041) | `sessions/workSlice.ts`, `tools/checkpoint.ts`, `sessions/runner.ts` | `checkpoint_write`, `workSlice`, `runs.continuation.checkpoint`, `checkpoint`/`heartbeat` events | guard on every terminal reply with the shared tool; the handoff is recorded on the run; no checkpoint card or slice control yet | not wired | implemented; terminal card, App presentation and `/slices` open | PAR-004 |
38
38
  | Accounts | `accounts/` | `accounts.*` | `/accounts` | Accounts | parity | — |
39
39
  | Models and effort | `accounts/choices.ts` | — | `/model`, `/effort` | composer pickers | parity | — |
40
- | Skills | `plugins/`, `tools/dispatch.ts` | `skill_load` | `$name`, `/skill:name`, browser Skills tab | Capability, composer | parity | — |
40
+ | Skills | `plugins/`, `tools/dispatch.ts`, `prompt/compile.ts` | `skill_load`; `Launch.requiredSkills` | `$name` at any word start (required for that request), `/skill:name`, browser Skills tab | composer `$` chip with `requiredSkills`; Capability | implemented; acceptance not recertified | — |
41
41
  | Plugins and Hub | `plugins/`, `hub/` | `plugins.*`, `hub.*` | full-screen `/plugins` local manager; separate full-screen `/hub` Community catalog with bounded registry/document Preview (no Pi-package Preview) | local Plugins manager; Community Hub with payload Preview for non-package entries | parity | — |
42
42
  | Plugin authoring and proposal review | `plugins/proposals.ts`, `tools/defaultSkills.ts` | `plugin_propose`, `plugins.proposals.*` | `/plugins` Proposals tab: preview, accept or reject | structured model tool and skill; review UI not implemented | partial; App review pending | PAR-017 |
43
43
  | Connected apps (MCP) | `mcp/` | `connections.*`, broker tools | resource browser Connections tab | Connections | parity | — |
44
44
  | Web search and page reading | `web/` | `web_search`, `web_fetch` | world tools | world tools | parity | — |
45
- | Isolated browser | `browser/` | shared `browser_*` schemas | isolated world tools, headed on request, `/settings` Browser tab | native WebKit: navigate, tabs, visible-text read and page scroll without refs; unsupported operations refuse explicitly | partial; native backend gap | PAR-015 |
45
+ | Isolated browser | `browser/` | shared `browser_*` schemas | isolated world tools, headed on request, `/settings` Browser tab | native WebKit: navigate, tabs, visible-text read, page screenshot and page scroll without refs; unsupported operations refuse explicitly; live screenshot acceptance pending | partial; native backend gap | PAR-015 |
46
46
  | Web annotations | `annotations/` (PROP-0017, PROP-0018, PROP-0038, PROP-0048) | `annotations.*`, `annotations.capture.*`, `page_notes_*` | `/annotate:` page (Annotate and All annotations tabs; DevTools app list and launch), `/annotate:notes`, `/annotate:submit`, `/annotate:clear` | website tab Annotate panel (inspector, queue to the focused thread, app windows through `annotations.capture.*`) | App behind PROP-0048 (queue methods removed); native WKWebView gestures unaccepted | PAR-011 |
47
47
  | Managed worktrees | `worktrees/` (PROP-0016) | `worktrees.*` | `--worktree`, `/worktree`, `xagent worktree` | Solo `/worktree` lists records only; Solo starts sessions in the project folder and has no worktree level | partial | PAR-012 |
48
48
  | Wiki notes (model tools) | `wiki/`, `tools/` | `wiki_*` | `Terminals.ownTools` registers Wiki-read tools through the session socket; `xagent-wiki` remains a person-facing shell utility | structured Harness tools | implemented on both; acceptance not recertified | PAR-002: acceptance not recertified |
@@ -56,7 +56,7 @@ implemented is not a parity claim; acceptance is tracked per command.
56
56
  | Provider limits | `usage/limits/` (PROP-0042) | `usage.limits` | `/usage:limit` (current account), `/usage:accounts` (all accounts) | not wired | terminal implemented; host planned | PAR-016 |
57
57
  | Session catalog (sessions per folder) | `sessions/` saved-history controls; shared catalog not implemented | `sessions.*` controls; no cross-surface catalog method | `/session` selects native Pi histories in the current folder | `/session` reads `bridge.agent.threads(SOLO_ID)`, maps each thread to its project folder, and filters by current effective account; `all` broadens only this App list | planned; catalogs differ | PAR-008 |
58
58
  | Folder choice and recent folders | project/worktree selection remains surface-specific | launch `cwd`, `--worktree`, `/worktree` | starts in launch folder; explicit worktree commands | Solo sidebar is Project › Session; new sessions use the project's folder, with no folder/worktree level | planned; no shared recent-folder picker | PAR-009 |
59
- | Computer control | — | `computer_*` (host tools) | unavailable, with reason | macOS bridge (host-backed) | host-backed | PAR-007 |
59
+ | Computer control | `computer/screenshot.ts` defines an opt-in approved-window screenshot contract; other tools remain host-owned | `computer_*` (host tools) | no computer screenshot backend | macOS bridge (host-backed); its display capture has not adopted the approved-window contract | partial; screenshot contract not integrated | PAR-007 |
60
60
  | Shared files vault | — | `files_*` (host tools) | none | Files | host only | PAR-007 |
61
61
 
62
62
  ## Core slash-command surfaces (PROP-0040)
@@ -125,7 +125,7 @@ or live-model sketch quality.
125
125
  | PAR-004 | Refinement and adoption only in the App | Variant tab actions: start, candidates, adopt, trust. |
126
126
  | PAR-005 | Wiki browsing only in the App | A `/wiki` page over `wiki.*`. |
127
127
  | PAR-006 | Acceptance of terminal first-party hooks | Source gap resolved: enabled hooks are returned by protected `firstParty.list` and loaded by `terminal/piExtension.ts`. Acceptance has not been recertified in this documentation pass. |
128
- | PAR-007 | Host tools without a Harness owner | Decide each: computer control stays host-backed (terminal shows unavailable); the files vault becomes a Harness capability or is dropped. |
128
+ | PAR-007 | Host tools without a complete Harness surface | The opt-in screenshot contract exists, but terminal availability reporting and a conforming host backend are not wired. The current macOS host captures a display, not an isolated approved window. The remaining computer controls are host-owned; the files vault becomes a Harness capability or is dropped. |
129
129
  | PAR-008 | Separate session catalogs | Proposal approved; shared catalog is not implemented. Terminal `/session` selects native Pi history in the current folder. App `/session` reads Solo `agent.threads`, maps threads through Solo project groups, and filters to the current effective account and folder; `/session all` broadens only within that App catalog. It cannot open an arbitrary native session path. No cross-surface catalog or visibility guarantee exists. |
130
130
  | PAR-009 | Shared folder/recent-project choice | Terminal starts from its launch `cwd` and can switch through its worktree surface. App Solo uses Project › Session and starts new sessions in the selected project's folder; it has no folder/worktree level or shared recent-folder picker. |
131
131
  | PAR-010 | Agent profile management in Solo | App Solo `/agents` can list profiles. Although the renderer allowlist includes `agents.get`, Solo does not wrap it; `agents.create/remove` are not exposed there. Profile details and management actions are unavailable, unlike terminal `/agents`. |
@@ -134,4 +134,4 @@ or live-model sketch quality.
134
134
  | PAR-013 | Expanded question wireframes and terminal layout | Closed by [PROP-0025 verification](evidence/terminal-question-wireframes/README.md): four-size production PTY acceptance covers aligned compact columns, body scrolling, pinned controls, Shift+arrow sketch panning and live resizing. Host contracts accept 80×24 literal ASCII sketches (1,943 bytes); six isolated browser tests include exact-boundary content, accessible overflow and unchanged selection/answers. This is controlled fixture acceptance, not live-provider or native Tauri evidence. |
135
135
  | PAR-014 | Connected terminal sketch display | Closed by [PROP-0026 verification](evidence/terminal-connected-sketches/README.md) and extended by [PROP-0028 verification](evidence/terminal-wireframe-normalization/README.md): production PTY fixtures cover connected corners/tees, title-bearing rules, missing and whitespace-shifted side edges, internal table widths, frame-attached dividers, resize/panning and unchanged raw persistence/answers. Harness and host checks passed. Host literal rendering remains permitted; no host implementation change was required. This is fixture verification, not an every-font or live-model guarantee. |
136
136
  | PAR-016 | Usage analytics and provider limits in the App | The terminal has `/usage:analysis` and `/usage:limit` over `usage.*`. The App presents the same capability: an analytics view and a per-account provider limit view. |
137
- | PAR-015 | Native WebKit browser-tool coverage | The shared browser schema is wider than the native backend. Native WebKit supports navigation, tabs, visible-text reads and scrolling without refs. Accessibility snapshots, snapshot-ref interactions (including click/type/select/ref-scroll), browser screenshots, waits and keyboard presses return an explicit `browser_unsupported` refusal before effects; there is no automatic switch to the isolated Harness browser. See page 13 and the native backend's `surfaces` note. |
137
+ | PAR-015 | Native WebKit browser-tool coverage | The shared browser schema is wider than the native backend. Native WebKit supports navigation, tabs, visible-text reads, page screenshots and scrolling without refs; live screenshot acceptance is pending. Accessibility snapshots, snapshot-ref interactions (including click/type/select/ref-scroll/ref-screenshot), waits and keyboard presses return an explicit `browser_unsupported` refusal before effects; there is no automatic switch to the isolated Harness browser. See page 13 and the native backend's `surfaces` note. |
@@ -0,0 +1,54 @@
1
+ # Xingular Harness 1.3.1-unstable.3
2
+
3
+ Unstable preview of the current Harness work. Install with `npm install @xingular/harness@unstable`.
4
+
5
+ - Name a skill anywhere in a message: `$name` at any word start makes that skill required for the
6
+ request (ADR-0048). `Launch.requiredSkills` carries it, and the compiler renders a
7
+ `required-skills` section at order 52 from the pinned variant through `readSkillFile`, bounded at
8
+ 12,000 characters. An unavailable skill is named honestly instead of invented, and the built-in
9
+ `plugin-authoring` skill stands in when the variant ships no skills. The section belongs to the
10
+ request, so an identical one is delivered again rather than deduplicated. The terminal builds the
11
+ same text with `requiredSkillsText` and appends it as a hidden `xingular-required-skills` message,
12
+ so the person's own wording reaches the model unchanged. In the composer the `$` palette opens at
13
+ the caret and inserts there, keeping the rest of the draft.
14
+ - Bound delegation per run and per profile (ADR-0052). A run now starts at most four distinct child
15
+ conversations: two `worker`, three `explorer`, one of any other named profile and one with no
16
+ profile. `SessionRunInput.subagents` / the SDK's `Subagents` options override them with
17
+ `maxChildren`, `maxByProfile`, `maxOther` and `maxUnprofiled` (clamped to 0–50), and the model's
18
+ `spawn_subagent` description states the budget it has left. `Subagents.spawn` refuses a child past
19
+ a limit with `invalid_request` and starts no run; continuing a stopped child costs no slot, and a
20
+ work-slice continuation keeps the parent's budget.
21
+ - Read what a request was made of. `context.attribution` returns one row per contributor with its
22
+ estimated tokens and share of the window, reconciled to the provider's measured total, and one
23
+ row's text on request; the terminal reaches it through `terminal.context`. The compiler now
24
+ records where its characters went: `renderSized` returns each section's size and the framing that
25
+ belongs to no section, `Delivery.sizes` stores them on the recorded pack, and `Source.chars`
26
+ attributes a variant's tool guidance inside the tools section. `context.snapshot` also records the
27
+ compaction headroom the run was held to (`compaction: { enabled, reserveTokens }`) from Pi's own
28
+ settings. The terminal page over this data arrives in a later preview; no `/context` command is
29
+ registered yet.
30
+ - Add an opt-in approved-window screenshot contract (ADR-0050). `src/computer/screenshot.ts` defines
31
+ `computer_screenshot` for a host-supplied backend: it captures exactly one currently approved
32
+ window, excludes the desktop, other windows and overlapping overlays, and refuses with
33
+ `COMPUTER_SCREENSHOT_UNAVAILABLE` rather than returning display pixels. It goes through the
34
+ ordinary tool gate, is not plan-mode safe, and adds no extension point. The standalone terminal
35
+ has no backend, and the existing macOS host capture has not adopted this contract.
36
+ - Deliver approvals and questions promptly without polling: the control-key-protected
37
+ `terminal.pending.wait` returns as soon as the pending revision changes and otherwise waits at most
38
+ 20 s, waking on additions, removals and disposal (ADR-0051). `terminal.pending` is unchanged and
39
+ the server protocol is untouched.
40
+ - Swap the OpenTUI send keys back. `Return` sends, or queues a follow-up behind a running reply, and
41
+ `Cmd+Return`, `Ctrl+Return` or `Alt+Return` steers that reply. The `app.message.followUp` action is
42
+ `app.message.steer` again, so a `keybindings.json` entry under the newer name needs renaming.
43
+ - Keep a stray mouse report out of input. A report split across the input parser's timeout no longer
44
+ types its `<button;column;row>` tail into the composer or a single-line field, so a scroll cannot
45
+ become a run's prompt or a filter; typing and pastes are untouched and `/trajectory` no longer
46
+ prints a recorded report.
47
+
48
+ ## Upgrading
49
+
50
+ - A leading `$name` is no longer rewritten to Pi's `/skill:name`. An embedding host sends required
51
+ skills as `Launch.requiredSkills`, and the message text reaches the model as written.
52
+ - Runs that spawned children freely now stop at the defaults above; set `subagents` explicitly to
53
+ change them.
54
+ - A `keybindings.json` entry named `app.message.followUp` is now `app.message.steer`.