pier-setup 0.3.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +24 -11
  2. package/install.mjs +63 -11
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -23,10 +23,13 @@ pier ships as **two halves**, installed separately:
23
23
 
24
24
  ### Highlights
25
25
 
26
- - **Todo loop**: `todo_write` with full-replace semantics, session JSONL as source of truth, correct branch rollback; live pane-title projection `▶i ○p ■b ✓c (N/M) · current task`; TUI widget is an activity-anchored window (the in_progress entry and its surrounding context stay visible as work moves down the list); `/todos` command (including unblock)
26
+ - **Todo loop**: `todo_write` with full-replace semantics, session JSONL as source of truth, correct branch rollback; live pane-title projection `▶i ○p ■b ✓c (N/M) · current task`; `/todos` command (including unblock)
27
27
  - **Todo anti-freeze**: an all-completed list unchanged for ≥6 turns flips the read hook from recitation to a rewrite warning; after ≥1h it archives — first offering one rewrite window with old entries as reference, then (if ignored) a final notice that clears the list (rm persisted to JSONL; the empty guard takes over — multi-step work must re-track); `/todos` still shows history, session JSONL untouched
28
28
  - **Interactive subagents**: every subagent = an isolated pi session in its own pane (separate context window); **a human can enter that pane and talk to it directly** (fix bugs, take over, answer ask_user_question)
