pi-herdr-agents 3.0.3 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +19 -1
- package/CONTEXT.md +33 -5
- package/README.md +166 -14
- package/RELEASING.md +4 -2
- package/docs/README.md +9 -0
- package/docs/worktree-subagents.md +1 -0
- package/maestro/adapters/pi/launch.ts +26 -4
- package/maestro/adapters/pi/pi-harness-adapter.ts +4 -0
- package/maestro/core/config/model-config.ts +83 -10
- package/maestro/core/config/sidebar-config.ts +63 -0
- package/maestro/core/config/task-model-init.ts +208 -13
- package/maestro/core/config/task-model-types.ts +19 -1
- package/maestro/core/config/type-guards.ts +5 -0
- package/maestro/runtime/delegated-marker.ts +206 -0
- package/maestro/runtime/index.ts +2 -1
- package/maestro/runtime/pi-run-session.ts +58 -7
- package/maestro/runtime/task-model-init.ts +106 -9
- package/maestro/surfaces/herdr/herdr-socket.ts +165 -0
- package/package.json +6 -6
- package/pi-extension/subagents/index.ts +159 -13
- package/plugins/sidebar/README.md +242 -0
- package/plugins/sidebar/herdr-plugin.toml +21 -0
- package/plugins/sidebar/sidebar.mjs +238 -0
- package/skills/pi-herdr-agents/SKILL.md +10 -3
package/AGENTS.md
CHANGED
|
@@ -27,12 +27,13 @@ Former bundled roles, `/plan`, its plan skill, and `/skill:orchestrate` now belo
|
|
|
27
27
|
- `pi-extension/subagents/index.ts` — Pi composition root: public tools/commands, role-pack event bridge, host policy, widgets, and parent delivery
|
|
28
28
|
- `pi-extension/subagents/model-registry.ts`, `config-path.ts` — permanent host-local SDK capability glue and configuration-path conventions
|
|
29
29
|
- `maestro/core/` — seam interfaces and types; activity/lifecycle/status projection, routing, wake-ups, and supervision
|
|
30
|
+
- `plugins/sidebar/` — optional standalone Node companion Herdr plugin; it imports nothing from the extension
|
|
30
31
|
- `maestro/core/roles/discovery.ts`, `maestro/core/config/` — role parsing/discovery, injected-directory config loaders, and task-model init prompt construction
|
|
31
32
|
- `maestro/core/worktree.ts`, `worktree-cleanup.ts` — manifest schema/state, handoff types, and cleanup eligibility/formatting
|
|
32
33
|
- `maestro/adapters/pi/` — `PiHarnessAdapter`, launch transactions, completion evidence, session I/O, activity files, SDK model glue, and task-model registry projection
|
|
33
34
|
- `maestro/adapters/pi/child/subagent-done.ts` — child protocol: `caller_ping`, `subagent_done`, and activity recording
|
|
34
|
-
- `maestro/surfaces/herdr/` — `HerdrSurfaceProvider`, Herdr CLI driver, and terminal scripts/placement
|
|
35
|
-
- `maestro/runtime/` — `RunSession`, Pi composition, run ownership, controls/retries, observation, delivery-gated cleanup, worktree operations/handoff, and task-model init composition
|
|
35
|
+
- `maestro/surfaces/herdr/` — `HerdrSurfaceProvider`, Herdr CLI driver, and terminal scripts/placement, plus `herdr-socket.ts` for socket pane metadata
|
|
36
|
+
- `maestro/runtime/` — `RunSession`, Pi composition, run ownership, controls/retries, observation, delivery-gated cleanup, worktree operations/handoff, and task-model init composition, plus `delegated-marker.ts` for delegated-child sidebar markers
|
|
36
37
|
- `maestro/adapters/fake/`, `maestro/surfaces/fake/`, `test/maestro/` — conformance fakes, seam tests, and the dependency-rule test
|
|
37
38
|
- `CONTEXT.md` — orchestration-domain glossary
|
|
38
39
|
- `docs/adr/` — hard-to-reverse architectural decisions; [ADR-0012](docs/adr/0012-adopt-maestro-seams-in-repo.md) records the maestro seams decision
|
package/CHANGELOG.md
CHANGED
|
@@ -7,10 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
9
9
|
|
|
10
|
-
## [v3.0
|
|
10
|
+
## [v3.2.0](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.1.0...v3.2.0)
|
|
11
|
+
|
|
12
|
+
### Commits
|
|
13
|
+
|
|
14
|
+
- feat: add optional Herdr sidebar focus plugin [`9d2fb03`](https://github.com/giuseppecrj/pi-herdr-agents/commit/9d2fb039b006e0d389d19b1105dd54e277f695ba)
|
|
15
|
+
- fix: update dependencies to resolve security advisories [`028ebd6`](https://github.com/giuseppecrj/pi-herdr-agents/commit/028ebd62fa8c8e967c15a9677c0d4a1abc52505a)
|
|
16
|
+
- docs: match sidebar README to main package style [`918db68`](https://github.com/giuseppecrj/pi-herdr-agents/commit/918db68bd5f56782198182aa48eb6586a46a0ef7)
|
|
17
|
+
|
|
18
|
+
## [v3.1.0](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.3...v3.1.0) - 2026-10-09
|
|
19
|
+
|
|
20
|
+
### Commits
|
|
21
|
+
|
|
22
|
+
- feat: route task-model init through pack approval [`1b6bacd`](https://github.com/giuseppecrj/pi-herdr-agents/commit/1b6bacd0b5eecb6f83ffba1d5a5ee57a8b0fa944)
|
|
23
|
+
- test: dispatch all Pi lifecycle handlers in placement fixtures [`f96e59d`](https://github.com/giuseppecrj/pi-herdr-agents/commit/f96e59de8890d39ce6c79be8de245d287fc2485e)
|
|
24
|
+
- chore: release v3.1.0 [`0bb8bcd`](https://github.com/giuseppecrj/pi-herdr-agents/commit/0bb8bcdc328d152c5cab5eaaf4d261f109055ee4)
|
|
25
|
+
|
|
26
|
+
## [v3.0.3](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.2...v3.0.3) - 2026-10-08
|
|
11
27
|
|
|
12
28
|
### Merged
|
|
13
29
|
|
|
30
|
+
- Release 3.0.3 [`#82`](https://github.com/giuseppecrj/pi-herdr-agents/pull/82)
|
|
14
31
|
- test(integration): re-read worktree row before asserting its open workspace [`#81`](https://github.com/giuseppecrj/pi-herdr-agents/pull/81)
|
|
15
32
|
- Fix Pi readiness check when Pi rewrites argv [`#80`](https://github.com/giuseppecrj/pi-herdr-agents/pull/80)
|
|
16
33
|
- Fix tsc errors and git-version-sensitive index test; document lint Node floor [`#79`](https://github.com/giuseppecrj/pi-herdr-agents/pull/79)
|
|
@@ -25,6 +42,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
|
25
42
|
- fix: settle stranded supervision waits and test wake retain accounting [`d18df1c`](https://github.com/giuseppecrj/pi-herdr-agents/commit/d18df1cb83bedf86951d2f9702a4a6185b44a8e8)
|
|
26
43
|
- test: bound the identity-capture loop anchor and close supervision test gaps [`4a0e71a`](https://github.com/giuseppecrj/pi-herdr-agents/commit/4a0e71a97ebc9ba3f21a5377504f24c3d837d9a2)
|
|
27
44
|
- Treat an unreadable Pi environment as unproven, not fatal [`b69151a`](https://github.com/giuseppecrj/pi-herdr-agents/commit/b69151ae85bd13c45bdf3e980c0b36e315c7c863)
|
|
45
|
+
- chore: release v3.0.3 [`1c75adb`](https://github.com/giuseppecrj/pi-herdr-agents/commit/1c75adb0864af9b922d1471734b086943435f238)
|
|
28
46
|
- test(worktree-cleanup): make the index-restore test hermetic and deterministic [`16840f6`](https://github.com/giuseppecrj/pi-herdr-agents/commit/16840f64b9481983cf44fc928061fd4e6822434d)
|
|
29
47
|
- test(worktree-cleanup): clear inherited git repository variables in the fixture [`323e1fb`](https://github.com/giuseppecrj/pi-herdr-agents/commit/323e1fbf6e6240eb5e7000d63fc84e23b391259a)
|
|
30
48
|
- test(worktrees): expect launch snapshots in scoped inventory [`f733040`](https://github.com/giuseppecrj/pi-herdr-agents/commit/f73304088c8a781b1ed7333a5a09175ec7c896e9)
|
package/CONTEXT.md
CHANGED
|
@@ -65,12 +65,30 @@ taken first stays authoritative.
|
|
|
65
65
|
_Avoid_: Interrupt, suppression, persistent stop, pane-close fallback
|
|
66
66
|
|
|
67
67
|
**Process identity**:
|
|
68
|
-
A
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
A child's Pi process named by immutable kernel facts (PID, start time, boot,
|
|
69
|
+
and PID namespace), recorded by the child at launch and verified by the parent
|
|
70
|
+
against the Herdr pane. Every managed worktree child records it; other children
|
|
71
|
+
record it when sidebar markers are on. A worktree cancel signals and judges
|
|
72
|
+
exit only by it, and a delegated marker renews only while it is alive.
|
|
72
73
|
_Avoid_: argv match, foreground process, shell visibility
|
|
73
74
|
|
|
75
|
+
**Delegated marker**:
|
|
76
|
+
The display-only pane token `piha_delegated_v1=live` that a parent reports on
|
|
77
|
+
a delegated child's pane when `sidebar.enabled` is on. It means the parent
|
|
78
|
+
classified the pane's process as its child within the last 15 seconds, not
|
|
79
|
+
that the process is alive when Herdr draws the row. One stable source, a
|
|
80
|
+
15-second TTL, and increasing sequence numbers bound a leftover marker;
|
|
81
|
+
renewal requires the original process identity. A `/worktree` handoff is
|
|
82
|
+
never marked.
|
|
83
|
+
_Avoid_: liveness proof, ownership record, security boundary, per-run source
|
|
84
|
+
|
|
85
|
+
**Sidebar Focus**:
|
|
86
|
+
The Herdr agent view that the optional `pi-herdr-agents.sidebar` plugin sets
|
|
87
|
+
on request. It hides rows whose delegated marker is `live` while their status
|
|
88
|
+
is idle, working, or done, and keeps blocked, unknown, and unmarked rows. All
|
|
89
|
+
clears only this plugin's view and reports a view that another tool owns.
|
|
90
|
+
_Avoid_: hiding processes, notification filter, automatic view, popup
|
|
91
|
+
|
|
74
92
|
**Pi startup confirmation**:
|
|
75
93
|
The bounded check that a `/worktree` handoff's Pi is running before its
|
|
76
94
|
workspace is focused: a Pi process in the root pane with the worktree cwd whose
|
|
@@ -210,7 +228,9 @@ architecture to planning and diagnosis, coding to workers, review to reviewers,
|
|
|
210
228
|
QA to software and test runners, and docs to documentation workers. Categories
|
|
211
229
|
describe work, not complexity. `/subagents-init [preferences]` drafts them from
|
|
212
230
|
the active extension-loaded registry's synchronous snapshot and existing saved
|
|
213
|
-
choices, with source-based research when available. A
|
|
231
|
+
choices, with source-based research when available. A single loaded extension
|
|
232
|
+
that offers on the task-model init approval event owns approval and the write
|
|
233
|
+
instead of the host writer. A dynamic provider awaiting
|
|
214
234
|
its initial catalog refresh might be absent. `task:<category>` is a subagent
|
|
215
235
|
model selector, not a command or parent model change. Ordered authenticated
|
|
216
236
|
candidate plans resolve before launch; ordinary nonpersistent runs can retry
|
|
@@ -227,6 +247,14 @@ the independence boundary; project policy may separately require a
|
|
|
227
247
|
different provider.
|
|
228
248
|
_Avoid_: Generic tier, reviewer-family enforcement, per-step routing
|
|
229
249
|
|
|
250
|
+
**Ranking basis**:
|
|
251
|
+
What a task-model proposal's ranking rests on, as the proposing model submitted
|
|
252
|
+
it: `registry-only`, or `research` with the http(s) sources consulted in that
|
|
253
|
+
run, how each influenced the ranking, and the remaining uncertainty. It travels
|
|
254
|
+
with one write for review and is never saved; `tasksMeta.method` keeps its kind.
|
|
255
|
+
Code checks its shape, not that a source was read.
|
|
256
|
+
_Avoid_: Verified research, evidence store
|
|
257
|
+
|
|
230
258
|
**Loop template**:
|
|
231
259
|
A future reusable orchestration definition beside `models`, describing stages,
|
|
232
260
|
task categories, and a termination/report contract. Loop templates are not
|
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ Delegate investigation, implementation, and review without blocking the parent s
|
|
|
17
17
|
- **Conversation handoff** — continue the active Pi conversation in a new worktree with `/worktree` while preserving the parent session.
|
|
18
18
|
- **Pack-neutral roles** — use project or global definitions and installable role packs; this package ships no default roles or workflows.
|
|
19
19
|
- **Persistent specialists** — retain one policy-bound Pi session for sequential, turn-based tasks.
|
|
20
|
+
- **Optional sidebar focus** — the companion [Pi Herdr Agents Sidebar](plugins/sidebar/README.md) Herdr plugin hides marked delegated children from Herdr's Agents view while you run Focus. Markers are display-only and follow the pane, so see its [limits](plugins/sidebar/README.md#reusing-a-delegated-pane).
|
|
20
21
|
|
|
21
22
|
## Requirements
|
|
22
23
|
|
|
@@ -57,7 +58,7 @@ Restart or `/reload` Pi after installation. Review package source before install
|
|
|
57
58
|
- [Requirements](#requirements), [Install](#install), [Safety and uninstall](#safety-and-uninstall)
|
|
58
59
|
- [Quick start](#quick-start), [Release notes](#release-notes), [How it works](#how-it-works)
|
|
59
60
|
- [What's Included](#whats-included): tools, commands, the operating skill, role packs
|
|
60
|
-
- [Async Subagent Flow](#async-subagent-flow): status, configuration, model routing
|
|
61
|
+
- [Async Subagent Flow](#async-subagent-flow): status, configuration, model routing, [sidebar markers](#sidebar-markers)
|
|
61
62
|
- [Spawning Subagents](#spawning-subagents), [Persistent specialists](#persistent-specialists), [Interrupting](#interrupting-a-running-subagent), [Cancelling](#cancelling-a-running-subagent)
|
|
62
63
|
- [The `/worktree` Workflow](#the-worktree-workflow), [Custom Agents](#custom-agents), [Tool Access Control](#tool-access-control)
|
|
63
64
|
- [Development](#development), [License](#license)
|
|
@@ -67,7 +68,8 @@ Restart or `/reload` Pi after installation. Review package source before install
|
|
|
67
68
|
- Child agents are real Pi processes running with your user account's permissions, inside Herdr panes. Worktrees isolate Git checkouts, not processes or permissions.
|
|
68
69
|
- The extension creates Herdr panes, tabs and managed worktrees only when a launch asks for them. It never pushes, merges, opens pull requests, deletes branches or removes worktrees on its own; cleanup is an explicit parent action ([worktree cleanup](#explicit-worktree-cleanup)).
|
|
69
70
|
- What it writes: `$PI_CODING_AGENT_DIR/herdr-agents/config.json` (only through `/subagents-init` or the writer tool, never on startup); per-launch artifacts under the parent session's `artifacts/<session-id>/` directory beside Pi's session store, which hold the child's full task text and any `systemPrompt` as Markdown files, activity snapshots and worktree manifests; and the child's own Pi session file. A managed worktree that carries its own `.pi/agent` directory receives that child's session inside the checkout. Treat task text as potentially sensitive when you share or inspect those files.
|
|
70
|
-
-
|
|
71
|
+
- Managed worktree children record their PID, process start time, boot ID, and PID namespace in `<session>.process.json` beside their session. With [`sidebar.enabled`](#sidebar-markers), every other child records the same facts, and a resumed child records them under `artifacts/<session-id>/process-identity/`. The parent then reports one display-only pane token, `piha_delegated_v1`, on each child's pane through the Herdr socket. With the setting off, the extension writes no pane metadata.
|
|
72
|
+
- To uninstall: first list and remove any retained worktrees while the extension is still loaded (`/worktree list`, then `/worktree remove <target>`), because those commands leave with the package. If you installed the optional sidebar plugin, remove it with `herdr plugin uninstall pi-herdr-agents.sidebar` (or `herdr plugin unlink` for a linked copy). Then run `pi remove npm:pi-herdr-agents`, and delete `$PI_CODING_AGENT_DIR/herdr-agents/` and the `artifacts/` directories above if you no longer want the configuration and launch records.
|
|
71
73
|
|
|
72
74
|
## Quick start
|
|
73
75
|
|
|
@@ -192,7 +194,7 @@ Subagent tabs, panes, and worktree workspaces are created without stealing keybo
|
|
|
192
194
|
| `worktree_list` | Parent-only inspect-only inventory of managed worktrees and cleanup blockers |
|
|
193
195
|
| `worktree_remove` | Parent-only explicit removal by `target` path, branch, or workspace ID; optional `preserve: true` commits dirty state first |
|
|
194
196
|
| `subagent_resume` | Resume a previous Pi-backed sub-agent session in a new ordinary pane (async) |
|
|
195
|
-
| `subagents_write_task_models` | Parent-only internal tool that validates and atomically writes `models.tasks` preferences, optionally conditional on `expectedConfigRevision` |
|
|
197
|
+
| `subagents_write_task_models` | Parent-only internal tool that validates and atomically writes `models.tasks` preferences, optionally conditional on `expectedConfigRevision` and carrying an unsaved ranking `basis` |
|
|
196
198
|
|
|
197
199
|
| Skill | Description |
|
|
198
200
|
| ----- | ----------- |
|
|
@@ -207,7 +209,7 @@ Subagent tabs, panes, and worktree workspaces are created without stealing keybo
|
|
|
207
209
|
| -------------------------- | ------------------------------------ |
|
|
208
210
|
| `/worktree <name> [task]` | Continue this session in a new managed worktree (`/worktree list` lists them) |
|
|
209
211
|
| `/subagent <agent> <task>` | Spawn a named agent directly (`/subagent list` lists available agents) |
|
|
210
|
-
| `/subagents-init [preferences]` | Draft task-category model preferences from the live authenticated registry, with optional ranking preferences |
|
|
212
|
+
| `/subagents-init [preferences]` | Draft task-category model preferences from the live authenticated registry, with optional ranking preferences; a loaded approval extension can own the write |
|
|
211
213
|
|
|
212
214
|
### Taxonomy and discovery
|
|
213
215
|
|
|
@@ -461,7 +463,10 @@ JSON without truncating models and reports its model count and JSON character
|
|
|
461
463
|
count (not a token estimate); large catalogs still consume context. This is the
|
|
462
464
|
current synchronous snapshot: a dynamic provider whose initial catalog refresh
|
|
463
465
|
has not completed might be absent. Init does not refresh providers or probe the
|
|
464
|
-
network for availability.
|
|
466
|
+
network for availability. The brief also includes `configRevision`, the
|
|
467
|
+
revision of the exact config bytes that supplied the saved preferences. Init
|
|
468
|
+
does not start while a turn runs or messages are queued, and an empty registry
|
|
469
|
+
ends with a notice instead of a prompt. Neither case writes anything.
|
|
465
470
|
|
|
466
471
|
The draft considers current saved task, default, and per-agent preferences.
|
|
467
472
|
Optional command arguments set ranking preferences. Otherwise it favors
|
|
@@ -482,8 +487,23 @@ sources, disclose uncertainty and notable exclusions, and avoid duplicate
|
|
|
482
487
|
upstream models across routes unless deliberate redundancy is explained. Display
|
|
483
488
|
names help identify candidates but, like aliases, do not prove upstream
|
|
484
489
|
equivalence; research is still required. Price or context size alone is not
|
|
485
|
-
quality evidence.
|
|
486
|
-
|
|
490
|
+
quality evidence. No live model probes run.
|
|
491
|
+
|
|
492
|
+
The proposal carries a ranking `basis`. It is `{"kind":"registry-only"}` when
|
|
493
|
+
search is unavailable or yields no usable evidence. It is `{"kind":"research"}`
|
|
494
|
+
only with the http(s) `sources` consulted in this run, the `influence` each had
|
|
495
|
+
on the ranking, and the remaining `uncertainty`. Code checks the basis's shape,
|
|
496
|
+
not whether a source was read, so the basis is labeled as submitted and
|
|
497
|
+
unverified.
|
|
498
|
+
|
|
499
|
+
While init runs, it emits `pi-herdr-subagents:task-models:init:approval:v1`.
|
|
500
|
+
With no offer, the prompt directs the model to `subagents_write_task_models`
|
|
501
|
+
with `expectedConfigRevision`. When exactly one loaded extension offers, init
|
|
502
|
+
opens that extension's flow, and the extension's instructions replace the
|
|
503
|
+
writer instruction; the extension then owns approval and the write. Two or more
|
|
504
|
+
offers, or an invalid one, stop init before any extension opens or a prompt is
|
|
505
|
+
sent. Once an offer is recorded, a refusal or failure also stops init: it never
|
|
506
|
+
falls back to the direct writer. See [Task-model init events](#task-model-init-events).
|
|
487
507
|
|
|
488
508
|
The writer validates and atomically replaces `models.tasks` and `tasksMeta`,
|
|
489
509
|
preserving unrelated settings. Its tool schema accepts partial nonempty
|
|
@@ -491,9 +511,15 @@ categories (omitted categories are removed), rejects empty `tasks: {}` input,
|
|
|
491
511
|
and rejects exact duplicate refs within a category after trimming;
|
|
492
512
|
IDs remain case-sensitive. Its result includes normalized saved `tasks`,
|
|
493
513
|
`tasksMeta`, `configPath`, `missingCategories`, and `configRevision`. Init requests all six categories
|
|
494
|
-
and a before/after table based on that saved result, not the unsaved draft
|
|
495
|
-
must explain missing categories or changed choices
|
|
496
|
-
|
|
514
|
+
and a before/after table based on that saved result, not the unsaved draft, and
|
|
515
|
+
must explain missing categories or changed choices.
|
|
516
|
+
|
|
517
|
+
Optional `basis` must have the same kind as `tasksMeta.method`. A research basis
|
|
518
|
+
needs at least one source with an http(s) URL that has a host and a nonblank
|
|
519
|
+
`influence`, plus a nonblank `uncertainty`. The writer validates it before
|
|
520
|
+
writing, returns it in the result, and never saves it; only `tasksMeta.method`
|
|
521
|
+
persists. Calls without `basis`, including research calls, keep the earlier
|
|
522
|
+
contract.
|
|
497
523
|
|
|
498
524
|
Optional `expectedConfigRevision` makes a write conditional on the config the
|
|
499
525
|
proposal was read from. A revision is `sha256:` followed by 64 lowercase hex
|
|
@@ -549,6 +575,128 @@ rewrites the file. Other values remain configuration errors, as do unknown keys
|
|
|
549
575
|
under `roles`. Registered role packs are the entire package layer, and global and
|
|
550
576
|
project definitions keep their precedence over them.
|
|
551
577
|
|
|
578
|
+
### Task-model init events
|
|
579
|
+
|
|
580
|
+
Two versioned `pi.events` channels let another extension take part in
|
|
581
|
+
`/subagents-init` without importing this package. Both carry the invoking
|
|
582
|
+
command's live `ExtensionCommandContext`, so they are for trusted extensions in
|
|
583
|
+
the same process. The context is current only while the event is emitted.
|
|
584
|
+
|
|
585
|
+
The host emits `pi-herdr-subagents:task-models:init:approval:v1` while init
|
|
586
|
+
runs:
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
type ApprovalRequest = {
|
|
590
|
+
apiVersion: 1;
|
|
591
|
+
brief: TaskModelBrief; // frozen: operatorPreferences, categories, configRevision, current, models (never empty)
|
|
592
|
+
context: ExtensionCommandContext;
|
|
593
|
+
offer(offer: { owner: string; open(): OpenResult }): "recorded" | "closed";
|
|
594
|
+
};
|
|
595
|
+
type OpenResult =
|
|
596
|
+
| {
|
|
597
|
+
kind: "ready";
|
|
598
|
+
destination: { toolName: string; instructions: string };
|
|
599
|
+
cancel(): void; // closes the flow this open created
|
|
600
|
+
}
|
|
601
|
+
| { kind: "blocked"; reason: string };
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
- Call `offer` synchronously, before the listener's first `await`. After `emit`
|
|
605
|
+
returns, `offer` answers `"closed"` and records nothing.
|
|
606
|
+
- Offer whenever your extension owns task-model approval, even when it is busy,
|
|
607
|
+
and refuse from `open`. `owner` is one printable token without spaces.
|
|
608
|
+
- The host opens nothing until collection ends. Two offers (even with the same
|
|
609
|
+
owner) or an invalid offer stop init. With one offer, the host calls `open()`
|
|
610
|
+
once, and it must return synchronously.
|
|
611
|
+
- `ready` must leave `toolName` active. Its `instructions` replace the host's
|
|
612
|
+
writer instruction in the prompt the host sends.
|
|
613
|
+
- `ready` must include `cancel`. When the host does not hand the prompt to Pi
|
|
614
|
+
(the tool is inactive, or `pi.sendUserMessage` throws), it calls `cancel()`
|
|
615
|
+
once before reporting `not-started`. `cancel` must close only the flow that
|
|
616
|
+
this `open` created, never a later one. A `cancel` that throws is reported
|
|
617
|
+
with the outcome.
|
|
618
|
+
- A throw, a promise, an invalid result (including a missing `cancel`), or
|
|
619
|
+
`blocked` stops init with a notice and no prompt. The host cannot undo what
|
|
620
|
+
`open` changed before failing.
|
|
621
|
+
- Pi accepts the prompt asynchronously. It can still reject it after `start`
|
|
622
|
+
reports `started`, for example when the selected model has no configured
|
|
623
|
+
auth; the host refuses up front only when no model is selected. Pi delivers
|
|
624
|
+
the prompt to `input` handlers (source `"extension"`) before that check and
|
|
625
|
+
emits `before_agent_start` and `agent_start` only after it passes. An
|
|
626
|
+
extension whose flow outlives one prompt should close it when other input or
|
|
627
|
+
a run arrives before those events, so a later request cannot inherit it.
|
|
628
|
+
- Pi's event bus logs and swallows listener exceptions. A listener that throws
|
|
629
|
+
before offering looks the same as no listener, so init uses the direct
|
|
630
|
+
writer. An extension that guards the writer still refuses that write.
|
|
631
|
+
|
|
632
|
+
Another extension's command emits `pi-herdr-subagents:task-models:init:start:v1`
|
|
633
|
+
to start the same init:
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
type StartRequest = {
|
|
637
|
+
apiVersion: 1;
|
|
638
|
+
context: ExtensionCommandContext;
|
|
639
|
+
preferences: string; // ranking preferences, passed through as typed
|
|
640
|
+
offer(offer: { owner: string; start(): InitOutcome }): "recorded" | "closed";
|
|
641
|
+
};
|
|
642
|
+
type InitOutcome =
|
|
643
|
+
| { kind: "started"; destination: string }
|
|
644
|
+
| { kind: "not-started"; reason: string };
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
The host offers synchronously as `pi-herdr-agents` and starts nothing until the
|
|
648
|
+
emitter calls `start()`. Call it only when exactly one host offered. `start`
|
|
649
|
+
runs init with the request's context and returns synchronously; the emitter
|
|
650
|
+
shows a `not-started` reason. The host does not listen in subagent sessions
|
|
651
|
+
and unsubscribes at `session_shutdown`. Unsubscribe an approval listener there
|
|
652
|
+
too, as the role-pack bridge does.
|
|
653
|
+
|
|
654
|
+
### Sidebar markers
|
|
655
|
+
|
|
656
|
+
Set `sidebar.enabled` to `true` to mark delegated children for the optional
|
|
657
|
+
[Pi Herdr Agents Sidebar](plugins/sidebar/README.md) Herdr plugin, whose Focus
|
|
658
|
+
action hides marked children from Herdr's Agents view. The setting defaults to
|
|
659
|
+
`false`.
|
|
660
|
+
|
|
661
|
+
```json
|
|
662
|
+
{
|
|
663
|
+
"sidebar": {
|
|
664
|
+
"enabled": true
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
Add the key to your existing `config.json`. The extension rejects a
|
|
670
|
+
`config.json` without its `status` section, so copy `config.json.example`
|
|
671
|
+
first if the file does not exist. Run `/reload` after changing the setting.
|
|
672
|
+
Children launched before the change stay unmarked.
|
|
673
|
+
|
|
674
|
+
Setting `sidebar.enabled` to `false` and running `/reload` retires every
|
|
675
|
+
existing marker and clears the markers of running children. A clear can fail to
|
|
676
|
+
reach Herdr, so a token still expires at most 15 seconds after the last write
|
|
677
|
+
Herdr accepted. Setting it back to `true` marks only later launches; a child
|
|
678
|
+
that was already running when you turned it off is never marked again.
|
|
679
|
+
|
|
680
|
+
With markers on, every fresh, resumed, and persistent child records its own
|
|
681
|
+
process identity, as managed worktree children always do. After the parent
|
|
682
|
+
verifies that identity, it reports the pane token `piha_delegated_v1` with the
|
|
683
|
+
value `live`, a 15-second time-to-live, and an increasing sequence number
|
|
684
|
+
through `HERDR_SOCKET_PATH`. It renews the token on supervision checks while
|
|
685
|
+
that exact process is alive. It clears the token when the run finalizes or is
|
|
686
|
+
suppressed, or when the process exits or can no longer be verified. A
|
|
687
|
+
`/worktree` handoff session is never marked.
|
|
688
|
+
|
|
689
|
+
Marker writes never fail a launch or keep Pi running. Each write has a 2-second
|
|
690
|
+
timeout on an unreferenced socket, failed writes are not retried, and three
|
|
691
|
+
failures in a row stop that child's marker. Process identity reads Linux
|
|
692
|
+
`/proc`, so on other systems, and outside Herdr, nothing is marked. After a
|
|
693
|
+
parent crash, a marker can remain for up to 15 seconds after the last write
|
|
694
|
+
Herdr applied. A marker belongs to the pane, so a different process started by
|
|
695
|
+
hand in a delegated pane stays hidden by Focus while the original process is
|
|
696
|
+
alive and supervised, even if it is suspended. Run All first or use a fresh
|
|
697
|
+
pane. See [what a marker proves](plugins/sidebar/README.md#what-a-marker-proves)
|
|
698
|
+
and [reusing a delegated pane](plugins/sidebar/README.md#reusing-a-delegated-pane).
|
|
699
|
+
|
|
552
700
|
### Supervision transport
|
|
553
701
|
|
|
554
702
|
On supported local filesystems, supervision uses file wake-ups plus one shared
|
|
@@ -1247,11 +1395,15 @@ provider; fakes are for conformance tests only.
|
|
|
1247
1395
|
- `maestro/adapters/pi/` — Pi launch, completion, session I/O, activity files,
|
|
1248
1396
|
model SDK glue, and registry projection behind `PiHarnessAdapter`;
|
|
1249
1397
|
`child/subagent-done.ts` implements the child protocol.
|
|
1250
|
-
- `maestro/surfaces/herdr/` — `HerdrSurfaceProvider`, Herdr CLI driver,
|
|
1251
|
-
terminal scripts/placement.
|
|
1398
|
+
- `maestro/surfaces/herdr/` — `HerdrSurfaceProvider`, Herdr CLI driver,
|
|
1399
|
+
terminal scripts/placement, and `herdr-socket.ts`, the bounded socket
|
|
1400
|
+
client for pane metadata.
|
|
1252
1401
|
- `maestro/runtime/` — run ownership, controls/retries, observation,
|
|
1253
|
-
delivery-gated cleanup, Pi composition, worktree operations/handoff,
|
|
1254
|
-
task-model init composition.
|
|
1402
|
+
delivery-gated cleanup, Pi composition, worktree operations/handoff,
|
|
1403
|
+
task-model init composition, and sidebar markers (`delegated-marker.ts`).
|
|
1404
|
+
- `plugins/sidebar/` — the optional Herdr plugin. Its `sidebar.mjs` uses Node
|
|
1405
|
+
built-ins only and imports nothing from the extension, so Herdr can install
|
|
1406
|
+
the directory by itself.
|
|
1255
1407
|
- `maestro/adapters/fake/`, `maestro/surfaces/fake/`, `test/maestro/` —
|
|
1256
1408
|
conformance fakes, seam tests, and the import dependency-rule test.
|
|
1257
1409
|
|
package/RELEASING.md
CHANGED
|
@@ -10,6 +10,8 @@ The published version must be unique on npm.
|
|
|
10
10
|
|
|
11
11
|
`3.0.0` is the pack-neutral baseline. It is a major release because it removes the bundled roles, `/plan`, `/skill:orchestrate`, `/iterate`, `/btw`, and `/btw-close`, and makes `roles.bundled` a deprecated no-op. Its hand-written breaking changes and migration notes are in the README [Release notes](README.md#release-notes) section, because `npm version` regenerates `CHANGELOG.md` and would discard hand edits there. Record later breaking-release notes in the same section.
|
|
12
12
|
|
|
13
|
+
`3.1.0` is a compatible minor release. It adds the request-scoped task-model init events that let a loaded pack approve `/subagents-init` proposals; with no pack offering, the direct writer is unchanged. It was prepared from feature commit `1b6bacd0b5eecb6f83ffba1d5a5ee57a8b0fa944`. It is not published until the release workflow succeeds.
|
|
14
|
+
|
|
13
15
|
Do not design a release that creates a GitHub Release without a successful npm publish for a new version. The workflow publishes first, then tags and creates the GitHub Release.
|
|
14
16
|
|
|
15
17
|
## Prerequisites
|
|
@@ -35,7 +37,7 @@ Manual deterministic Herdr integration (required before you push a release commi
|
|
|
35
37
|
npm run test:integration
|
|
36
38
|
```
|
|
37
39
|
|
|
38
|
-
Run that suite from inside Herdr. It uses real Pi and Herdr processes with the local deterministic provider, so it needs no provider credentials or network access.
|
|
40
|
+
Run that suite from inside Herdr. It uses real Pi and Herdr processes with the local deterministic provider, so it needs no provider credentials or network access. `test/integration/sidebar.test.ts` also starts two disposable Herdr servers with their own HOME, configuration, sockets, and plugin registry under the system temporary directory, and removes them afterwards. It needs Linux and skips elsewhere.
|
|
39
41
|
|
|
40
42
|
The optional live-provider smoke test is not a release gate:
|
|
41
43
|
|
|
@@ -43,7 +45,7 @@ The optional live-provider smoke test is not a release gate:
|
|
|
43
45
|
PI_TEST_MODEL="openai-codex/gpt-5.6-luna" PI_TEST_TIMEOUT=180000 npm run test:integration:live
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
Do not release from skipped Herdr tests. Confirm the package preview includes `README.md`, `CHANGELOG.md`, `AGENTS.md`, `docs/`, `config.json.example`,
|
|
48
|
+
Do not release from skipped Herdr tests. Confirm the package preview includes `README.md`, `CHANGELOG.md`, `AGENTS.md`, `docs/`, `config.json.example`, `examples/role-pack/`, and exactly `README.md`, `herdr-plugin.toml`, and `sidebar.mjs` under `plugins/sidebar/`, excludes the active `docs/sidebar-plan.md`, contains no `agents/` resources, no `skills/` resources other than the host-owned `skills/pi-herdr-agents/SKILL.md`, and no `pi-extension/subagents/plan-skill.md`, and excludes `pi-extension/subagents/workflow-worker.js`. Confirm it excludes plans, journals, sessions, prototypes, generated evidence, local `config.json` and `openspec/`, and that the worktree integration tests leave no test workspace behind. Durable user configuration is `$PI_CODING_AGENT_DIR/herdr-agents/config.json`; package-root configuration is ignored, so users must move an older file manually or re-run `/subagents-init`.
|
|
47
49
|
|
|
48
50
|
## npm authentication
|
|
49
51
|
|
package/docs/README.md
CHANGED
|
@@ -11,6 +11,9 @@ are evidence, not shipped contracts, when a later ADR supersedes them.
|
|
|
11
11
|
- [`../CONTEXT.md`](../CONTEXT.md) — orchestration glossary.
|
|
12
12
|
- [`worktree-subagents.md`](worktree-subagents.md) — worktree operation,
|
|
13
13
|
review, recovery, and cleanup.
|
|
14
|
+
- [`../plugins/sidebar/README.md`](../plugins/sidebar/README.md) — the
|
|
15
|
+
optional Herdr sidebar plugin: installation, Focus and All, the delegated
|
|
16
|
+
marker contract, and its staleness limits.
|
|
14
17
|
|
|
15
18
|
The package is a pack-neutral execution host: it launches asynchronous Pi
|
|
16
19
|
children in Herdr and supports managed worktrees for writing tasks, but ships no
|
|
@@ -66,6 +69,12 @@ real implementations; in-memory fakes are conformance fixtures only.
|
|
|
66
69
|
| [`0014`](adr/0014-operator-cancel-terminal-intent.md) | Accepted | Operator cancel records terminal intent before owned termination: no fallback, one confirmed cancelled result, unconfirmed runs stay live. |
|
|
67
70
|
| [`0015`](adr/0015-report-opened-primary-workspace.md) | Accepted | Report, and never automatically close, a primary workspace that worktree creation opened. |
|
|
68
71
|
|
|
72
|
+
## Active design
|
|
73
|
+
|
|
74
|
+
- `sidebar-plan.md` — local plan and audit record for the sidebar plugin and
|
|
75
|
+
its marker bridge. It is excluded from the npm package, so it has no link
|
|
76
|
+
here. It is not a shipped contract; the plugin README is.
|
|
77
|
+
|
|
69
78
|
## Historical material
|
|
70
79
|
|
|
71
80
|
- [`orchestrated-review-workflow-plan.md`](orchestrated-review-workflow-plan.md)
|
|
@@ -194,6 +194,7 @@ The extension never pushes, creates a PR, merges, cherry-picks, or changes the p
|
|
|
194
194
|
- **Ordinary launch failure:** if a fresh launch or `subagent_resume` fails after the extension creates its pane, the extension closes that pane and preserves the original launch error. It does not close a caller-supplied surface.
|
|
195
195
|
- **Creation failure:** the manifest is marked failed. If Herdr created the branch but returned an incomplete response, the extension reconciles a unique branch match through `/worktree list` and records any recovered workspace/path.
|
|
196
196
|
- **Launch failure after creation:** the manifest is marked failed and the workspace, forked session, and path are retained. The destination is not focused unless Pi startup is confirmed.
|
|
197
|
+
- **Sidebar marker:** with `sidebar.enabled`, the worktree child's root pane carries the delegated marker only while the child's own Pi process runs. The parent clears it when the run finalizes, so the retained shell, and any agent you later start in it, is not marked. After a parent crash the marker expires within 15 seconds of the last write Herdr applied. See the [sidebar plugin README](../plugins/sidebar/README.md#what-a-marker-proves).
|
|
197
198
|
- **Worker failure:** summary and available Git state are returned; the workspace remains open. Auto-exit waits until Pi is fully settled, so a transient provider error followed by automatic compaction or retry does not end the worker early.
|
|
198
199
|
- **Operator cancel:** `subagent_cancel` never closes the retained root pane or workspace. At launch, the child's protocol extension records its own process identity (PID, `/proc` start time, boot ID, and PID-namespace link) in `<session>.process.json`, and the parent accepts it only while that process is alive in the parent's PID namespace as the Herdr pane shell or a descendant of it. Cancel sends SIGTERM only to that identity, re-verified (PID and start time) immediately before the signal, then confirms that the identity no longer exists, or that the pane is gone while it is not known alive. Command-line text and Herdr's foreground list are never evidence: Pi rewrites its process title, and Herdr lists foreground processes only. A live identity (such as a suspended Pi with SIGTERM pending), or one whose SIGTERM fails while it is still alive, leaves termination `unconfirmed` even if the pane is gone. An identity that was not captured (including on non-Linux hosts), is unreadable, belongs to another boot or PID namespace, or whose PID now has another start time is never signalled and also leaves it `unconfirmed` unless pane absence confirms termination. The PID could still be reused between the final check and the signal, because Node has no pidfd signal. Every Herdr query is bounded by the cancel deadline, and an answer that arrives after the deadline, even before the timer runs, never changes the reported outcome. The run stays live, and retrying `subagent_cancel` re-checks; if the launch capture expired, the retry captures the identity again within its own deadline. On confirmation, the manifest is marked `cancelled` and the normal Git handoff is delivered once. A parent shutdown while termination is unconfirmed records the plain shutdown state, never `cancelled`. No Git cleanup, branch change, or removal happens.
|
|
199
200
|
- **`caller_ping`:** the child exits with `needs_help`; continue worktree-bound follow-up in the retained workspace rather than through `subagent_resume`. Public `subagent_resume` rejects managed-worktree child sessions before creating a pane so it cannot silently lose worktree ownership or policy.
|
|
@@ -54,6 +54,8 @@ export interface FreshPiLaunchRequest {
|
|
|
54
54
|
fork?: boolean;
|
|
55
55
|
handoff?: { leafId: string };
|
|
56
56
|
surface?: string;
|
|
57
|
+
/** Ordinary children record their process identity too (sidebar markers). */
|
|
58
|
+
recordProcessIdentity?: boolean;
|
|
57
59
|
parent: {
|
|
58
60
|
cwd: string;
|
|
59
61
|
invocationCwd?: string;
|
|
@@ -86,6 +88,8 @@ export interface ResumePiLaunchRequest {
|
|
|
86
88
|
name: string;
|
|
87
89
|
sessionFile: string;
|
|
88
90
|
message?: string;
|
|
91
|
+
/** Record the resumed process's identity (sidebar markers). */
|
|
92
|
+
recordProcessIdentity?: boolean;
|
|
89
93
|
parent: {
|
|
90
94
|
sessionId: string;
|
|
91
95
|
sessionDir: string;
|
|
@@ -125,9 +129,15 @@ export interface PiRunningChild {
|
|
|
125
129
|
stopState?: "requested" | "pending" | "failed";
|
|
126
130
|
stopFailure?: string;
|
|
127
131
|
crashNotified?: boolean;
|
|
128
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Where the child records its own process identity: every managed worktree
|
|
134
|
+
* child, and other children when the launch asks for it.
|
|
135
|
+
*/
|
|
129
136
|
processIdentityFile?: string;
|
|
130
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* Verified at launch readiness. A worktree cancel's only signal and exit
|
|
139
|
+
* evidence; sidebar markers renew only while it is alive.
|
|
140
|
+
*/
|
|
131
141
|
processIdentity?: PiProcessIdentity;
|
|
132
142
|
/** Settles when launch-readiness capture verifies or gives up; never rejects. */
|
|
133
143
|
processIdentityCapture?: Promise<PiProcessIdentity | undefined>;
|
|
@@ -718,7 +728,8 @@ function prepareChildSession(
|
|
|
718
728
|
activityFile,
|
|
719
729
|
// A handoff Pi loads no child protocol extension, so it records nothing.
|
|
720
730
|
processIdentityFile:
|
|
721
|
-
surface.worktree
|
|
731
|
+
(surface.worktree || resolved.request.recordProcessIdentity) &&
|
|
732
|
+
!resolved.request.handoff
|
|
722
733
|
? getSubagentProcessIdentityFile(sessionFile)
|
|
723
734
|
: undefined,
|
|
724
735
|
};
|
|
@@ -1034,6 +1045,11 @@ async function launchResumedPiSubagent(
|
|
|
1034
1045
|
await operations.waitForShellReady(surface);
|
|
1035
1046
|
const activityFile = getSubagentActivityFile(artifactDir, id);
|
|
1036
1047
|
mkdirSync(dirname(activityFile), { recursive: true });
|
|
1048
|
+
// The session file is reused, and its sidecar keeps the first identity
|
|
1049
|
+
// ever published for it, so each resumed run records under its own ID.
|
|
1050
|
+
const processIdentityFile = request.recordProcessIdentity
|
|
1051
|
+
? join(artifactDir, "process-identity", `${id}.json`)
|
|
1052
|
+
: undefined;
|
|
1037
1053
|
|
|
1038
1054
|
let messageFile: string | undefined;
|
|
1039
1055
|
if (request.message) {
|
|
@@ -1058,6 +1074,9 @@ async function launchResumedPiSubagent(
|
|
|
1058
1074
|
`PI_SUBAGENT_ID=${shellQuote(id)}`,
|
|
1059
1075
|
`PI_SUBAGENT_ACTIVITY_FILE=${shellQuote(activityFile)}`,
|
|
1060
1076
|
`PI_SUBAGENT_AUTO_EXIT=${autoExit ? "1" : "0"}`,
|
|
1077
|
+
...(processIdentityFile
|
|
1078
|
+
? [`PI_SUBAGENT_PROCESS_FILE=${shellQuote(processIdentityFile)}`]
|
|
1079
|
+
: []),
|
|
1061
1080
|
];
|
|
1062
1081
|
const toolAllowlist = buildSubagentToolAllowlist(
|
|
1063
1082
|
policy.tools?.join(","),
|
|
@@ -1093,7 +1112,7 @@ async function launchResumedPiSubagent(
|
|
|
1093
1112
|
].join("\n"),
|
|
1094
1113
|
},
|
|
1095
1114
|
);
|
|
1096
|
-
|
|
1115
|
+
const running: PiRunningChild = {
|
|
1097
1116
|
id,
|
|
1098
1117
|
name: request.name,
|
|
1099
1118
|
task: request.message ?? "resumed session",
|
|
@@ -1105,7 +1124,10 @@ async function launchResumedPiSubagent(
|
|
|
1105
1124
|
interactive,
|
|
1106
1125
|
runtimePlan: undefined,
|
|
1107
1126
|
lifecycle: createLifecycle(startTime),
|
|
1127
|
+
processIdentityFile,
|
|
1108
1128
|
};
|
|
1129
|
+
startProcessIdentityCapture(running, operations);
|
|
1130
|
+
return running;
|
|
1109
1131
|
} catch (error) {
|
|
1110
1132
|
try {
|
|
1111
1133
|
await operations.closePane(surface);
|
|
@@ -97,6 +97,8 @@ export interface PiHarnessAdapterOptions {
|
|
|
97
97
|
worktreeOperations?: WorktreeOperations;
|
|
98
98
|
/** Kernel process facts for a retained worktree child's identity; tests inject it. */
|
|
99
99
|
processProbe?: ProcessIdentityProbe;
|
|
100
|
+
/** Ordinary and resumed children also record their identity (sidebar markers). */
|
|
101
|
+
recordProcessIdentity?: boolean;
|
|
100
102
|
/** Bound for confirming a retained worktree child's exit. Default: 5000ms. */
|
|
101
103
|
killTimeoutMs?: number;
|
|
102
104
|
/** Explicit local evidence/lifecycle bridge. The host still owns delivery and deduplication. */
|
|
@@ -209,6 +211,7 @@ export class PiHarnessAdapter implements HarnessAdapter {
|
|
|
209
211
|
: undefined
|
|
210
212
|
: undefined,
|
|
211
213
|
surface: opts.surface?.id,
|
|
214
|
+
recordProcessIdentity: this.options.recordProcessIdentity,
|
|
212
215
|
parent: this.options.parent,
|
|
213
216
|
runtimePlan:
|
|
214
217
|
resolved?.runtimePlan ??
|
|
@@ -294,6 +297,7 @@ export class PiHarnessAdapter implements HarnessAdapter {
|
|
|
294
297
|
name: opts.name,
|
|
295
298
|
sessionFile: opts.sessionId,
|
|
296
299
|
message: opts.message,
|
|
300
|
+
recordProcessIdentity: this.options.recordProcessIdentity,
|
|
297
301
|
parent: this.options.parent,
|
|
298
302
|
behavior: { autoExit: opts.autoExit },
|
|
299
303
|
},
|