pier-setup 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +7 -3
  2. package/install.mjs +67 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -27,9 +27,9 @@ pier ships as **two halves**, installed separately:
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
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 + worker progress reports** (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 worker-only `report_progress` tool pipes checkpoints into the master's context as a display-less custom message — no chat flood, no forced turn
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
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
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)
33
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)
34
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)
35
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
@@ -77,6 +77,10 @@ node install.mjs update --dev # re-check env + versions; pull the repo yourse
77
77
  ```
78
78
 
79
79
  The script verifies the environment (node / pi / herdr versions) and registers both halves.
80
+ pier must be registered in pi from exactly one source: `install`/`update` drops the legacy
81
+ `git:github.com/July24/pier` registration automatically, and stops (exit 1, nothing changed)
82
+ when pier is already registered from another source - e.g. a `--dev` checkout before a user-mode
83
+ install - printing the `pi remove …` to run. `version` reports the source pi actually loads.
80
84
 
81
85
  `update` re-runs `pi update npm:pi-pier` (falls back to `pi install`) and
82
86
  `herdr plugin install … --yes`. Override sources with
@@ -165,7 +169,7 @@ Runtime policies and operational limits are centralized in `runtime-policy.ts` (
165
169
  |---|---|---|---|
166
170
  | `PIER_SUBAGENT_TIMEOUT_MS` | `600000` | ms | Subagent inactivity budget / overall timeout before forced termination |
167
171
  | `PIER_SETTLEMENT_WINDOW_MS` | `60000` | ms | Settlement notice window, machine injection grace period, and takeover idle threshold |
168
- | `PIER_OBSERVATION_WINDOW_MS` | `30000` | ms | Post-settle observation window before auto-consuming finished subagents |
172
+ | `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 |
169
173
  | `PIER_FOREGROUND_PATIENCE_MS` | `300000` | ms | Foreground execution patience before auto-demoting a subagent to background |
170
174
  | `PIER_GC_TICK_MS` | `30000` | ms | Subagent garbage collection ticker interval |
171
175
  | `PIER_POLL_INTERVAL_MS` | `30000` | ms | State observation polling interval for subagent state transitions |
package/install.mjs CHANGED
@@ -127,14 +127,17 @@ function cmpSemver(a, b) {
127
127
  return 0;
128
128
  }
129
129
 
130
- function readPkgVersion(dir) {
130
+ function readPkgJson(dir) {
131
131
  try {
132
- return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).version ?? null;
132
+ return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
133
133
  } catch {
134
134
  return null;
135
135
  }
136
136
  }
137
137
 
