gentle-pi 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/orchestrator-delegation.md +13 -8
- package/assets/orchestrator.md +2 -2
- package/docs/gentle-shell.md +40 -17
- package/docs/readme-reference.md +41 -7
- package/docs/review-integration.md +25 -11
- package/extensions/gentle-agents.ts +85 -17
- package/extensions/gentle-ai.ts +179 -12
- package/extensions/gentle-shell.ts +408 -38
- package/extensions/gentle-todo.ts +19 -1
- package/lib/agents-view.ts +41 -14
- package/lib/agents-widget.ts +84 -13
- package/lib/command-palette-catalog.ts +1 -0
- package/lib/double-esc-cancel-policy.ts +138 -0
- package/lib/inprocess-reviewer.ts +260 -0
- package/lib/model-routing-authority.ts +1 -1
- package/lib/native-review-cli.ts +23 -0
- package/lib/odd-runtime-delegation-gate.ts +88 -0
- package/lib/review-host-relay.ts +262 -94
- package/lib/review-integration-v2.ts +110 -26
- package/lib/shell-bar.ts +158 -29
- package/lib/shell-card.ts +19 -9
- package/lib/shell-changes-view.ts +43 -5
- package/lib/shell-changes.ts +92 -5
- package/lib/shell-hover.ts +39 -0
- package/lib/shell-prompt.ts +10 -1
- package/lib/shell-sidebar-layout.ts +111 -15
- package/lib/shell-sidebar.ts +16 -0
- package/lib/shell-todo.ts +7 -1
- package/lib/shell-usage-view.ts +98 -10
- package/lib/shell-usage.ts +226 -10
- package/package.json +2 -1
- package/runtime/native-review-cli.mjs +23 -0
- package/runtime/review-integration-v2.mjs +110 -26
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/maintainer/provider-relay-matrix.mjs +118 -47
- package/scripts/mirror-odd-routing.mjs +242 -0
- package/scripts/verify-package-files.mjs +3 -3
- package/tests/agents-grouping.test.ts +75 -18
- package/tests/agents-view.test.ts +28 -18
- package/tests/agents-widget.test.ts +100 -12
- package/tests/command-palette.test.ts +1 -0
- package/tests/devbinary/pi-host-relay.devtest.ts +176 -138
- package/tests/double-esc-cancel-policy.test.ts +194 -0
- package/tests/gentle-agents.test.ts +528 -5
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai.test.ts +69 -5
- package/tests/gentle-shell.test.ts +903 -25
- package/tests/gentle-todo.test.ts +17 -4
- package/tests/inprocess-reviewer.test.ts +368 -0
- package/tests/maintainer/provider-relay.maintest.ts +101 -143
- package/tests/native-review-capability-contract.test.ts +32 -1
- package/tests/odd-routing-canonical-ratchet.test.ts +293 -0
- package/tests/odd-routing-contract.test.ts +57 -0
- package/tests/odd-runtime-delegation-gate.test.ts +212 -0
- package/tests/orchestrator-rdd-ownership.test.ts +3 -3
- package/tests/package-manifest.test.ts +6 -6
- package/tests/review-controller-native-routing.test.ts +60 -1
- package/tests/review-host-relay-routing.test.ts +77 -0
- package/tests/review-host-relay.test.ts +285 -239
- package/tests/review-integration-v2-forward.test.ts +61 -0
- package/tests/review-integration-v2.test.ts +116 -1
- package/tests/review-relay-transport-agent.test.ts +83 -0
- package/tests/runtime-harness.mjs +11 -0
- package/tests/session-changes-shell.test.ts +27 -0
- package/tests/session-worktree-registry.test.ts +41 -0
- package/tests/shell-bar.test.ts +224 -6
- package/tests/shell-card.test.ts +5 -3
- package/tests/shell-changes-view.test.ts +47 -0
- package/tests/shell-changes.test.ts +177 -0
- package/tests/shell-hover.test.ts +19 -0
- package/tests/shell-prompt.test.ts +20 -0
- package/tests/shell-sidebar-fullscreen.test.ts +59 -0
- package/tests/shell-sidebar-layout.test.ts +243 -5
- package/tests/shell-sidebar.test.ts +25 -1
- package/tests/shell-todo.test.ts +36 -0
- package/tests/shell-usage-view.test.ts +123 -3
- package/tests/shell-usage.test.ts +254 -6
- package/lib/opaque-pi-reviewer-adapter.ts +0 -284
- package/tests/opaque-pi-reviewer-adapter.test.ts +0 -266
|
@@ -123,14 +123,19 @@ Keep one writer and a short synthesized handoff. Delegation is mandatory at the
|
|
|
123
123
|
|
|
124
124
|
#### Mandatory Delegation Triggers
|
|
125
125
|
|
|
126
|
-
These are parent-orchestrator routing boundaries.
|
|
127
|
-
|
|
128
|
-
1. **
|
|
129
|
-
2. **
|
|
130
|
-
3. **
|
|
131
|
-
4. **
|
|
132
|
-
5. **
|
|
133
|
-
|
|
126
|
+
These are parent-orchestrator routing boundaries; do not pass these rules to child agents as permission to orchestrate. These triggers are mandatory, not advisory. When one fires, stop and delegate through the runtime's subagent mechanism before continuing; executing past a fired trigger inline is a routing defect even if the work succeeds. Delegation keeps the parent context thin enough to orchestrate; it does not slow the work down.
|
|
127
|
+
|
|
128
|
+
1. **Mapping trigger (4-file rule):** when understanding the work requires 4 or more files, delegate one narrow exploration or mapping task before deciding or writing anything.
|
|
129
|
+
2. **Writer trigger (Multi-file write rule):** when implementation touches 2 or more non-trivial files, delegate one bounded writer instead of editing them inline.
|
|
130
|
+
3. **Incident rule:** after wrong `cwd`, accidental repository/worktree mutation, failed merge recovery, confusing test command, or environment workaround, stop and diagnose the incident separately before resuming.
|
|
131
|
+
4. **Long-session backstop (Long-session rule):** after about 20 tool calls, 5 exploratory reads, or 2 non-mechanical edits without any delegation, pause and delegate the next bounded unit of work.
|
|
132
|
+
5. **Verification rule** (gentle-pi#661/#662, RDD-aware): executing or delegating verification commands goes to `gentle-ai-verify`; only the 1–3-file read-only check stays inline. The normative on/off/unknown routing is stated once under Pi Trigger Runtime Bindings below; reference it, do not restate it.
|
|
133
|
+
|
|
134
|
+
**Preparation trigger:** reading that prepares a write, and broad research or context compression, delegate together with or ahead of the write instead of filling the parent context.
|
|
135
|
+
|
|
136
|
+
**Route declaration:** for substantial work, record the chosen route per task (inline or delegated) and the trigger evidence in the feature document, so skipped delegation is observable instead of silent.
|
|
137
|
+
|
|
138
|
+
These triggers never select SDD and never create SDD artifacts; they only choose between direct inline and delegated direct inside the organic flow.
|
|
134
139
|
|
|
135
140
|
For bounded multi-file writes, prefer the installed package-owned `gentle-ai-worker`, then a user-configured `worker`. If neither worker definition exists, fall back to the native `Agent` even when `subagent_*` tools are available. If no delegation mechanism is available, stop and explain the blocker. Judgment Day phase roles are never generic fallbacks. If the generic writer chain is unavailable, use the documented native generic fallback or stop.
|
|
136
141
|
|
package/assets/orchestrator.md
CHANGED
|
@@ -56,8 +56,8 @@ Mandatory Delegation Triggers — once fired, delegate through the best availabl
|
|
|
56
56
|
1. **4-file rule** — 4+ files to understand → delegate a scout/mapping task.
|
|
57
57
|
2. **Multi-file write rule** — 2+ non-trivial files touched → delegate one writer.
|
|
58
58
|
3. **Incident rule** — diagnose wrong cwd/worktree/git/tooling incidents separately before resuming work.
|
|
59
|
-
4. **
|
|
60
|
-
5. **
|
|
59
|
+
4. **Long-session rule** — ~20 tool calls, 5 exploratory reads, or 2 non-mechanical edits without delegation → pause and delegate.
|
|
60
|
+
5. **Verification rule** — executing/delegating verification commands → `gentle-ai-verify`; only the 1-3-file read-only check stays inline.
|
|
61
61
|
|
|
62
62
|
{{GENTLE_PI_BACKGROUND_POLICY}}; rules: the background-subagents block in the delegation contract.
|
|
63
63
|
|
package/docs/gentle-shell.md
CHANGED
|
@@ -15,22 +15,31 @@ The [v2.6.0 release](https://github.com/Gentleman-Programming/gentle-pi/releases
|
|
|
15
15
|
- The Agents List and Details views preserve the orchestrator/session hierarchy and completion, abort, and lost-exit history. Parent-child queries and notifications have an explicit handoff path, while model, effort, and usage stay observable per task.
|
|
16
16
|
- Named `/gentle:profiles` atomically route the orchestrator separately from packaged and review roles; see the [technical reference](readme-reference.md#agent-model-profiles) for the profile model.
|
|
17
17
|
|
|
18
|
-
The source checkout currently prepares `gentle-pi` `3.
|
|
18
|
+
The source checkout currently prepares `gentle-pi` `3.3.0` with a package-local Gentle AI `v3.4.0` pin; this is not a claim that `3.3.0` is published.
|
|
19
19
|
|
|
20
20
|
## Shell interactions and runtime behavior
|
|
21
21
|
|
|
22
|
-
Gentle Shell is the Pi workspace experience provided by the `gentle-pi` package. It follows the Gentle themes: one border language,
|
|
22
|
+
Gentle Shell is the Pi workspace experience provided by the `gentle-pi` package. It follows the Gentle themes: one border language, rose for whatever is alive.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
### Fullscreen layout
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
At 140 columns or wider, fullscreen splits into a live header row over a transcript-and-rail split, both driven by [`lib/shell-sidebar-layout.ts`](../lib/shell-sidebar-layout.ts):
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
```text
|
|
29
|
+
✿ Gentle Shell ⟡ ~/work/gentle-pi main ⟡ gpt-5.5 · medium · team ctx ▰▰▰▰▱▱▱▱ 45% ⟡ $9.49 sub
|
|
30
|
+
```
|
|
29
31
|
|
|
30
|
-
The
|
|
32
|
+
- The header is one row, always visible, and carries only what changes every frame: session identity on the left (brand, cwd, branch, dirty count, model · effort · profile) and the two live counters right-aligned (the context gauge and session cost). It never shows the working/thinking state or extension statuses — those stay in the prompt title and the compact bar. When the terminal is too narrow for everything, segments give way in a fixed order — profile, then effort, then the whole cwd/branch/dirty group — before the counters are touched; below that, only the brand survives, and below that the header renders nothing.
|
|
33
|
+
- The right rail scrolls **Status → Changes → TODO**, each an event-driven card that only repaints when its own state changes: a model switch or a cost tick refreshes the header, not the rail. Every card (sidebar or not) paints the same rose frame — the rounded border in the theme's plain border role, the title in the accent role — the look every `CARD_TONE.INFO` card in Gentle Shell uses (warning/error/success cards keep their own tone colors).
|
|
34
|
+
- The Status card carries only what an explicit event refreshes: Project (cwd, branch, session name, active profile), Changes, and Integrations (other extensions' statuses). Model, effort, context, cost, and the per-model usage table live in the header instead — the header ticks every frame, so duplicating them in a card would just make that card repaint every frame too.
|
|
35
|
+
- Gentle Agents is not part of the rail in any mode: its one card stays above the editor, where it already lived, with fixed right-aligned columns for `model · effort`, tokens, cost, and elapsed, each sized to the widest value among the shown tasks — so the numbers line up vertically even when one row's values are much shorter than another's. A queued task fills only the elapsed column with the word `queued`, leaving the other columns blank rather than overwriting the row.
|
|
36
|
+
- The sidebar reuses its last frame until something it paints changes, so silent frames stay cheap; a per-section cache means one card's changing digest (or the header's) never forces an unrelated card to redraw.
|
|
37
|
+
- Narrow terminals and regular mode keep the compact bottom bar and the above-editor Agents widget, with no header row and no sidebar.
|
|
38
|
+
|
|
39
|
+
Below 140 columns, or in regular mode, the compact bottom bar replaces pi's three-line footer with a single line of segments instead:
|
|
31
40
|
|
|
32
41
|
```text
|
|
33
|
-
✿ gentle
|
|
42
|
+
✿ gentle shell ⟡ ~/work/gentle-pi main ⟡ gpt-5.5 · medium ⟡ ctx ▰▰▰▰▱▱▱▱ 45% ⟡ $9.49 sub ⟡ MCP: 3 servers enabled Release notes
|
|
34
43
|
```
|
|
35
44
|
|
|
36
45
|
- Context is a gauge, not a number. It turns amber at 80% and red at 95%; after compaction it shows `?%` until the next response.
|
|
@@ -63,6 +72,8 @@ Changes shows **captured write/edit operations from this agent session and its o
|
|
|
63
72
|
- Diffs compare the content observed before the agent's first captured operation with its latest captured result, not with HEAD. Consecutive agent edits combine; an agent revert removes its net change.
|
|
64
73
|
- Edits from your editor or other sessions do not update these captured diffs. If an external or unobserved edit breaks continuity before the next agent operation on the same file, the file is marked **diff unavailable**, rather than mixing ownership.
|
|
65
74
|
- Only worktrees in the coordinating session's Git clone are accepted. Child evidence is accepted only from an owned task with paired successful write/edit events and a matching target.
|
|
75
|
+
- A changed file's worktree is resolved from its own directory upward (`git rev-parse --show-toplevel` starting there, never from an ancestor's cwd), so a repository nested inside another — a project scaffolded inside a personal workspace clone, say — is always attributed to its own, inner repository, never the outer one.
|
|
76
|
+
- When changes span more than one worktree, each tree header shows that root's own branch name, `no commits yet` for an unborn branch, or `detached` only for a real detached HEAD. The label is read from Git's HEAD once per root while the overlay is open (`symbolic-ref` and `rev-parse --verify`); the overlay still never runs `status`, `diff` or a worktree scan on your behalf.
|
|
66
77
|
- **Coverage is deliberately limited to write/edit tools.** Shell commands, custom mutation tools, failed/interrupted outcomes and children without the capture extension provide no attributed diff. A missing row does not mean the repository is clean or that no other changes occurred.
|
|
67
78
|
|
|
68
79
|
### Bounds and session lifetime
|
|
@@ -78,7 +89,7 @@ The separate `session_worktree_register` tool still registers canonical same-clo
|
|
|
78
89
|
`/gentle:changes` or `alt+g` opens the two-pane viewer. Worktrees are accordion groups on the left; selecting a file displays its captured diff on the right.
|
|
79
90
|
|
|
80
91
|
- `j`/`k` or arrows navigate. On a group, Enter, Space or Right expands it; Left returns to its parent or collapses it. `ctrl+j/k` or Page Up/Down scroll the diff; Escape or `q` closes.
|
|
81
|
-
- Fullscreen left-click selects files; mouse wheels scroll the file list and diff independently.
|
|
92
|
+
- Fullscreen left-click selects files; mouse wheels scroll the file list and diff independently. Hovering an unselected row (worktree or file, in either pane's list) paints it in the same shared hover role every clickable surface in the shell uses; it never opens or selects the file, and never overrides the already-selected row's own role.
|
|
82
93
|
- Opening, pressing `r`, and the overlay's refresh cadence consult only the captured session model. They never rescan Git or load the current file contents. Same-line-count edits invalidate the diff preview by content revision.
|
|
83
94
|
- On a file, `o` or Enter opens the actual current file in `$VISUAL` or `$EDITOR`, with its worktree as cwd. Edits made there are external and are not attributed to the agent.
|
|
84
95
|
- `GENTLE_PI_SHELL_CHANGES_KEY` rebinds the shortcut; `off` disables it. `GENTLE_PI_SHELL_CHANGES_POLL_MS` controls only the open overlay's in-memory refresh. `GENTLE_PI_SHELL_CHANGES_WATCH_MS` no longer enables filesystem polling.
|
|
@@ -90,19 +101,29 @@ The separate `session_worktree_register` tool still registers canonical same-clo
|
|
|
90
101
|
|
|
91
102
|
To use `ctrl+p` like OpenCode, rebind Pi's `app.model.cycleForward` in `~/.pi/agent/keybindings.json` (Pi reserves that action, so an extension cannot take `ctrl+p` while it holds it) and set `GENTLE_PI_COMMANDS_KEY=ctrl+p`.
|
|
92
103
|
|
|
93
|
-
Subscription usage shows in the bar after the cost, and `/gentle:usage` opens a panel with
|
|
104
|
+
Subscription usage shows in the bar after the cost, and `/gentle:usage` opens a panel with one row per window of every provider: the limit name, its meter, its percentage and, when that window reports one, its reset, all on one line. Codex, Claude and NaN all read the same way. In fullscreen mode, clicking the header's `usage` segment opens this same panel. The panel's own footer hints (`r refresh`, `esc close`) are clickable too, not just keyboard shortcuts, and hovering either one paints it in the shell's shared hover role while a refresh already in flight ignores a repeated click.
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
✿ gentle shell ⟡ … ⟡ $9.49 sub ⟡ codex 5h ▰▰▰▰▰▱▱▱ 62% · week 31%
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The panel rows a provider reports its windows with:
|
|
94
111
|
|
|
95
112
|
```text
|
|
96
|
-
✿
|
|
113
|
+
✿ nan · updated just now
|
|
114
|
+
deepseek-v4-flash ▰▰▰▰▱▱▱▱▱▱▱▱▱▱▱▱ 26% · resets in 12d 17h
|
|
115
|
+
glm5.3-flash ▰▰▱▱▱▱▱▱▱▱▱▱▱▱▱▱ 11% · resets in 12d 17h
|
|
97
116
|
```
|
|
98
117
|
|
|
99
118
|
- For Codex, usage comes from the same account usage endpoint the Codex CLI reads, using the OAuth token pi already holds. It is fetched at session start, at most every 5 minutes after a turn, and on `r` in the panel. Rate-limit headers on SSE responses are picked up too.
|
|
100
119
|
- For Claude Pro/Max, usage arrives in the rate-limit headers of every response, so the 5h and weekly windows appear after the first turn.
|
|
101
|
-
-
|
|
120
|
+
- For NaN Cloud, usage comes from the quota endpoint the official dashboard reads, with the same API key pi already holds. Each metered model reports one allowance for the billing period, and that window carries no label: the model id names it in the bar and the reset text says what it is in the panel. A model that also reports a rolling window shows that one labeled next to it (`4h`), which today's payload does not send; percentages are tokens used over the allowance, exactly as the dashboard draws them, and the allowance is the full-period cap (`fullCap`) whenever the model reports a positive one, because `cap` alone is the prorated allowance of the period in progress. It is fetched under the same 5-minute rule as Codex, counted per provider so a switch fetches the provider it switched to, refuses redirects so the bearer cannot be replayed to another origin, and keeps no cached copy. The endpoint sits outside NaN's published OpenAPI, so the parser reads it defensively: a model that reports no allowance is skipped, as the dashboard skips it, while a metered model whose usage cannot be read fails the whole read, so a partial payload never replaces a complete snapshot with a cheaper-looking one. A session that already has a snapshot keeps the last valid one through a malformed payload or a failed fetch, and the pending note appears only while there is nothing to draw.
|
|
121
|
+
- The bar names the subscription it shows (`codex`, `claude`, a NaN model) and always follows the active model. A provider with per-model allowances draws the session model's own meter, falling back to its family and then to the account total, never to whichever model the payload happens to list first — and that holds for a payload that reports a single metered model too, because one allowance is still per-model data rather than a reason to echo the first entry. The panel puts the active provider first, marked with the petal, and says why it has no data when it does not: API-key providers have no subscription windows, Claude reports after the first response, Codex and NaN wait for a fetch. A provider without per-model allowances keeps its single aggregate line in the sidebar, unchanged.
|
|
122
|
+
- A provider with per-model allowances is ordered by family on both surfaces: a family stays together, the family that consumes most comes first, and the models inside it follow the same rule, most used first. There are no `total` rows anywhere — an aggregate nobody can act on only costs space — so the account and family totals survive only as the bar's fallback name when the session model holds no allowance of its own (`nan total`). An allowance row leaves the window label empty and prints `name meter percent`, while a labeled sub-window (`4h`) keeps its column, and the reset a window reports rides that same line after a `·`; a window without one ends at its percentage, never on a dangling separator. The sidebar's Usage group prints those same rows in that same order, so the breakdown does not require opening the panel, and stops at the percentage: the reset dates stay in the panel. A row whose windows all round to `0%` is dropped from that group — an allowance nobody has touched yet tells the reader nothing the missing row does not — and the same rule retires the aggregate line of a provider without raw allowances once every window it shows sits at `0%`; the bar and the panel keep printing it, so a zeroed subscription is still verifiable there.
|
|
102
123
|
- Only the plan name and the windows are kept; account details in the payload are discarded.
|
|
103
124
|
- Gauges turn amber at 80% and red at 95%, like the context gauge.
|
|
104
125
|
|
|
105
|
-
Gentle notices are drawn as cards: the same rounded frame as the prompt
|
|
126
|
+
Gentle notices are drawn as cards: the same rounded frame as the prompt. An informational card paints the rounded frame in the theme's plain border role and its title in the accent role — the rose look every sidebar card, the review preflight reminder, and a quiet Agents card share. A warning, error, or success card paints its frame and title in its own tone color instead.
|
|
106
127
|
|
|
107
128
|
```text
|
|
108
129
|
╭─ ✿ Gentle AI · review preflight ─────────────────────────────────────╮
|
|
@@ -125,11 +146,13 @@ Agent paths follow `GENTLE_PI_AGENT_HOME`, then `PI_CODING_AGENT_DIR`, then `~/.
|
|
|
125
146
|
|
|
126
147
|
```text
|
|
127
148
|
╭─ ❀ Agents · 1 active · 1 done ─────────────────────────────── 1m24s ╮
|
|
128
|
-
│ ✓ sdd-explore map footer data sources
|
|
129
|
-
│ ◐ sdd-apply write gentle-shell footer
|
|
130
|
-
|
|
149
|
+
│ ✓ sdd-explore map footer data sources gpt-5.6-terra · 34k · $0.27 · 25s │
|
|
150
|
+
│ ◐ sdd-apply write gentle-shell footer gpt-5.6-terra · 120k · $12.50 · 41s │
|
|
151
|
+
╰──────────────────────────────────────────────────────────────────────────────────╯
|
|
131
152
|
```
|
|
132
153
|
|
|
154
|
+
The card is above the editor in every mode, including fullscreen — it is not one of the sidebar's cards. Each metadata field (`model · effort`, tokens, cost, elapsed) gets its own fixed, right-aligned column sized to the widest value among the shown tasks, so the numbers line up vertically even when one row's values are much shorter than another's; a queued task fills only the elapsed column with the word `queued`, leaving the rest of the row blank rather than overwriting it. When the card is too narrow for every column, it degrades one column at a time and the same way for every row: the task text goes first, then the `model · effort` label, then tokens, then cost; elapsed is the last column standing, since it is the one value the reader cannot rebuild from anything else on screen.
|
|
155
|
+
|
|
133
156
|
Every subagent is its own `pi --mode rpc` child process, so the terminal never runs subagent work: the host reads JSON lines, applies each one as a small delta to a bounded per-task thread, and notifies only the listeners of that task. A task-mode child's question (`ctx.ui.select`, `confirm`, `input`, `editor`) reaches you as an ordinary pi dialog; a background child's question is dismissed. Subagents have no automatic total execution timeout: a long-running child remains live while it continues emitting RPC events. A silent child still times out through the configurable `stall_timeout_ms` watchdog (default four minutes). An announced tool call that is still running is live work, not silence, so it is bounded by `tool_stall_timeout_ms` instead (default 30 minutes, never below `stall_timeout_ms`). Closing pi stops the children that are still running.
|
|
134
157
|
|
|
135
158
|
- `subagent_list_agents`, `subagent_run` (`agent`, `task`, `label?`, `context?`, `workspace_root?`, `mode?` task or background), `subagent_status`, `subagent_result`, `subagent_list_tasks`, `subagent_reply` (one current-session reply to a live child query), `subagent_cancel`, `subagent_send_message` (steer a running child), `subagent_continue` (resume a finished task in its own session).
|
|
@@ -143,10 +166,10 @@ Every subagent is its own `pi --mode rpc` child process, so the terminal never r
|
|
|
143
166
|
- Mouse controls take priority over keyboard hints: **Follow** (`f`), **Open session** (`o`), **Stop** (`s`, legacy `c`, owned active tasks only), and **Scope** (`a`). A compact footer's `>` cycles through actions. Scope switches between this session's direct active children and all open orchestrators, including idle ones. Open writes a markdown transcript for `$EDITOR`, not a resumed child session. `j`/`k` move through lists or scroll an expanded thread; `ctrl+j`/`ctrl+k` and Page Down/Up page the thread. In Pi fullscreen mode, the wheel scrolls the viewport under the pointer; regular terminal mode does not capture mouse input. Below 12 columns or three rows, only a bounded Close cell remains; zero-sized terminals render nothing.
|
|
144
167
|
- The thread displays all retained Text, Thinking, Note, and Tool content without an additional presentation cap; existing store limits and truncation markers still apply. Only the selected task is subscribed while the overlay is open.
|
|
145
168
|
- Thread entries are presented as labeled Text, Thinking, Note, or Tool blocks; tool blocks show their status and nonempty output.
|
|
146
|
-
- Current scope has no orchestrator wrapper
|
|
169
|
+
- Current scope has no orchestrator wrapper. Its own session's finished subagents stay listed as history after the active ones — newest ended first — with their terminal glyph, elapsed frozen at completion, and their thread inspectable; a finished row is never cancellable. The header reads `N active · M finished`. History is capped at 200 finished tasks per session (oldest dropped); resuming a session (`session_start` with reason `resume`) restores that session's own finished tasks from disk automatically, a brand-new session starts empty, and All sessions discovers open Pi instances sharing the same agent profile, even across repositories — it does not infer open sessions from retained tasks and stays presence-only (no cross-session history browsing). Directory headings support left/right and mouse expansion, and cannot stop or open a task. Peer children and their retained threads are read-only: no local stop, editor-open, or continuation routing, and no import into the local task store.
|
|
147
170
|
- Presence refresh is paged while the overlay is open. Graceful shutdown withdraws an instance; after abrupt closure its last heartbeat may remain visible for up to 15 seconds plus the time to complete the next directory refresh. A recent heartbeat is a heuristic, not proof that a process is alive. Same-profile, same-user processes share retained activity text; this is not an authorization channel.
|
|
148
171
|
- `alt+s` confirms stopping the current active or queued subagents owned by the current process. `GENTLE_PI_AGENTS_STOP_KEY` rebinds it; `off` disables it.
|
|
149
|
-
- Finished tasks are written to `~/.pi/agent/gentle-agents/tasks/` (one JSON per task, newest `history_max_tasks` kept, default 200) and come back on demand for `subagent_result` and `subagent_continue
|
|
172
|
+
- Finished tasks are written to `~/.pi/agent/gentle-agents/tasks/` (one JSON per task, newest `history_max_tasks` kept, default 200) and come back on demand for `subagent_result` and `subagent_continue`; an id looked up this way from an unrelated session never enters the overlay or becomes cancellable. Child sessions live under `~/.pi/agent/gentle-agents/sessions/`.
|
|
150
173
|
- `ctrl+shift+a` collapses the card to its first row (`GENTLE_PI_AGENTS_KEY`), `GENTLE_PI_AGENTS_VIEW_KEY` rebinds the overlay, `GENTLE_PI_AGENTS_PI` overrides the pi command used for children, and `GENTLE_PI_AGENTS=0` disables the tools and the card.
|
|
151
174
|
|
|
152
175
|
### Gentle Todo
|
package/docs/readme-reference.md
CHANGED
|
@@ -24,6 +24,7 @@ ODD is the predefined workflow: it runs by default on every request, without the
|
|
|
24
24
|
- **One feature document:** `odd/tasks/<feature-name>.md` holds objective, problem, why, scope, constraints, actionable checklist with stable IDs and acceptance criteria, verification evidence, progress, and next step. Project-scoped Engram topic `odd/<feature-name>/tasks` mirrors the full document and repository-relative locator. Keep concise rationale for meaningful accepted changes here, not a separate plan or exhaustive journal. Accepted user, review, or verification changes update intent and tasks together; preserve valid completed work, add new tasks or reopen invalidated items with reasons. Findings alone do not authorize expansion or acceptance. Routine corrections stay with their tasks; checkoffs require observed proof.
|
|
25
25
|
- **Recovery:** write local progress first and read back both copies; writes are not atomic. Unavailable Engram leaves an explicit pending mirror, not invented success or a block on unrelated safe work. Before implementation or resume, the parent reads full feature memory and the actual task file, reconciles code and evidence, and preserves conflicting versions. Pass the locator and relevant context; workers read the document before edits. The existing Todo UI is a projection, not another authority.
|
|
26
26
|
- **Task size:** about 400 authored changed lines (additions plus deletions) is advisory only, not a cap, acceptance criterion, automatic stop, forced split, or RDD trigger. Keep coherent behavior with tests and docs, explain natural overages, and continue under existing PR policy. Forward this instruction to workers; never remove whitespace, comments, or tests, minify, invent abstractions, or split artificially for cosmetic savings.
|
|
27
|
+
- **Runtime boundary:** in a primary turn, direct `edit`/`write` calls may successfully mutate one repository file and repeat that path. A second distinct file is refused before mutation and must go through `subagent_run`; `odd/tasks/**` bookkeeping and delegated child actors are exempt, failed calls consume nothing, and the next primary start resets the boundary.
|
|
27
28
|
- **Research:** optional research addresses a named uncertainty. Establish problem, intended outcome, constraints, and current evidence; inspect code and adapt depth to consequence, not fixed questionnaires or rounds. The parent asks one focused product question only when needed, then waits; workers return gaps. Use available authorized documentation/web tools, prefer primary sources, and attribute claims to URLs/code locations. Distinguish facts, assumptions, contradictions, freshness, and gaps; return a recommendation, tradeoffs, open questions, and implementation implications. Forward these instructions to an existing fresh general worker, not a specialized agent or `sdd-research`. Unavailable evidence pauses only unsafe dependent decisions. Research stays read-only with no new persistence/readiness machinery; a brief proposal is needed only for a real decision.
|
|
28
29
|
- **Assumptions:** at most one scoped independent read-only challenge for a high-consequence unproven premise, including a small security-critical change. Deterministic failures need fixes, not debate. Native RDD claims stay with its refuter.
|
|
29
30
|
- **TDD:** resolve on/off from existing project/session configuration or explicit user choice; retain source and exact runner in the feature document when present and forward all three on every implementation delegation, refreshing on resume. Test presence does not enable TDD. Enabled requires observed RED before implementation → GREEN → REFACTOR; disabled still requires ordinary functional checks. Unknown/conflicting mode or a missing runner needs only the clarification affecting the next action, never invented precedence, commands, or `sdd-init`.
|
|
@@ -105,7 +106,7 @@ This is guidance through existing tools, not a new CLI, phase, state engine, or
|
|
|
105
106
|
| **Skill creation workflow** | Provides the `gentle-ai-skill-creator`/`gentle-ai-skill-improver` skills, `/skill-creation` prompt, and packaged style guide for LLM-first skills. |
|
|
106
107
|
| **Delivery skills** | Includes issue-first PRs, chained PRs, work-unit commits, cognitive docs, comment writing, and Judgment Day review. |
|
|
107
108
|
| **Bounded native review** | Freezes one candidate, dispatches only controller-selected lenses, and records native authority. Review outcomes are informational; delivery follows ordinary repository policy. |
|
|
108
|
-
| **Verified native runtime** | The current source checkout provisions the exact package-local Gentle AI v3.
|
|
109
|
+
| **Verified native runtime** | The current source checkout provisions the exact package-local Gentle AI v3.4.0 runtime: signed, SHA-256-pinned release archives on Darwin/Linux and a Go SumDB-verified source build on Windows x64/arm64. It validates package-local integrity and rejects PATH, global, sibling, symlink, and mode fallbacks. |
|
|
109
110
|
| **Runtime safety** | Blocks destructive shell commands, asks for confirmation for sensitive operations, and blocks direct read/write/edit access to sensitive paths. |
|
|
110
111
|
|
|
111
112
|
## Native pointer regions
|
|
@@ -142,7 +143,7 @@ The stable release is [`v2.6.0`](https://github.com/Gentleman-Programming/gentle
|
|
|
142
143
|
|
|
143
144
|
### Source checkout
|
|
144
145
|
|
|
145
|
-
This checkout prepares `gentle-pi` `3.
|
|
146
|
+
This checkout prepares `gentle-pi` `3.3.0`; it is source state, not a published release. Its package-local native runtime pin is Gentle AI `v3.4.0`, distinct from the published `v2.6.0` pairing.
|
|
146
147
|
|
|
147
148
|
The native SDD status consumer accepts both the pinned producer's legacy
|
|
148
149
|
`apply`/`verify`/`remediate`/`archive` instruction record and the classical
|
|
@@ -153,7 +154,7 @@ Unknown or incomplete instruction records still fail closed.
|
|
|
153
154
|
The Pi runtime now uses native status exclusively for SDD and retires standalone
|
|
154
155
|
sync. The full chain follows completed apply to archive, where applicable delta
|
|
155
156
|
specs are composed; verification remains explicitly invokable. With the current
|
|
156
|
-
3.
|
|
157
|
+
3.4.0 pin, native still requires verification and its emitted evidence requirements;
|
|
157
158
|
a plain practical PASS report does not satisfy that legacy native gate. Pi forwards
|
|
158
159
|
those exact instructions without overriding readiness or inventing legacy evidence.
|
|
159
160
|
Classical direct-archive behavior is compatibility-tested with an identified
|
|
@@ -187,7 +188,7 @@ pi install npm:gentle-pi@2.6.0
|
|
|
187
188
|
|
|
188
189
|
RDD remains opt-in. Enable it only through an explicit user decision with `/gentle:review-mode enable`; `status` lets you inspect the mode without changing it.
|
|
189
190
|
|
|
190
|
-
The source checkout's RDD integration installs Gentle AI only into its private `.gentle-ai/` directory. Darwin and Linux use pinned release assets with asset and executable SHA-256 verification (signed archives for source pin `v3.
|
|
191
|
+
The source checkout's RDD integration installs Gentle AI only into its private `.gentle-ai/` directory. Darwin and Linux use pinned release assets with asset and executable SHA-256 verification (signed archives for source pin `v3.4.0`; raw prerelease binaries only under a prerelease pin). Windows x64 and arm64 build the exact `v3.4.0` source tag with a local Go 1.25.10+ toolchain, a sealed Go environment, `GOTOOLCHAIN=local`, and `GOSUMDB=sum.golang.org`; it does not download Go automatically. Windows provenance is Go-toolchain plus SumDB evidence and postinstall tamper detection, **not** Authenticode or protection against a malicious joint binary-and-manifest replacement. Package-private locks coordinate cooperative concurrent or crashed installers; their tombstones fail closed. A malicious same-user process with write access to package-private `node_modules` is outside that protocol because it can already replace package code, binary, or manifest, and portable Node has no pathname-delete CAS. It never uses `PATH` or a global `gentle-ai` installation. For development or offline installs only, set `GENTLE_PI_SKIP_GENTLE_AI_INSTALL=1`; native review operations then fail closed with an actionable `package-local-binary-missing` error. To recover explicitly, if `GENTLE_PI_SKIP_GENTLE_AI_INSTALL` is set, remove or unset it before changing to the installed `gentle-pi` package directory. Then run `node scripts/install-gentle-ai.mjs`. This invokes the package-owned installer without relying on a global binary or npm configuration change. A missing binary can result from skipped lifecycle scripts, but does not prove that lifecycle scripts were disabled.
|
|
191
192
|
|
|
192
193
|
Recommended companion packages:
|
|
193
194
|
|
|
@@ -361,13 +362,13 @@ flowchart TD
|
|
|
361
362
|
|
|
362
363
|
VALIDATE is informational. Commit, push, PR, and release commands follow ordinary repository policy; RDD never authorizes, rewrites, consumes review state for, or blocks them. Dangerous-command safety and destructive-review consent remain independent.
|
|
363
364
|
|
|
364
|
-
For the source checkout, native contract pairing is exact: this adapter resolves only the integrity-verified package-local Gentle AI v3.
|
|
365
|
+
For the source checkout, native contract pairing is exact: this adapter resolves only the integrity-verified package-local Gentle AI v3.4.0 executable, independently hashes it, then negotiates `gentle-ai.review-integration/v2` outside the repository. Capabilities are cached by that executable digest. Every START, target status, FINALIZE, validate, and BIND-SDD request passes the same contract identifier. Negotiated envelopes decode exactly against the vendored schemas; `recover` routes only the provider-selected `action_disposition`, and optional additions require a future compatible schema/minor that the provider explicitly advertises and the consumer negotiates.
|
|
365
366
|
|
|
366
367
|
Contract `/v2` replaces the Base64 `candidate_diff` reviewer transport of `/v1` with immutable `base_tree`/`candidate_tree` plus an ordered `changed_path_manifest` and never an inline patch. `gentle-pi` negotiates `/v2` only, with no dual-lane fallback; the cutover landed as one atomic commit against gentle-ai v2.2.2 (tracked by the `migrate-review-integration-v2` change), and the `/v1` schemas stay packaged because the `/v2` schemas `$ref` into their fragments. This provider contract version is unrelated to Pi's own internal "compact-v2" review-authority naming used below — the shared digit is coincidental, not a version pairing.
|
|
367
368
|
|
|
368
369
|
Target status owns `current_target`, `unrelated`, `ambiguous`, and `corrupted` applicability and returns one native action. Pi does not reconstruct ordinary authority from provider-private files or choose a lineage from repository-wide history. Restart recovery rebuilds only the derived candidate view from the native Git/content projection, including intended-untracked paths, symlinks, and immutable gitlink identities. Native failure envelopes retain their exact mutation outcome, replayability, required inputs, request digest, and next action. After an unknown or lost mutating result, Pi calls target status before any replay decision and returns only the provider-declared action.
|
|
369
370
|
|
|
370
|
-
Once the source checkout's pinned gentle-ai runtime (currently v3.
|
|
371
|
+
Once the source checkout's pinned gentle-ai runtime (currently v3.4.0) has written review authority, rollback MUST preserve every native store and receipt and MUST NOT run a downgraded binary against that repository. Disable the Pi route or roll forward to a compatible authority-aware release instead; deleting authority data or reinstalling an older binary is not a rollback path.
|
|
371
372
|
|
|
372
373
|
### FINALIZE wrapper input
|
|
373
374
|
|
|
@@ -730,7 +731,7 @@ Profiles are named, switchable snapshots of the global agent-model routing from
|
|
|
730
731
|
|
|
731
732
|
Applying a profile writes `~/.pi/gentle-ai/models.json`, then reconciles agent frontmatter and `subagents.json` the same way `/gentle:models` does. A profile is a complete snapshot: every discoverable agent it omits returns to inherit, so routing materialized by a previous profile, by `/gentle:models`, or by a migration never survives a switch silently. The reconciliation happens on the next subagent launch, and that launch still routes with the previous routing — expect one launch of lag after switching. The active profile is persisted so `/gentle:profiles` reopens with the applied profile marked.
|
|
732
733
|
|
|
733
|
-
A profile also carries the orchestrator under the reserved routing key `orchestrator`. Applying a profile that defines it writes `defaultProvider`, `defaultModel`, and `defaultThinkingLevel` to Pi's global `settings.json` (preserving every other key; an unreadable `settings.json` aborts that part and is reported instead of being overwritten). Applying a profile without an `orchestrator` entry never moves the orchestrator, and `s` snapshots the currently effective orchestrator together with the routing. `orchestrator` is reserved: it is not a subagent name, is never written to `subagents.json`, and is not counted as a role.
|
|
734
|
+
A profile also carries the orchestrator under the reserved routing key `orchestrator`. Applying a profile that defines it writes `defaultProvider`, `defaultModel`, and `defaultThinkingLevel` to Pi's global `settings.json` (preserving every other key; an unreadable `settings.json` aborts that part and is reported instead of being overwritten) and switches the session you are in to that model and thinking level right away, so the orchestrator answers with the profile's model from the next turn. When the model is not in Pi's catalog or its provider has no authentication, the default for new sessions is still recorded and the apply note says this session kept its current model. Applying a profile without an `orchestrator` entry never moves the orchestrator, and `s` snapshots the currently effective orchestrator together with the routing. `orchestrator` is reserved: it is not a subagent name, is never written to `subagents.json`, and is not counted as a role.
|
|
734
735
|
|
|
735
736
|
The panel's current routing, the `current` seed, and `s` all read the routing in effect: `models.json` where it has an entry, and otherwise the `subagents.json` model profile or frontmatter routing the runtime actually resolves for that agent. A sparse `models.json` therefore never hides routing that is still live. When `profiles.json` is missing, the command seeds one profile named `current` captured from that effective routing, marked active only when it has routing entries. Profiles or routing entries dropped by normalization are named in a warning instead of being lost silently.
|
|
736
737
|
|
|
@@ -822,6 +823,7 @@ One limitation is worth stating. When a pinned profile omits an agent, that agen
|
|
|
822
823
|
| `/gentle:commands` | Opens the command palette (default `alt+k`): a curated, grouped menu (Configuration, Session, Diagnostics, SDD, Skills) of registered Gentle commands; search and run by label. |
|
|
823
824
|
| `/gentle:persona` | Switches global persona mode, with project override support. |
|
|
824
825
|
| `/gentle:background-subagents` | Shows or sets the managed background-subagents policy (`status\|enable\|disable`), naming the source that decided it. |
|
|
826
|
+
| `/gentle:double-esc-cancel` | Shows or sets the double-esc-cancel preference (`status\|enable\|disable`); no argument toggles it. |
|
|
825
827
|
| `/gentle:telemetry` | Shows or changes the local Gentle AI telemetry trigger (`status\|enable\|disable\|preview`). |
|
|
826
828
|
| `/gentle:review-mode` | Shows or sets the receipt-driven development mode (`status\|enable\|disable`); user-initiated only, Pi automation never toggles it. |
|
|
827
829
|
| `/gentle:banner` | Configures startup banner rose, text logo, and color preset. |
|
|
@@ -865,6 +867,38 @@ Both files use the strict shape `{"schema":"gentle-pi.background-subagents/v1","
|
|
|
865
867
|
|
|
866
868
|
Because the project file outranks the global one, `enable` still writes the global file but reports plainly when a project file keeps the effective policy unchanged. The resolved capability (`ready` or `absent`) reports whether `subagent_run` is actually callable in this session; a policy of `on` with capability `absent` means Gentle Agents is disabled or the retired subagents package is still installed.
|
|
867
869
|
|
|
870
|
+
### Esc behavior
|
|
871
|
+
|
|
872
|
+
The Gentle prompt matches Claude Code's Esc model on top of Pi's own. Four flows share the frame's single hint slot on the bottom rule, each decided by its own state:
|
|
873
|
+
|
|
874
|
+
1. **Working: cancel keeps the queue moving.** Esc aborts the running turn (a single Esc by default, or the confirming second Esc when double-esc-cancel below is enabled). Any steer or follow-up messages queued while the turn ran are sent as the next turn once the abort settles, instead of being dumped back into the editor for the user to notice and resend by hand; if another turn starts first (for example the user sends the restored draft before the abort has fully settled), the queued text simply waits and is sent once that turn settles, so it is never lost and never injected mid-turn. The user's own unsent draft stays in the editor untouched. Completed work before the abort is preserved, as Pi already does. Images inside a queued message are dropped, because Pi's own restore already drops them before this code ever sees the text. If Pi ever restores a shape this code does not recognize, Pi's own text is left exactly as written and nothing is dispatched, rather than guessing.
|
|
875
|
+
2. **Working: double-esc-cancel (opt-in, off by default).** While the prompt is working (autocomplete hidden), a single Esc still aborts the turn immediately by default, exactly like Pi's own escape. Once enabled, the first Esc is swallowed and the prompt frame shows `esc again to cancel`; a second Esc within 1000ms falls through so Pi's own `onEscape` performs the abort (and flow 1 above still applies to that second Esc). Letting the window expire treats the next Esc as a first press again.
|
|
876
|
+
3. **Idle with a draft: double Esc clears it.** With the prompt idle, autocomplete hidden, and non-empty editor text, the first Esc shows `esc again to clear` instead of doing nothing; a second Esc within 500ms, on the exact same text, adds the draft to history (recoverable with the Up arrow) and clears it. Editing the draft between the two presses starts a fresh first press on the new text instead of clearing the edit away. Letting the window expire treats the next Esc as a first press again. A bash-mode draft (starting with `!`) is never touched by this gate; Pi's own bash-mode Esc keeps deciding it.
|
|
877
|
+
4. **Idle, empty editor: unchanged.** Pi's own idle double-Esc (`/tree` or `/fork`, 500ms) keeps deciding this case entirely; the prompt never intercepts it.
|
|
878
|
+
|
|
879
|
+
Overlays, autocomplete cancel, and bash mode all consume the first Esc locally and are unaffected by any of the four flows above.
|
|
880
|
+
|
|
881
|
+
Double-esc-cancel's policy is user-owned: only an explicit `/gentle:double-esc-cancel enable` or `disable` writes it, and Pi automation never toggles it.
|
|
882
|
+
|
|
883
|
+
```text
|
|
884
|
+
/gentle:double-esc-cancel Toggle the effective policy (on -> off, off -> on).
|
|
885
|
+
/gentle:double-esc-cancel status Report the effective policy and the deciding source.
|
|
886
|
+
/gentle:double-esc-cancel enable Write "on" to the global file.
|
|
887
|
+
/gentle:double-esc-cancel disable Write "off" to the global file.
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
Unlike `/gentle:background-subagents`, no argument here reports status; it toggles the effective policy instead, since this preference has only one file layer and nothing else can outrank a write.
|
|
891
|
+
|
|
892
|
+
Three sources can decide the policy, and the first hit wins:
|
|
893
|
+
|
|
894
|
+
| Priority | Source | Notes |
|
|
895
|
+
| -------- | ------------------------------------------- | ------------------------------------------------------------ |
|
|
896
|
+
| 1 | `<configHome>/double-esc-cancel.json` | Global file, written by `enable`/`disable`. `configHome` honors `GENTLE_PI_CONFIG_HOME` and defaults to `~/.pi/gentle-ai`. There is no project-level override: this preference changes what a keypress does, and per-project overrides would make the same key do two different things depending on which repo is open. |
|
|
897
|
+
| 2 | `GENTLE_PI_DOUBLE_ESC_CANCEL` | Exactly `on` or `off`. Any other value is ignored, and it decides only when the global file does not exist. |
|
|
898
|
+
| 3 | Built-in default | `off`. |
|
|
899
|
+
|
|
900
|
+
The file uses the strict shape `{"schema":"gentle-pi.double-esc-cancel/v1","policy":"on"}`. A file that is present but malformed fails closed to `off` instead of falling through to the environment variable, and the command reports that case as a warning instead of an ordinary `off`. The extension resolves the policy once at startup and updates it in memory when the command runs; the prompt never re-reads the file on every keypress.
|
|
901
|
+
|
|
868
902
|
Startup banner settings remain global in `banner.json` under `GENTLE_PI_CONFIG_HOME` (default `~/.pi/gentle-ai`). Existing `showRose` and `showTextLogo` opt-outs independently control the main startup artwork; both default to enabled. Changes apply on the next session or `/reload`. Color presets are `pink` (default), `cyan`, `yellow`, and `green`. The static sidebar heading is independent of these preferences and follows the active theme.
|
|
869
903
|
|
|
870
904
|
Startup flag:
|
|
@@ -8,20 +8,32 @@ Gentle Pi is a transport consumer, not a review authority. Gentle AI generates t
|
|
|
8
8
|
|
|
9
9
|
| Component | Responsibility |
|
|
10
10
|
| --- | --- |
|
|
11
|
-
|
|
|
12
|
-
| Host coordinator | Executes the exact Go-issued materialize/submission tokens,
|
|
11
|
+
| In-process reviewer completion | A pure completion (`lib/inprocess-reviewer.ts`): resolves the lens's "provider/id" selection through the live model registry, authenticates through the registry's own resolver, and completes the Go-materialized prompt as one frozen user message — no systemPrompt, no tools, no session, no extension hooks, no child process. |
|
|
12
|
+
| Host coordinator | Executes the exact Go-issued materialize/submission tokens, runs the completion through the caller-supplied model registry, and submits its result only through the supplied token. |
|
|
13
13
|
| Gentle AI (Go) | Go owns worktree, lineage, candidate freeze, lens selection, correction, validator, approval burn, and review semantics. Delivery commands remain ordinary repository-policy operations. |
|
|
14
14
|
|
|
15
|
-
The
|
|
15
|
+
The completion does not parse bindings, select work, rebuild prompts, inspect repository state, retry, classify results, or create authority. The coordinator does not infer a command or replace a provider-issued token. The package has no durable receipt or policy authority.
|
|
16
16
|
|
|
17
17
|
## Transport behavior
|
|
18
18
|
|
|
19
19
|
1. Gentle AI emits an opaque materialization or submission token for the selected Pi runtime.
|
|
20
|
-
2. The host coordinator executes that exact token and gives only the materialized bytes to the
|
|
21
|
-
3. The
|
|
22
|
-
4. The coordinator sends
|
|
20
|
+
2. The host coordinator executes that exact token and gives only the materialized bytes to the completion, as the single user message of one frozen prompt.
|
|
21
|
+
3. The completion resolves its "provider/id" selection through the live model registry, authenticates, and returns the completion's text to the coordinator. A run that produced no text, attempted a tool call, or exceeded its bound fails typed with evidence (the completion's stop reason, its aborted/timed-out signal, or its reviewer model, depending on the code); a run over the fixed output bound also fails typed.
|
|
22
|
+
4. The coordinator sends that text only through the exact Go-issued submission token.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
## Reviewer completion selection (user-owned)
|
|
25
|
+
|
|
26
|
+
There is no model flag, no extension allowlist, and no ambient default model: the in-process completion resolves entirely through the caller-supplied model registry (`ctx.modelRegistry`), and a missing registry or an unconfigured lens is refused typed as `reviewer-config-invalid` before materialize ever runs, never a mid-review transport failure.
|
|
27
|
+
|
|
28
|
+
- **Lens model and thinking level** — the capture path reads the lens's entry from the agent model routing config (`review-risk`, `review-resilience`, `review-readability`, `review-reliability`) and forwards its `model` and `thinking` fields verbatim to the completion. A routing entry with no configured model is refused typed, naming the routing key — the completion never falls back to another provider or an ambient default.
|
|
29
|
+
|
|
30
|
+
A typed reviewer refusal fails closed. The coordinator reports the refusal — including the completion's evidence — without an agentless lifecycle fallback, local retry policy, synthetic result, or alternate approval path.
|
|
31
|
+
|
|
32
|
+
## Refuter and targeted validator
|
|
33
|
+
|
|
34
|
+
The refuter and targeted-validator roles are host-mediated in-process completions too, on a provider that advertises the v9 role contract: the collect input carries `--materialize=true` and a provider-owned submission descriptor, exactly like a lens capture-result materialize slot — materialize, complete in-process, then submit through the exact `--input` form. Their entries in the agent model routing config, `review-refuter` and `review-validator`, select the model and thinking level the same way `review-<lens>` does for a lens; a missing entry is refused typed, naming that key, before materialize ever runs.
|
|
35
|
+
|
|
36
|
+
An older provider that has not advertised the v9 role contract still renders each role as a self-contained vector (binding tokens plus `--agent=pi --execute=true`, no submission): executing that exact vector makes Go materialize the role prompt, run its own locked-down pi subprocess, and admit the verdict itself. The host still accepts this compatibility form unchanged.
|
|
25
37
|
|
|
26
38
|
## Dynamic contract delivery
|
|
27
39
|
|
|
@@ -29,17 +41,19 @@ Package static assets intentionally omit lifecycle instructions, candidate routi
|
|
|
29
41
|
|
|
30
42
|
## Integration constraints
|
|
31
43
|
|
|
32
|
-
- Keep
|
|
44
|
+
- Keep the completion frozen: raw prompt bytes in as one user message, the completion's text or a typed, evidenced error out.
|
|
33
45
|
- Preserve Go-issued materialize and submission tokens exactly; they are the only authority-bearing inputs the host may execute.
|
|
34
|
-
-
|
|
46
|
+
- Keep the reviewer completion selection user-owned: it never invents a model or falls back to another provider or an ambient default; it only forwards the validated caller-owned selection and thinking level, or refuses typed.
|
|
47
|
+
- Treat a reviewer refusal as unavailable evidence, never as an approval, completion, or permission to substitute a local workflow.
|
|
35
48
|
- Keep command safety and user interaction in the host, without interpreting provider authority state.
|
|
36
49
|
- Keep durable review state, admissions, correction accounting, and approvals in Gentle AI. Keep delivery decisions in ordinary repository policy.
|
|
37
50
|
|
|
38
51
|
## Review checklist
|
|
39
52
|
|
|
40
|
-
- [ ] The
|
|
53
|
+
- [ ] The completion still takes raw prompt bytes as one frozen user message and returns text or a typed, evidenced error — no child process, no extension allowlist.
|
|
41
54
|
- [ ] The coordinator executes only exact Go-issued materialize/submission tokens.
|
|
42
|
-
- [ ]
|
|
55
|
+
- [ ] The reviewer completion selection stays refused typed unless the caller-owned model registry and a configured lens selection validate.
|
|
56
|
+
- [ ] Typed reviewer refusal remains fail-closed.
|
|
43
57
|
- [ ] No package code or static prompt uses review authority to decide, authorize, rewrite, or block delivery commands.
|
|
44
58
|
|
|
45
59
|
← [Back to README](../README.md)
|
|
@@ -13,12 +13,11 @@ import os from "node:os";
|
|
|
13
13
|
import { join, resolve, isAbsolute, sep } from "node:path";
|
|
14
14
|
import { createBashToolDefinition, createLocalBashOperations, type BashOperations, keyHint, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import { Text, type TUI } from "@earendil-works/pi-tui";
|
|
16
|
-
import { sidebarPart } from "../lib/shell-sidebar.ts";
|
|
17
16
|
import { invalidateSidebar } from "../lib/shell-sidebar-layout.ts";
|
|
18
17
|
import { createCompletionQueue } from "../lib/agents-completion-delivery.ts";
|
|
19
18
|
import { AGENT_MODE, discoverAgents, parseAgentDefinition, loadAgentsConfig, resolveAgentProfile, withPinnedModelProfiles, type AgentDefinition, type AgentMode } from "../lib/agents-config.ts";
|
|
20
19
|
import { resolveBackgroundSubagentsPolicy } from "../lib/background-subagents-policy.ts";
|
|
21
|
-
import { isFinished, TASK_STATUS, TaskStore, type AskRequest, type TaskRecord } from "../lib/agents-protocol.ts";
|
|
20
|
+
import { isFinished, TASK_EVENT, TASK_STATUS, TaskStore, type AskRequest, type TaskRecord } from "../lib/agents-protocol.ts";
|
|
22
21
|
import { AgentRunner, piCommand, abortReasonText, plannedCommands, type RemediationPlan, type RemediationScope, REMEDIATION_PLAN_ENV, parseRemediationPlan, type AskAnswer, type RunnerDeps, type SddChangeSelection, type TaskRequest } from "../lib/agents-runner.ts";
|
|
23
22
|
import { ChildMessenger, type IpcEndpoint } from "../lib/agents-messaging.ts";
|
|
24
23
|
import { ActiveSessionClient, ActiveSessionListener, SessionPresenceRegistry, type PresenceRecord, type ReceivedNotification, type SentNotification, type SessionPresenceCandidate } from "../lib/agents-session-transport.ts";
|
|
@@ -499,6 +498,9 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
499
498
|
let renderQueued = false;
|
|
500
499
|
let cancelClock: (() => void) | undefined;
|
|
501
500
|
const ownedTaskIds = new Set<string>();
|
|
501
|
+
// One diagnostic note per (task, guard): a dropped mutation says why once,
|
|
502
|
+
// not once per file, so a chatty child cannot flood its own thread.
|
|
503
|
+
const droppedAttributionGuards = new Set<string>();
|
|
502
504
|
const stoppingTaskIds = new Set<string>();
|
|
503
505
|
const yieldedTaskIds = new Set<string>();
|
|
504
506
|
const metricsNow = deps.metricsNow ?? (() => performance.now());
|
|
@@ -744,16 +746,43 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
744
746
|
}
|
|
745
747
|
},
|
|
746
748
|
onSuccessfulMutation: (task, tool) => {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
749
|
+
// Guard chain and posture are unchanged: only owned tasks of the
|
|
750
|
+
// active parent, inside registered roots, ever relay. The notes only
|
|
751
|
+
// explain a drop -- they never widen or narrow what gets attributed.
|
|
752
|
+
// The cheap session/ownership guards decide before any worktree
|
|
753
|
+
// resolution, so a foreign or stale mutation never reaches git.
|
|
754
|
+
let root: string | undefined;
|
|
755
|
+
let childRoot: string | undefined;
|
|
756
|
+
const noteDrop = (guard: string) => {
|
|
757
|
+
const key = `${task.id}:${guard}`;
|
|
758
|
+
if (droppedAttributionGuards.has(key)) return;
|
|
759
|
+
droppedAttributionGuards.add(key);
|
|
760
|
+
try { store.apply(task.id, { type: TASK_EVENT.NOTE, text: `changes not attributed: ${guard} (root=${root ?? "unknown"}, child=${childRoot ?? "unknown"})` }, deps.now()); }
|
|
761
|
+
catch { /* A note is best-effort explanation; it must never block the guard it is explaining. */ }
|
|
762
|
+
};
|
|
763
|
+
if (!sessions || !worktrees) return noteDrop("session-inactive");
|
|
764
|
+
if (task.parentSessionId !== activeSessionId()) return noteDrop("parent-session-mismatch");
|
|
765
|
+
if (!ownedTaskIds.has(task.id)) return noteDrop("not-owned");
|
|
766
|
+
root = deps.resolveWorktree(tool.path, task.cwd)?.root;
|
|
767
|
+
childRoot = deps.resolveWorktree(task.cwd, task.cwd)?.root;
|
|
768
|
+
if (!root) return noteDrop("root-unresolved");
|
|
769
|
+
if (root !== childRoot) return noteDrop("root-mismatch");
|
|
770
|
+
if (!worktrees.roots().includes(root)) return noteDrop("root-not-registered");
|
|
771
|
+
if (!tool.evidence) {
|
|
772
|
+
noteDrop("evidence-missing");
|
|
773
|
+
} else if (tool.evidence.root !== root) {
|
|
774
|
+
noteDrop("evidence-root-mismatch");
|
|
775
|
+
} else {
|
|
776
|
+
let path = tool.path.replace(/^@/, "");
|
|
777
|
+
if (path === "~" || path.startsWith("~/")) path = os.homedir() + path.slice(1);
|
|
778
|
+
let resolvedPath: string | undefined;
|
|
752
779
|
try {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
}
|
|
780
|
+
resolvedPath = realpathSync(resolve(task.cwd, path));
|
|
781
|
+
} catch {
|
|
782
|
+
noteDrop("evidence-path-unreadable");
|
|
783
|
+
}
|
|
784
|
+
if (resolvedPath === resolve(root, tool.evidence.path)) pi.events.emit(SESSION_CHANGE_RELAY, { sessionId: task.parentSessionId, evidence: { ...tool.evidence, id: `${task.id}:${tool.toolCallId}` } });
|
|
785
|
+
else if (resolvedPath !== undefined) noteDrop("evidence-path-mismatch");
|
|
757
786
|
}
|
|
758
787
|
recordReviewMutation(pi, sessions, root, { source: "subagent", taskId: task.id, toolName: tool.toolName, toolCallId: tool.toolCallId });
|
|
759
788
|
},
|
|
@@ -895,6 +924,33 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
895
924
|
return stored?.task;
|
|
896
925
|
};
|
|
897
926
|
|
|
927
|
+
// Restores this exact session's own finished subagents as visible history
|
|
928
|
+
// on an explicit resume, or on startup into a session that already has
|
|
929
|
+
// entries -- never on new, fork, or reload (see the session_start handler's
|
|
930
|
+
// preexisting check below, which is the actual gate). Marked in
|
|
931
|
+
// restoredTaskIds like any other disk restoration, so it stays
|
|
932
|
+
// non-cancellable (ownedTaskIds never gained the id either way) and is
|
|
933
|
+
// skipped if the id is somehow already live.
|
|
934
|
+
const restoreSessionHistory = async (ctx: ExtensionContext, sessionId: string): Promise<void> => {
|
|
935
|
+
let history: Awaited<ReturnType<typeof loadHistory>>;
|
|
936
|
+
try {
|
|
937
|
+
history = await loadHistory(tasksDir);
|
|
938
|
+
} catch {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
// The session may have moved on while disk was read; a stale restore
|
|
942
|
+
// must never land in the wrong session's store.
|
|
943
|
+
if (ctx.sessionManager.getSessionId() !== sessionId) return;
|
|
944
|
+
// Fire-and-forget from session_start: a throwing summary subscriber must
|
|
945
|
+
// never surface as an unhandled rejection. History is best-effort.
|
|
946
|
+
try {
|
|
947
|
+
for (const { task, thread } of history) {
|
|
948
|
+
if (task.parentSessionId !== sessionId) continue;
|
|
949
|
+
if (store.restore(task, thread)) restoredTaskIds.add(task.id);
|
|
950
|
+
}
|
|
951
|
+
} catch { /* Partial history is acceptable; the live session keeps running. */ }
|
|
952
|
+
};
|
|
953
|
+
|
|
898
954
|
const openOverlay = async (ctx: ExtensionContext) => {
|
|
899
955
|
if (!ctx.hasUI) return;
|
|
900
956
|
if (ctx.mode !== "tui") {
|
|
@@ -974,19 +1030,19 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
974
1030
|
ui = ctx.hasUI ? ctx.ui : undefined;
|
|
975
1031
|
sessions = ctx.sessionManager;
|
|
976
1032
|
tickClock();
|
|
1033
|
+
// Agents is not a rail part: the fullscreen sidebar only suppresses
|
|
1034
|
+
// bottom components registered through sidebarPart, and this widget is
|
|
1035
|
+
// the only Agents surface in every mode, so it stays a plain component.
|
|
977
1036
|
ui?.setWidget(AGENTS_WIDGET_KEY, (tui, theme) => {
|
|
978
1037
|
host = tui;
|
|
979
1038
|
sidebarTui = tui;
|
|
980
|
-
return
|
|
1039
|
+
return {
|
|
981
1040
|
render(width: number) {
|
|
982
1041
|
const lines = renderAgentsCard(visibleTasks(), theme, width, deps.now(), { collapsed, collapseKey, maxRows: widgetRows(tui.terminal?.rows), viewKey });
|
|
983
1042
|
return lines.length === 0 ? [] : [...lines, ""];
|
|
984
1043
|
},
|
|
985
1044
|
invalidate() {},
|
|
986
|
-
}
|
|
987
|
-
render: (width) => renderAgentsCard(visibleTasks(), theme, width, deps.now(), { collapsed, collapseKey, viewKey }),
|
|
988
|
-
invalidate() {},
|
|
989
|
-
});
|
|
1045
|
+
};
|
|
990
1046
|
});
|
|
991
1047
|
};
|
|
992
1048
|
|
|
@@ -1362,13 +1418,25 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1362
1418
|
});
|
|
1363
1419
|
}
|
|
1364
1420
|
|
|
1365
|
-
pi.on("session_start", async (
|
|
1421
|
+
pi.on("session_start", async (event, ctx) => {
|
|
1366
1422
|
// A resumed, reloaded, or replaced session starts with an empty completion
|
|
1367
1423
|
// queue so nothing pending from another session can replay here.
|
|
1368
1424
|
completions.dropAll();
|
|
1369
1425
|
presence?.dispose();
|
|
1370
1426
|
registryFor(ctx);
|
|
1371
1427
|
showWidget(ctx);
|
|
1428
|
+
// An explicit in-session /resume always brings this session's own
|
|
1429
|
+
// finished subagents back from disk. Pi also reports "startup" (not
|
|
1430
|
+
// "resume") when the CLI is launched directly into an existing session
|
|
1431
|
+
// file, e.g. --continue or the --resume picker (agent-session.js:152
|
|
1432
|
+
// defaults to "startup"); that case restores too, but only when the
|
|
1433
|
+
// session actually has prior entries -- a brand-new session can also be
|
|
1434
|
+
// announced as "startup", and a fresh session has none. "new", "fork",
|
|
1435
|
+
// and "reload" never restore here, matching the existing on-demand
|
|
1436
|
+
// resolveTask path for anything else.
|
|
1437
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
1438
|
+
const preexisting = event.reason === "resume" || (event.reason === "startup" && ctx.sessionManager.getEntries().length > 0);
|
|
1439
|
+
if (preexisting && sessionId) void restoreSessionHistory(ctx, sessionId);
|
|
1372
1440
|
try {
|
|
1373
1441
|
presence = PresencePublisher.start({ profile: agentHome, sessionId: activeSessionId() ?? "",
|
|
1374
1442
|
label: ctx.sessionManager.getSessionName?.() || ctx.sessionManager.getCwd().split(/[\\/]/).pop() || "Orchestrator", activity: [] });
|