29
- - **Subagent output preview**: `subagent(action: "output", agentId)` returns what a running background subagent has printed since the previous call — an in-memory per-pane cursor computes the delta (`agent.read`, falling back to `pane.read` on older herdr), and the result carries status, revision, truncation and a `restart` flag for scrolled/cleared buffers, so the master can notice a stuck worker instead of waiting for settlement
29
+ - **Delegation identity** (D111): spawn hands the worker a `PI_HERDR_DELEGATION` context (parent pane/session, taskId, task, role), replayed env > worker marker > branch fold; the worker's system prompt gains a `pier-delegation` section (identity + one stable delivery contract), its transcript opens with an "⟵ delegated by master …" card, and its pane title carries an `↳` prefix; `pi-parent`/`pi-task` herdr tokens ride their own report batch, the dashboard groups workers under their master, and blocked alerts name the edge (`worker ← master`)
30
+ - **Task slugs** (D112): spawn allocates a task slug (numbered on collision); tool outputs print `slug@paneId` and `agentId` accepts the bare slug, bare pane id, or the combined form (the slug part survives a revive that changed the pane). A master bash call that sleeps ≥60 s while background workers run gets a note that the settlement notice arrives on its own (D125; the earlier `report_progress` tool was never called and is gone)
31
+ - **Unified TUI status strip** (D113/D114): the resident widget is one line per agent — self as `▶ current item ✓done/total`, each running subagent as `↳ slug ▶ current item ✓d/n` (blocked shows the task + reason; settled rows never occupy the strip; a fully completed self list hides the widget instead of showing a bare `✓N/N`); workers push their full todo list over the pipe on every `todo_write` (event-driven, no polling), and `/pier-tree` opens the complete delegation tree (your todos + every worker's full list + settled nodes) in the shared Pier **popup** — the same singleton panel as `/dashboard`, opened on its `Tree` tab (inside the popup, `1`/`2`/`Tab` switch views; an explicit `/pier-tree overlay` keeps the independent full-window pane; non-Herdr falls back to the in-pane notify view); `/dashboard` opens the same panel on its `Dashboard` tab
32
+ - **Subagent output preview**: `subagent(action: "output", agentId)` returns what a running background subagent has printed since the previous call — an in-memory per-pane cursor computes the delta (`agent.read`, falling back to `pane.read` on older herdr), and the result carries status, revision, truncation and a `restart` flag for scrolled/cleared buffers, so the master can notice a stuck worker instead of waiting for settlement. TUI chrome (frames, footer) is stripped; once the worker has finished, the complete transcript report replaces the pane's lossy rendered copy, and a later settlement notice points at that delivery instead of repeating it (D124)
30
33
  - **Soft write-locks**: per-pane lock beacons on `write`/`edit` paths; cross-pane conflicts warn (default) or block (`PI_HERDR_WRITE_LOCK=1`). The warning lists every holder; `/locks` is the human view of the same table (`herdr agent list` exposes the raw tokens to agents)
31
34
  - **Role profiles**: built-in `master` / `worker-default`; custom roles as `.pi-herdr/roles/<name>.json`; toolset converges per role (deny rules are not bypassable)
32
35
  - **Human gate**: subagent `ask_user_question` → sidebar blocked marker + notification; manual takeover (ESC-interrupt then typing) is heuristically detected so the master pauses/returns management automatically
@@ -45,8 +48,8 @@ pier ships as **two halves**, installed separately:
45
48
  ### Requirements
46
49
 
47
50
  - Node ≥ 22
48
- - pi ≥ 0.86.0 (`@earendil-works/pi-coding-agent`) — dynamic toolset (transcript-backed `setActiveTools` deltas) and role switching require it
49
- - herdr ≥ 0.9.0 (macOS / Linux / Windows; Windows is preview beta)
51
+ - pi ≥ 1.3.0 (`@earendil-works/pi-coding-agent`) — `before_agent_start.messageType` and resumed-session active tools
52
+ - herdr ≥ 0.9.5 (macOS / Linux / Windows; Windows is preview beta) — native `pane.focused`, self-reported resume, and shutdown-safe session saves
50
53
 
51
54
  ### One-shot install (recommended)
52
55
 
@@ -162,16 +165,15 @@ Runtime policies and operational limits are centralized in `runtime-policy.ts` (
162
165
  |---|---|---|---|
163
166
  | `PIER_SUBAGENT_TIMEOUT_MS` | `600000` | ms | Subagent inactivity budget / overall timeout before forced termination |
164
167
  | `PIER_SETTLEMENT_WINDOW_MS` | `60000` | ms | Settlement notice window, machine injection grace period, and takeover idle threshold |
165
- | `PIER_OBSERVATION_WINDOW_MS` | `30000` | ms | Post-settle observation window before auto-consuming finished subagents |
168
+ | `PIER_OBSERVATION_WINDOW_MS` | `30000` | ms | Post-settle observation window before auto-consuming finished subagents; skipped when the worker's transcript already holds a finalized answer and its pane is idle |
166
169
  | `PIER_FOREGROUND_PATIENCE_MS` | `300000` | ms | Foreground execution patience before auto-demoting a subagent to background |
167
170
  | `PIER_GC_TICK_MS` | `30000` | ms | Subagent garbage collection ticker interval |
168
171
  | `PIER_POLL_INTERVAL_MS` | `30000` | ms | State observation polling interval for subagent state transitions |
169
172
  | `PIER_READY_TIMEOUT_MS` | `90000` | ms | Subagent pane pipe readiness wait (exponential backoff; a pane that exited fails immediately with its last output attached) |
170
173
  | `PIER_SESSION_TTL_SECONDS` | `600` | s | Session retention TTL after subagent exit before GC cleanup |
171
174
  | `PIER_GIT_TIMEOUT_MS` | `10000` | ms | Execution timeout for git operations (worktree creation, diff summary, cleanup) |
172
- | `PIER_FOCUS_POLL_MS` | `1500` / `0` | ms | Focus sampling for heat layout (`0` disables). Default 1500ms on Herdr <0.9.1; 0 (event-first) on 0.9.1+ |
173
175
  | `PIER_TERM_READ_MAX` | `8000` | chars | Maximum terminal buffer characters read per operation |
174
- | `PIER_TERM_IDLE_MS` | `1800000` | ms | Idle time before pier nudges about an open terminal |
176
+ | `PIER_TERM_IDLE_MS` | `300000` | ms | Idle time before the second terminal-cleanup nudge (one is sent at turn end for a terminal used that turn) |
175
177
  | `PIER_TODO_GRACE_MS` | `30000` | ms | Settle grace before the unfinished-todo reminder |
176
178
  | `PIER_TRACE` | – | flag | Write diagnostics (tool renderers, swallowed errors) to stderr |
177
179
  | `PIER_JEV_ENABLE` | `0` | flag | Jev decision layer (TypeSafe System One classification) master switch; every call site fails open — see `docs/rfc-jev-integration.md` |
@@ -189,15 +191,26 @@ processes stay as they are (`PI_HERDR_SUBAGENT`, `PI_HERDR_ROLE_MANIFEST`, `PI_H
189
191
  `/pier-config doctor` lists every option with its effective value and where it came from, plus the
190
192
  errors pier deliberately swallowed this session.
191
193
 
192
- **Focus heat:** Herdr 0.9.0 resolved mouse focus in the client and did not deliver `pane.focused`
193
- to plugins, so each pane sampled `layout.export → focused_pane_id` and replayed the workbench
194
- event. Herdr 0.9.1+ delivers `pane.focused` natively, so the poller defaults off. Override with
195
- `PIER_FOCUS_POLL_MS`; `PIER_WORKBENCH_ROOT` points at a relocated plugin checkout.
194
+ **Focus heat:** Herdr 0.9.5 delivers `pane.focused` natively, so pier starts no per-pane sampler;
195
+ the workbench receives the event directly. `PIER_WORKBENCH_ROOT` points at a relocated plugin checkout.
196
+
197
+ **Upstream tracking:** pi 1.3 / herdr 0.9.5 impact, version-floor cleanup, and the next extension
198
+ directions (remote machines, native worktrees, plugin actions):
199
+ **[docs/rfc-upstream-absorption-2026-10.md](docs/rfc-upstream-absorption-2026-10.md)**.
196
200
 
197
201
  ### Efficiency mechanisms (D100–D103, opt-in)
198
202
 
199
203
  Three token-saving mechanisms ship **disabled by default** and are safe to try one at a time:
200
204
 
205
+ **Origin — credit where due**: the mechanism ideas and names come from NVIDIA's
206
+ [SoL-Pi](https://github.com/NVlabs/SoL-Pi) ([paper](https://arxiv.org/abs/2609.20519) ·
207
+ [blog](https://nvlabs.github.io/SoL-Pi/)) — four harness-level efficiency mechanisms surfaced by
208
+ scaled auto-research loops. pier adopts three of them (ObservationPack / EPR / OCC; the fourth,
209
+ Action Fusion, is not adopted) and re-implements them on its own stack, extending the originals
210
+ with a jev decision layer (small System One classifications at the diagnostic gate, excerpt-window
211
+ pick, and settle attribution — [rfc-jev-integration.md](docs/rfc-jev-integration.md)), role gating,
212
+ and the 4-plane efficiency config with per-mechanism JSONL telemetry (D100–D103).
213
+
201
214
  - **ObservationPack** — large tool outputs are projected as placeholders after the first `fullSends` requests, with paged retrieval via `obs_recall` (session JSONL stays untouched).
202
215
  - **EPR** — long diagnostic (`bash` test/build) logs are reduced in-process to a byte-verified evidence receipt; the raw log is archived first and stays readable.
203
216
  - **OCC** — `todo_write` boundaries drive pi's native compaction under a KV-cache cost model, carrying unfinished tasks across the compaction.
package/install.mjs CHANGED
@@ -100,6 +100,11 @@ function tryRun(cmd, args) {
100
100
  try { run(cmd, args); return true; } catch (e) { log(` (${e.message.split('\n')[0]})`); return false; }
101
101
  }
102
102
 
103
+ /** Same as tryRun without the diagnostic line, for feature probes whose failure is expected. */
104
+ function tryRunQuiet(cmd, args) {
105
+ try { run(cmd, args); return true; } catch { return false; }
106
+ }
107
+
103
108
  function parseVersion(s) {
104
109
  const m = String(s).match(/(\d+)\.(\d+)\.(\d+)/);
105
110
  return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
@@ -145,13 +150,13 @@ function checkEnv() {
145
150
  if (major < 22) die(`node ≥ 22 required, found ${process.versions.node}`);
146
151
  log(`✓ node ${process.versions.node}`);
147
152
  const piVersion = (() => { try { return parseVersion(run('pi', ['--version'])); } catch { return null; } })();
148
- if (!piVersion || !geVersion(piVersion, [0, 86, 0])) {
149
- die(`pi ≥ 0.86.0 required${piVersion ? `, found ${piVersion.join('.')}` : ' (pi not found or not runnable)'}. Install: npm i -g @earendil-works/pi-coding-agent`);
153
+ if (!piVersion || !geVersion(piVersion, [1, 3, 0])) {
154
+ die(`pi ≥ 1.3.0 required${piVersion ? `, found ${piVersion.join('.')}` : ' (pi not found or not runnable)'}. Install: npm i -g @earendil-works/pi-coding-agent`);
150
155
  }
151
156
  log(`✓ pi ${piVersion.join('.')}`);
152
157
  const herdrVersion = (() => { try { return parseVersion(run('herdr', ['--version'])); } catch { return null; } })();
153
- if (!herdrVersion || !geVersion(herdrVersion, [0, 9, 0])) {
154
- die(`herdr ≥ 0.9.0 required${herdrVersion ? `, found ${herdrVersion.join('.')}` : ' (herdr not found)'}. Install: https://herdr.dev/`);
158
+ if (!herdrVersion || !geVersion(herdrVersion, [0, 9, 5])) {
159
+ die(`herdr ≥ 0.9.5 required${herdrVersion ? `, found ${herdrVersion.join('.')}` : ' (herdr not found)'}. Install: https://herdr.dev/`);
155
160
  }
156
161
  log(`✓ herdr ${herdrVersion.join('.')}`);
157
162
  return { piVersion, herdrVersion };
@@ -245,16 +250,57 @@ function pkgDirFromExtPath(extPath) {
245
250
  return null;
246
251
  }
247
252
 
248
- function herdrPluginInfo() {
249
- let out;
250
- try { out = run('herdr', ['plugin', 'list']); } catch { return { installed: false, spec: null, sha: null }; }
251
- const line = out.split(/\r?\n/).find((l) => l.includes('pier.workbench'));
252
- if (!line) return { installed: false, spec: null, sha: null };
253
+ const HERDR_PLUGIN_ID = 'pier.workbench';
254
+ const HERDR_PLUGIN_NAME = 'pier-workbench';
255
+ const NOT_INSTALLED = Object.freeze({ installed: false, spec: null, sha: null, version: null, source: null, enabled: false });
256
+
257
+ /** `plugin list --json` (herdr 0.9.5+): returns undefined for a malformed payload, null when the
258
+ * plugin is absent, so the caller can fall back to the text listing on older binaries. */
259
+ function parseHerdrPluginJson(out) {
260
+ let parsed;
261
+ try { parsed = JSON.parse(out); } catch { return undefined; }
262
+ const plugins = parsed?.result?.plugins ?? parsed?.plugins;
263
+ if (!Array.isArray(plugins)) return undefined;
264
+ const p = plugins.find((x) => x && (x.plugin_id === HERDR_PLUGIN_ID || x.name === HERDR_PLUGIN_NAME));
265
+ if (!p) return null;
266
+ const source = p.source && typeof p.source === 'object' ? p.source : {};
267
+ const kind = typeof source.kind === 'string' ? source.kind : null;
268
+ const root = typeof p.plugin_root === 'string' ? p.plugin_root : null;
269
+ const commit = typeof source.commit === 'string' ? source.commit : null;
270
+ const spec = kind === 'local'
271
+ ? `local:${root ?? p.manifest_path ?? ''}`
272
+ : (typeof p.version === 'string' ? `v${p.version}` : (kind ?? 'installed'));
273
+ return {
274
+ installed: true,
275
+ spec,
276
+ sha: commit ? commit.slice(0, 7) : null,
277
+ version: typeof p.version === 'string' ? p.version : null,
278
+ source: kind,
279
+ enabled: p.enabled === true,
280
+ };
281
+ }
282
+
283
+ function parseHerdrPluginText(out) {
284
+ const line = out.split(/\r?\n/).find((l) => l.includes(HERDR_PLUGIN_ID));
285
+ if (!line) return NOT_INSTALLED;
253
286
  const bracket = line.match(/\[([^\]]+)\]/);
254
287
  const spec = bracket ? bracket[1] : line.trim();
255
288
  const at = spec.lastIndexOf('@');
256
289
  const fullSha = at >= 0 && !spec.startsWith('local:') ? spec.slice(at + 1) : null;
257
- return { installed: true, spec, sha: fullSha ? fullSha.slice(0, 7) : null };
290
+ return { installed: true, spec, sha: fullSha ? fullSha.slice(0, 7) : null, version: null, source: spec.startsWith('local:') ? 'local' : null, enabled: false };
291
+ }
292
+
293
+ function herdrPluginInfo() {
294
+ try {
295
+ const out = run('herdr', ['plugin', 'list', '--json']);
296
+ const parsed = parseHerdrPluginJson(out);
297
+ if (parsed !== undefined) return parsed ?? NOT_INSTALLED;
298
+ } catch { /* older herdr or no server; fall through to the text listing */ }
299
+ try {
300
+ return parseHerdrPluginText(run('herdr', ['plugin', 'list']));
301
+ } catch {
302
+ return NOT_INSTALLED;
303
+ }
258
304
  }
259
305
 
260
306
  function collectStatus() {
@@ -328,7 +374,13 @@ function update() {
328
374
  if (tryRun('pi', ['update', piSource()])) log(`✓ pi extension updated (${piSource()})`);
329
375
  else if (tryRun('pi', ['install', piSource()])) log(`✓ pi extension installed (${piSource()})`);
330
376
  else { console.error('✗ pi update/install failed'); log(` manual: pi update ${piSource()}`); process.exitCode = 1; }
331
- if (tryRun('herdr', ['plugin', 'install', HERDR_SPEC, '--yes'])) log(`✓ herdr plugin updated (${HERDR_SPEC})`);
377
+ // 0.9.4+ updates the GitHub checkout in place without disturbing running plugin panes.
378
+ // Only try it for an installed, non-local plugin; otherwise install from the configured spec.
379
+ const existing = herdrPluginInfo();
380
+ const updated = (existing.installed && existing.source !== 'local'
381
+ && tryRunQuiet('herdr', ['plugin', 'update', HERDR_PLUGIN_ID, '--yes']))
382
+ || tryRunQuiet('herdr', ['plugin', 'install', HERDR_SPEC, '--yes']);
383
+ if (updated) log(`✓ herdr plugin updated (${HERDR_SPEC})`);
332
384
  else { console.error('✗ herdr plugin update failed'); log(` manual: herdr plugin install ${HERDR_SPEC} --yes`); process.exitCode = 1; }
333
385
  }
334
386
  log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pier-setup",
3
- "version": "0.3.1",
3
+ "version": "0.4.2",
4
4
  "description": "One-shot installer for pier: install / update / version / uninstall for pi-pier + pier.workbench",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,8 +31,9 @@
31
31
  "packages/*"
32
32
  ],
33
33
  "scripts": {
34
+ "build:workbench": "tsc -p packages/pier-workbench/tsconfig.build.json",
34
35
  "typecheck": "tsc --noEmit -p packages/pier-ext/tsconfig.json",
35
- "test": "npm run typecheck && node --test \"packages/*/test/*.test.ts\"",
36
+ "test": "npm run build:workbench && npm run typecheck && node --test \"packages/*/test/*.test.ts\"",
36
37
  "test:mutation": "stryker run",
37
38
  "prepare": "node ./install.mjs --prepare"
38
39
  },
@@ -42,7 +43,6 @@
42
43
  "@deepseek-ai/cordis-plugin-loader": "^1.0.2",
43
44
  "@deepseek-ai/cordis-plugin-timer": "^1.1.3",
44
45
  "@stryker-mutator/core": "^10.0.0",
45
- "@stryker-mutator/typescript-checker": "^10.0.0",
46
46
  "typescript": "~5.9.3"
47
47
  },
48
48
  "pi": {