138
+ const readPkgVersion = (dir) => readPkgJson(dir)?.version ?? null;
139
+ const readPkgName = (dir) => readPkgJson(dir)?.name ?? null;
140
+
138
141
  function npmLatest(name) {
139
142
  try {
140
143
  return run('npm', ['view', name, 'version'], { timeout: 15000 });
@@ -181,12 +184,56 @@ function registeredPiPackages() {
181
184
  } catch { return []; }
182
185
  }
183
186
 
184
- /** Clean up legacy git-mode registration before install/update; if --pi-spec explicitly specifies that git source, treat it as target and keep it. */
185
- function removeLegacyGitRegistration() {
186
- if (piSource() === LEGACY_GIT_SPEC) return;
187
- if (!registeredPiPackages().includes(LEGACY_GIT_SPEC)) return;
188
- if (tryRun('pi', ['remove', LEGACY_GIT_SPEC])) log(`✓ removed legacy git-mode registration (${LEGACY_GIT_SPEC}); using ${piSource()}`);
189
- else { console.error('✗ failed to remove legacy git-mode registration'); log(` manual: pi remove ${LEGACY_GIT_SPEC}`); process.exitCode = 1; }
187
+ /** A `packages` entry is a string spec or `{ source }`; local paths are relative to the agent dir. */
188
+ function packageSource(entry) {
189
+ return typeof entry === 'string' ? entry : (typeof entry?.source === 'string' ? entry.source : null);
190
+ }
191
+
192
+ function localPackageDir(src) {
193
+ if (/^(npm|git|https?|ssh):/.test(src)) return null;
194
+ return resolve(piAgentDir(), src.startsWith('~/') ? join(homedir(), src.slice(2)) : src);
195
+ }
196
+
197
+ /** Registered sources that load pier's extension (npm, git, or a local checkout of pi-pier). */
198
+ function registeredPierSources() {
199
+ return registeredPiPackages().map(packageSource).filter((src) => {
200
+ if (!src) return false;
201
+ if (npmNameFromSpec(src) === 'pi-pier' || /^git:.*july24\/pier/i.test(src)) return true;
202
+ const dir = localPackageDir(src);
203
+ return dir !== null && readPkgName(dir) === 'pi-pier';
204
+ });
205
+ }
206
+
207
+ function sameSource(registered, target) {
208
+ const dir = localPackageDir(registered);
209
+ if (dir !== null) return localPackageDir(target) === dir;
210
+ const name = npmNameFromSpec(registered);
211
+ return name !== null ? npmNameFromSpec(target) === name : registered === target;
212
+ }
213
+
214
+ /** Human/CLI form of a registered source: local entries become absolute (`pi remove` takes them). */
215
+ function displaySource(src) {
216
+ const dir = localPackageDir(src);
217
+ return dir !== null ? dir : src;
218
+ }
219
+
220
+ /**
221
+ * Before install/update: drop the legacy git-mode registration (unless it is the explicit target),
222
+ * and refuse when pier is registered from any other source — two registrations load every pier
223
+ * tool twice and pi rejects the duplicate at launch.
224
+ */
225
+ function reconcilePierRegistrations() {
226
+ for (const src of registeredPierSources()) {
227
+ if (sameSource(src, piSource())) continue;
228
+ if (src === LEGACY_GIT_SPEC) {
229
+ if (tryRun('pi', ['remove', LEGACY_GIT_SPEC])) log(`✓ removed legacy git-mode registration (${LEGACY_GIT_SPEC}); using ${piSource()}`);
230
+ else { console.error('✗ failed to remove legacy git-mode registration'); log(` manual: pi remove ${LEGACY_GIT_SPEC}`); process.exitCode = 1; }
231
+ continue;
232
+ }
233
+ const switchHint = localPackageDir(src) !== null && !dev ? ', or keep it and rerun with --dev'
234
+ : dev && localPackageDir(src) === null ? ', or keep it and rerun without --dev' : '';
235
+ die(`pier is already registered in pi from ${displaySource(src)}; adding ${piSource()} would load every pier tool twice.\n Remove it first: pi remove ${displaySource(src)}${switchHint}`);
236
+ }
190
237
  }
191
238
 
192
239
  function npmNameFromSpec(spec) {
@@ -305,13 +352,20 @@ function herdrPluginInfo() {
305
352
 
306
353
  function collectStatus() {
307
354
  const installer = readPkgVersion(ROOT);
308
- const extPath = dev ? (existsSync(EXT_PATH) ? EXT_PATH : null) : resolveUserExtPath();
309
- const extDir = extPath ? pkgDirFromExtPath(extPath) : null;
355
+ // Report what pi actually loads, not the spec this run would install.
356
+ const registered = registeredPierSources();
357
+ const registeredDir = registered.map(localPackageDir).find((d) => d !== null);
358
+ const extDir = dev
359
+ ? (existsSync(EXT_PATH) ? EXT_DIR : null)
360
+ : registeredDir ?? (() => { const p = resolveUserExtPath(); return p ? pkgDirFromExtPath(p) : null; })();
361
+ const spec = registered.length > 0
362
+ ? registered.map((src) => (localPackageDir(src) !== null ? `local:${localPackageDir(src)}` : src)).join(', ')
363
+ : piSource();
310
364
  return {
311
365
  product: (extDir ? readPkgVersion(extDir) : null) || installer,
312
366
  mode: dev ? 'dev' : 'user',
313
367
  installer: { name: 'pier-setup', version: installer },
314
- piExt: { spec: piSource(), version: extDir ? readPkgVersion(extDir) : null, path: extDir },
368
+ piExt: { spec, version: extDir ? readPkgVersion(extDir) : null, path: extDir },
315
369
  herdr: herdrPluginInfo(),
316
370
  latest: { 'pier-setup': npmLatest('pier-setup'), 'pi-pier': npmLatest('pi-pier') },
317
371
  };
@@ -362,7 +416,7 @@ function version() {
362
416
 
363
417
  function update() {
364
418
  checkEnv();
365
- removeLegacyGitRegistration();
419
+ reconcilePierRegistrations();
366
420
  const me = readPkgVersion(ROOT);
367
421
  const latestSetup = npmLatest('pier-setup');
368
422
  if (latestSetup && me && cmpSemver(me, latestSetup) < 0) {
@@ -389,7 +443,7 @@ function update() {
389
443
 
390
444
  function install() {
391
445
  checkEnv();
392
- removeLegacyGitRegistration();
446
+ reconcilePierRegistrations();
393
447
 
394
448
  if (dev) {
395
449
  if (tryRun('pi', ['install', piSource()])) log('✓ pi extension installed (packages/pier-ext, local)');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pier-setup",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
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",