pi-herdsman 0.22.0 → 0.22.1

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/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
15
15
  // package.json
16
16
  var package_default = {
17
17
  name: "pi-herdsman",
18
- version: "0.22.0",
18
+ version: "0.22.1",
19
19
  type: "module",
20
20
  description: "Asynchronous Pi subagents and agent fleet orchestration for parallel coding agents with nested delegation, background work, and supervision in herdr.",
21
21
  license: "Apache-2.0",
@@ -302,6 +302,7 @@ import {
302
302
  rmdirSync as rmdirSync2,
303
303
  statSync,
304
304
  unlinkSync as unlinkSync2,
305
+ watch,
305
306
  writeSync as writeSync2
306
307
  } from "node:fs";
307
308
  import { basename as basename2, dirname as dirname2, join as join3 } from "node:path";
@@ -568,6 +569,64 @@ var LIMITS = {
568
569
  result: 4 * 1024 * 1024
569
570
  };
570
571
  var root = join3(herdsmanDataRoot(), "runtime", "mailboxes-v5");
572
+ function observeMailbox(directory, relevant, reconcile, onError) {
573
+ let watcher;
574
+ let scheduled;
575
+ let stopped = false;
576
+ let errorReported = false;
577
+ const schedule = () => {
578
+ if (stopped || scheduled) return;
579
+ scheduled = setImmediate(() => {
580
+ scheduled = void 0;
581
+ if (stopped) return;
582
+ try {
583
+ reconcile();
584
+ } catch (error2) {
585
+ onError(error2);
586
+ }
587
+ });
588
+ };
589
+ const attach = () => {
590
+ if (stopped || watcher) return;
591
+ try {
592
+ const next = watch(
593
+ directory,
594
+ { persistent: false },
595
+ (_event, filename) => {
596
+ const name = filename?.toString();
597
+ if (!name || relevant(name)) schedule();
598
+ }
599
+ );
600
+ next.on("error", (error2) => {
601
+ next.close();
602
+ if (watcher === next) watcher = void 0;
603
+ if (!stopped && !errorReported) {
604
+ errorReported = true;
605
+ onError(error2);
606
+ }
607
+ });
608
+ watcher = next;
609
+ } catch (error2) {
610
+ if (!errorReported) {
611
+ errorReported = true;
612
+ onError(error2);
613
+ }
614
+ }
615
+ };
616
+ attach();
617
+ const timer = setInterval(() => {
618
+ attach();
619
+ schedule();
620
+ }, 1e3);
621
+ timer.unref?.();
622
+ return () => {
623
+ stopped = true;
624
+ clearInterval(timer);
625
+ if (scheduled) clearImmediate(scheduled);
626
+ watcher?.close();
627
+ watcher = void 0;
628
+ };
629
+ }
571
630
  function agentMailboxPath(workspaceId, agentLabel) {
572
631
  return join3(
573
632
  root,
@@ -2144,16 +2203,51 @@ var MAX_PROCESS_CMDLINE_BYTES = 4 * 1024;
2144
2203
  var POLL_INTERVAL = 75;
2145
2204
  var INITIAL_RATIO = 0.65;
2146
2205
  var AGENT_SPLIT_RATIO = 0.5;
2147
- var LIFECYCLE_SUBSCRIPTIONS = [
2206
+ var HERDR_EVENT_SUBSCRIPTIONS = [
2207
+ { type: "workspace.created" },
2208
+ { type: "workspace.updated" },
2209
+ { type: "workspace.metadata_updated" },
2210
+ { type: "workspace.renamed" },
2211
+ { type: "pane.created" },
2148
2212
  { type: "pane.closed" },
2149
2213
  { type: "pane.exited" },
2150
2214
  { type: "pane.moved" },
2215
+ { type: "pane.updated" },
2216
+ { type: "pane.agent_detected" },
2217
+ { type: "pane.agent_status_changed" },
2218
+ { type: "tab.created" },
2151
2219
  { type: "tab.closed" },
2220
+ { type: "tab.renamed" },
2152
2221
  { type: "workspace.closed" },
2222
+ { type: "worktree.created" },
2223
+ { type: "worktree.opened" },
2153
2224
  { type: "worktree.removed" }
2154
2225
  ];
2155
- var LIFECYCLE_SUBSCRIPTION_ID = "pi-herdsman:lifecycle";
2156
- var LIFECYCLE_RECONNECT_MS = 1e3;
2226
+ var HERDR_EVENT_SUBSCRIPTION_ID = "pi-herdsman:events";
2227
+ var HERDR_EVENT_RECONNECT_MS = 1e3;
2228
+ var PRESENTATION_MIN_INTERVAL_MS = 2e3;
2229
+ var LIFECYCLE_EVENTS = /* @__PURE__ */ new Set([
2230
+ "workspace_created",
2231
+ "workspace_closed",
2232
+ "pane_created",
2233
+ "pane_closed",
2234
+ "pane_exited",
2235
+ "pane_moved",
2236
+ "tab_created",
2237
+ "tab_closed",
2238
+ "worktree_created",
2239
+ "worktree_opened",
2240
+ "worktree_removed"
2241
+ ]);
2242
+ var PRESENTATION_EVENTS = /* @__PURE__ */ new Set([
2243
+ "workspace_updated",
2244
+ "workspace_metadata_updated",
2245
+ "workspace_renamed",
2246
+ "pane_updated",
2247
+ "pane_agent_detected",
2248
+ "pane_agent_status_changed",
2249
+ "tab_renamed"
2250
+ ]);
2157
2251
  var MAX_EVENT_BUFFER_BYTES = 1024 * 1024;
2158
2252
  var HERDR_AGENT_STATE_EXTENSION = join5(
2159
2253
  getAgentDir3(),
@@ -2493,10 +2587,31 @@ async function herdrSessionSnapshot(pi, ctx, signal) {
2493
2587
  function watchHerdrLifecycle(socketPath2, signal, onChange) {
2494
2588
  let socket;
2495
2589
  let retry;
2590
+ let presentationTimer;
2591
+ let nextPresentationAt = 0;
2592
+ const invalidatePresentation = () => {
2593
+ if (presentationTimer || signal.aborted) return;
2594
+ presentationTimer = setTimeout(
2595
+ () => {
2596
+ presentationTimer = void 0;
2597
+ if (signal.aborted) return;
2598
+ nextPresentationAt = Date.now() + PRESENTATION_MIN_INTERVAL_MS;
2599
+ onChange(void 0, "presentation");
2600
+ },
2601
+ Math.max(0, nextPresentationAt - Date.now())
2602
+ );
2603
+ presentationTimer.unref?.();
2604
+ };
2605
+ const flushPresentation = () => {
2606
+ if (presentationTimer) clearTimeout(presentationTimer);
2607
+ presentationTimer = void 0;
2608
+ nextPresentationAt = Date.now() + PRESENTATION_MIN_INTERVAL_MS;
2609
+ };
2496
2610
  const connect = () => {
2497
2611
  if (signal.aborted) return;
2498
2612
  let buffer = "";
2499
2613
  let subscribed = false;
2614
+ let disconnectReported = false;
2500
2615
  const current = createConnection(socketPath2);
2501
2616
  socket = current;
2502
2617
  current.setEncoding("utf8");
@@ -2508,9 +2623,9 @@ function watchHerdrLifecycle(socketPath2, signal, onChange) {
2508
2623
  current.once("connect", () => {
2509
2624
  current.write(
2510
2625
  `${JSON.stringify({
2511
- id: LIFECYCLE_SUBSCRIPTION_ID,
2626
+ id: HERDR_EVENT_SUBSCRIPTION_ID,
2512
2627
  method: "events.subscribe",
2513
- params: { subscriptions: LIFECYCLE_SUBSCRIPTIONS }
2628
+ params: { subscriptions: HERDR_EVENT_SUBSCRIPTIONS }
2514
2629
  })}
2515
2630
  `
2516
2631
  );
@@ -2534,23 +2649,45 @@ function watchHerdrLifecycle(socketPath2, signal, onChange) {
2534
2649
  drop();
2535
2650
  return;
2536
2651
  }
2652
+ if (message?.id === HERDR_EVENT_SUBSCRIPTION_ID && message?.error) {
2653
+ if (message.error.code === "events_lost" && !signal.aborted) {
2654
+ disconnectReported = true;
2655
+ flushPresentation();
2656
+ onChange(void 0, "reconcile");
2657
+ }
2658
+ drop();
2659
+ return;
2660
+ }
2537
2661
  if (!subscribed) {
2538
- if (message?.id !== LIFECYCLE_SUBSCRIPTION_ID || message?.error || !message?.result) {
2662
+ if (signal.aborted || message?.id !== HERDR_EVENT_SUBSCRIPTION_ID || message?.result?.type !== "subscription_started") {
2539
2663
  drop();
2540
2664
  return;
2541
2665
  }
2542
2666
  subscribed = true;
2543
- onChange();
2667
+ flushPresentation();
2668
+ onChange(void 0, "reconcile");
2544
2669
  continue;
2545
2670
  }
2546
- onChange(removedHerdrWorktree(message));
2671
+ if (signal.aborted) return;
2672
+ const event = message?.event;
2673
+ const category = LIFECYCLE_EVENTS.has(event) ? "lifecycle" : PRESENTATION_EVENTS.has(event) ? "presentation" : void 0;
2674
+ if (category === "presentation") invalidatePresentation();
2675
+ else if (category) {
2676
+ flushPresentation();
2677
+ onChange(removedHerdrWorktree(message), category);
2678
+ }
2547
2679
  }
2548
2680
  });
2549
2681
  current.on("error", drop);
2550
2682
  current.once("close", () => {
2551
2683
  if (socket === current) socket = void 0;
2552
2684
  if (signal.aborted) return;
2553
- retry = setTimeout(connect, LIFECYCLE_RECONNECT_MS);
2685
+ if (subscribed && !disconnectReported) {
2686
+ disconnectReported = true;
2687
+ flushPresentation();
2688
+ onChange(void 0, "reconcile");
2689
+ }
2690
+ retry = setTimeout(connect, HERDR_EVENT_RECONNECT_MS);
2554
2691
  retry.unref?.();
2555
2692
  });
2556
2693
  };
@@ -2558,6 +2695,7 @@ function watchHerdrLifecycle(socketPath2, signal, onChange) {
2558
2695
  "abort",
2559
2696
  () => {
2560
2697
  if (retry) clearTimeout(retry);
2698
+ if (presentationTimer) clearTimeout(presentationTimer);
2561
2699
  socket?.destroy();
2562
2700
  },
2563
2701
  { once: true }
@@ -3895,7 +4033,7 @@ import {
3895
4033
  } from "@earendil-works/pi-coding-agent";
3896
4034
  import { contentText as contentText2 } from "@earendil-works/pi-ai";
3897
4035
  import { realpathSync as realpathSync3, statSync as statSync5 } from "node:fs";
3898
- import { watchFile, unwatchFile, unlinkSync as unlinkSync7 } from "node:fs";
4036
+ import { unlinkSync as unlinkSync7 } from "node:fs";
3899
4037
  import { createHash as createHash7, randomUUID as randomUUID8 } from "node:crypto";
3900
4038
  import { homedir as homedir3 } from "node:os";
3901
4039
  import { join as join9, resolve as resolve4 } from "node:path";
@@ -8090,7 +8228,7 @@ function projectSupervision(options) {
8090
8228
  // extension/agent-controller.ts
8091
8229
  function buildAgentStatusSnapshot(view, ctx, host) {
8092
8230
  const ownerSessionId = ctx.sessionManager.getSessionId();
8093
- const unresolvedMailboxState = host.scope.kind === "lead" && host.hasStateIssues?.();
8231
+ const unresolvedMailboxState = host.scope.kind === "lead" && view.stateIssues.length > 0;
8094
8232
  const listed = view.visible.map(
8095
8233
  (snapshot) => host.listedAgentRecord(
8096
8234
  view,
@@ -8262,6 +8400,7 @@ function createAgentStatusRuntime() {
8262
8400
  if (ctx.mode !== "tui" || !ctx.hasUI) return;
8263
8401
  const primed = statusPrimed && ctx === statusContext;
8264
8402
  const generation = ctx === statusContext && requestActive ? statusGeneration : ++statusGeneration;
8403
+ clearTimer();
8265
8404
  statusContext = ctx;
8266
8405
  ctx.ui.setWidget("pi-herdsman", (tui, theme) => {
8267
8406
  const widget = createStatusWidget(() => tui.requestRender(), theme);
@@ -8273,7 +8412,8 @@ function createAgentStatusRuntime() {
8273
8412
  return widget;
8274
8413
  });
8275
8414
  requestActive = true;
8276
- statusTimer = setInterval(() => void refresh(ctx, generation), 2e3);
8415
+ statusTimer = setInterval(() => void refresh(ctx, generation), 1e4);
8416
+ statusTimer.unref?.();
8277
8417
  statusPrimed = false;
8278
8418
  if (!primed) void refresh(ctx, generation);
8279
8419
  },
@@ -8506,11 +8646,6 @@ pending-result agent work still blocks escalation.`;
8506
8646
  function durableIdentityKey(state) {
8507
8647
  return `${state.workspaceId}\0${state.piSessionId}`;
8508
8648
  }
8509
- function durableParentCandidates(snapshot, child) {
8510
- return snapshot.filter(
8511
- ({ state }) => state.workspaceId === child.workspaceId && state.piSessionId === child.ownerSessionId
8512
- );
8513
- }
8514
8649
  function directChildStates(parent) {
8515
8650
  return listAgentStates().filter(
8516
8651
  ({ state }) => state.workspaceId === parent.workspaceId && state.ownerSessionId === parent.piSessionId
@@ -8635,37 +8770,31 @@ function visibleAgentSnapshots(snapshot, scope, ownerSessionId) {
8635
8770
  );
8636
8771
  if (scope.kind === "managed-agent") return direct;
8637
8772
  const visible = [...direct];
8638
- const visibleIdentities = new Set(
8639
- direct.map(({ state }) => durableIdentityKey(state))
8640
- );
8641
- const pending = agents.filter(
8642
- ({ state }) => state.ownerSessionId !== ownerSessionId
8643
- );
8644
- while (pending.length) {
8645
- let progressed = false;
8646
- for (let index = pending.length - 1; index >= 0; index--) {
8647
- const agent = pending[index];
8648
- const parents = durableParentCandidates(agents, agent.state);
8649
- if (parents.length !== 1) continue;
8650
- const parent = parents[0];
8651
- if (!visibleIdentities.has(durableIdentityKey(parent.state))) continue;
8652
- visible.push({ ...agent, parentLabel: parent.state.agentLabel });
8653
- visibleIdentities.add(durableIdentityKey(agent.state));
8654
- pending.splice(index, 1);
8655
- progressed = true;
8656
- }
8657
- if (!progressed) break;
8773
+ const byIdentity = /* @__PURE__ */ new Map();
8774
+ const childrenByOwner = /* @__PURE__ */ new Map();
8775
+ for (const agent of agents) {
8776
+ const identity = durableIdentityKey(agent.state);
8777
+ const owner = `${agent.state.workspaceId}\0${agent.state.ownerSessionId}`;
8778
+ const identityCandidates = byIdentity.get(identity) ?? [];
8779
+ identityCandidates.push(agent);
8780
+ byIdentity.set(identity, identityCandidates);
8781
+ const children = childrenByOwner.get(owner) ?? [];
8782
+ children.push(agent);
8783
+ childrenByOwner.set(owner, children);
8784
+ }
8785
+ const seen = new Set(direct);
8786
+ for (let index = 0; index < visible.length; index++) {
8787
+ const parent = visible[index];
8788
+ const identity = durableIdentityKey(parent.state);
8789
+ if (byIdentity.get(identity)?.length !== 1) continue;
8790
+ for (const child of childrenByOwner.get(identity) ?? []) {
8791
+ if (seen.has(child)) continue;
8792
+ seen.add(child);
8793
+ visible.push({ ...child, parentLabel: parent.state.agentLabel });
8794
+ }
8658
8795
  }
8659
8796
  return visible;
8660
8797
  }
8661
- function unknownAgentRecords() {
8662
- return listAgentStateIssues().map(({ diagnostic }) => ({
8663
- state: "unknown",
8664
- available_tools: [],
8665
- managed: true,
8666
- diagnostic
8667
- }));
8668
- }
8669
8798
  function statusBreadcrumb(snapshot, candidate, identity, sameIdentity) {
8670
8799
  if (!candidate) return ["?"];
8671
8800
  const current = snapshot.agents.find(
@@ -9417,11 +9546,25 @@ function assertManagedAgentCascadeSafe(snapshots, sameIdentity, deliveredRootRes
9417
9546
  async function managedAgentSnapshots(pi, ctx, dependencies, signal, proveLead = false, allWorkspaces = false, suppliedInventory, allowTranscriptDefinitionFallback = true) {
9418
9547
  const inventory = suppliedInventory ?? await herdrSessionSnapshot(pi, ctx, signal);
9419
9548
  const currentWorkspaceId = process.env.HERDR_WORKSPACE_ID ?? ctx.cwd;
9420
- const allMailboxes = listAgentStates();
9549
+ const { states: allMailboxes, issues: stateIssues } = scanAgentStates();
9421
9550
  const mailboxes = allWorkspaces ? allMailboxes : allMailboxes.filter(
9422
9551
  ({ state }) => state.workspaceId === currentWorkspaceId
9423
9552
  );
9424
- const agents = mailboxes.map(({ path, state }) => {
9553
+ const pendingOwners = /* @__PURE__ */ new Set();
9554
+ const completedResultsPending = mailboxes.map(({ path, state }) => {
9555
+ let pending = false;
9556
+ if (state.completedRequestId) {
9557
+ try {
9558
+ pending = !!readResult(path, state.completedRequestId);
9559
+ } catch {
9560
+ pending = true;
9561
+ }
9562
+ }
9563
+ if (state.resultError || state.activeRequestId || pending)
9564
+ pendingOwners.add(`${state.workspaceId}\0${state.ownerSessionId}`);
9565
+ return pending;
9566
+ });
9567
+ const agents = mailboxes.map(({ path, state }, index) => {
9425
9568
  const presence = managedAgentPresence(state, inventory);
9426
9569
  const agent = presence.kind === "live" ? presence.agent : void 0;
9427
9570
  let agentDefinition;
@@ -9440,14 +9583,7 @@ async function managedAgentSnapshots(pi, ctx, dependencies, signal, proveLead =
9440
9583
  const piSessionId = session?.kind === "id" ? session.value : state.piSessionId;
9441
9584
  const piSessionPath = session?.kind === "path" ? session.value : state.piSessionFile;
9442
9585
  const lifecycleState = agent ? normalizeHerdrLifecycleState(agent) : "unknown";
9443
- let completionPending = false;
9444
- if (state.completedRequestId) {
9445
- try {
9446
- completionPending = !!readResult(path, state.completedRequestId);
9447
- } catch {
9448
- completionPending = true;
9449
- }
9450
- }
9586
+ const completionPending = completedResultsPending[index];
9451
9587
  const handoffPending = unacknowledgedRequestExists(path, state);
9452
9588
  const liveState = agent ? agentControlState(
9453
9589
  lifecycleState,
@@ -9457,19 +9593,7 @@ async function managedAgentSnapshots(pi, ctx, dependencies, signal, proveLead =
9457
9593
  !!state.pendingAskId,
9458
9594
  !!state.resultError
9459
9595
  ) : "unknown";
9460
- const pendingDirectChildWork = mailboxes.some(
9461
- ({ path: childPath, state: child }) => {
9462
- if (child.workspaceId !== state.workspaceId || child.ownerSessionId !== state.piSessionId)
9463
- return false;
9464
- if (child.resultError || child.activeRequestId) return true;
9465
- if (!child.completedRequestId) return false;
9466
- try {
9467
- return !!readResult(childPath, child.completedRequestId);
9468
- } catch {
9469
- return true;
9470
- }
9471
- }
9472
- );
9596
+ const pendingDirectChildWork = pendingOwners.has(durableIdentityKey(state));
9473
9597
  const waitingForChildren = liveState === "settling" && !!state.activeRequestId && !completionPending && !handoffPending && !state.resultError && pendingDirectChildWork;
9474
9598
  const existingLiveProjection = waitingForChildren ? "blocked" : liveState;
9475
9599
  const projectedState = completionPending || state.resultError ? "settling" : presence.kind === "lost" ? "lost" : presence.kind === "unknown" ? "unknown" : existingLiveProjection;
@@ -9512,7 +9636,13 @@ async function managedAgentSnapshots(pi, ctx, dependencies, signal, proveLead =
9512
9636
  } catch {
9513
9637
  }
9514
9638
  }
9515
- return { agents, mailboxes, liveAgents: inventory.agents, leadSessionIds };
9639
+ return {
9640
+ agents,
9641
+ mailboxes,
9642
+ stateIssues,
9643
+ liveAgents: inventory.agents,
9644
+ leadSessionIds
9645
+ };
9516
9646
  }
9517
9647
  function listedAgentRecord(view, snapshot, ownerSessionId, scope, unresolvedMailboxState, dependencies) {
9518
9648
  const { listed, state, presence, parentLabel } = snapshot;
@@ -9581,9 +9711,7 @@ function createAgentController(pi, options) {
9581
9711
  const runtimes = /* @__PURE__ */ new Map();
9582
9712
  const pendingStarts = /* @__PURE__ */ new Map();
9583
9713
  const resultWatchers = /* @__PURE__ */ new Map();
9584
- const resultWatchRetries = /* @__PURE__ */ new Map();
9585
9714
  const askWatchers = /* @__PURE__ */ new Map();
9586
- const askWatchRetries = /* @__PURE__ */ new Map();
9587
9715
  const resultDeliveryInFlight = /* @__PURE__ */ new Set();
9588
9716
  const askDeliveryInFlight = /* @__PURE__ */ new Set();
9589
9717
  const resultDeliveryRetries = /* @__PURE__ */ new Map();
@@ -10279,7 +10407,8 @@ function createAgentController(pi, options) {
10279
10407
  runtime.startedAt = Date.now();
10280
10408
  runtime.contextPercent = void 0;
10281
10409
  watchResult(runtime, context, signal);
10282
- watchAsk(runtime, context, signal);
10410
+ if (runtime.activeRequestId === requestId)
10411
+ watchAsk(runtime, context, signal);
10283
10412
  }
10284
10413
  return requestId;
10285
10414
  } finally {
@@ -10520,7 +10649,12 @@ function createAgentController(pi, options) {
10520
10649
  scope,
10521
10650
  context.sessionManager.getSessionId()
10522
10651
  );
10523
- const unknown = scope?.kind === "lead" ? unknownAgentRecords() : [];
10652
+ const unknown = scope?.kind === "lead" ? snapshot2.stateIssues.map(({ diagnostic }) => ({
10653
+ state: "unknown",
10654
+ available_tools: [],
10655
+ managed: true,
10656
+ diagnostic
10657
+ })) : [];
10524
10658
  const unresolved = unknown.length > 0;
10525
10659
  const agents = visible.map(
10526
10660
  (item) => listRecord(
@@ -10630,7 +10764,7 @@ function createAgentController(pi, options) {
10630
10764
  "Agent belongs to another owner session",
10631
10765
  "transcript"
10632
10766
  );
10633
- const unresolved = scope?.kind === "lead" && listAgentStateIssues().length > 0;
10767
+ const unresolved = scope?.kind === "lead" && view.stateIssues.length > 0;
10634
10768
  const actions = listedAgentRecord(
10635
10769
  view,
10636
10770
  candidate,
@@ -11106,21 +11240,13 @@ function createAgentController(pi, options) {
11106
11240
  if (!id) return;
11107
11241
  cancelResultRetry(resultDeliveryRetries, runtime, id);
11108
11242
  cancelResultRetry(resultCleanupRetries, runtime, id);
11109
- const path = `${runtime.mailboxPath}/result-${id}.json`;
11110
- const retry = resultWatchRetries.get(path);
11111
- if (retry) clearTimeout(retry);
11112
- resultWatchRetries.delete(path);
11113
- const watcher = resultWatchers.get(path);
11114
- if (watcher) unwatchFile(path, watcher);
11243
+ const path = join9(runtime.mailboxPath, `result-${id}.json`);
11244
+ resultWatchers.get(path)?.();
11115
11245
  resultWatchers.delete(path);
11116
11246
  };
11117
11247
  const stopAskWatcher = (runtime) => {
11118
11248
  const path = agentStatePath(runtime.mailboxPath);
11119
- const retry = askWatchRetries.get(path);
11120
- if (retry) clearTimeout(retry);
11121
- askWatchRetries.delete(path);
11122
- const watcher = askWatchers.get(path);
11123
- if (watcher) unwatchFile(path, watcher);
11249
+ askWatchers.get(path)?.();
11124
11250
  askWatchers.delete(path);
11125
11251
  };
11126
11252
  const invalidateRuntime = (label) => {
@@ -11141,29 +11267,10 @@ function createAgentController(pi, options) {
11141
11267
  runtime.startedAt = void 0;
11142
11268
  runtimes.delete(label);
11143
11269
  };
11144
- const watch = (path, watchers, retries, context, check, retry) => {
11145
- watchers.set(path, check);
11146
- try {
11147
- check();
11148
- if (watchers.get(path) !== check) return;
11149
- watchFile(path, { interval: 250 }, check);
11150
- } catch (error2) {
11151
- watchers.delete(path);
11152
- options.reportWatcherError(context, error2);
11153
- if (!retries.has(path)) {
11154
- retries.set(
11155
- path,
11156
- setTimeout(() => {
11157
- retries.delete(path);
11158
- retry();
11159
- }, 250)
11160
- );
11161
- }
11162
- }
11163
- };
11164
11270
  const watchResult = (runtime, context, signal, requestId = runtime.activeRequestId) => {
11165
11271
  if (!requestId) return;
11166
- const path = `${runtime.mailboxPath}/result-${requestId}.json`;
11272
+ const filename = `result-${requestId}.json`;
11273
+ const path = join9(runtime.mailboxPath, filename);
11167
11274
  stopResultWatcher(runtime, requestId);
11168
11275
  const check = () => {
11169
11276
  if (active && runtimes.get(runtime.label) === runtime)
@@ -11173,10 +11280,14 @@ function createAgentController(pi, options) {
11173
11280
  } catch {
11174
11281
  }
11175
11282
  };
11176
- watch(path, resultWatchers, resultWatchRetries, context, check, () => {
11177
- if (runtime.activeRequestId === requestId && active)
11178
- watchResult(runtime, context, signal, requestId);
11179
- });
11283
+ const stop = observeMailbox(
11284
+ runtime.mailboxPath,
11285
+ (name) => name === filename,
11286
+ check,
11287
+ (error2) => options.reportWatcherError(context, error2)
11288
+ );
11289
+ resultWatchers.set(path, stop);
11290
+ check();
11180
11291
  };
11181
11292
  const watchAsk = (runtime, context, signal) => {
11182
11293
  const path = agentStatePath(runtime.mailboxPath);
@@ -11189,21 +11300,16 @@ function createAgentController(pi, options) {
11189
11300
  if (error2 instanceof OperationError && error2.detail.category === "incompatible_build")
11190
11301
  return;
11191
11302
  options.reportWatcherError(context, error2);
11192
- if (!askWatchRetries.has(path))
11193
- askWatchRetries.set(
11194
- path,
11195
- setTimeout(() => {
11196
- askWatchRetries.delete(path);
11197
- if (active && runtimes.get(runtime.label) === runtime)
11198
- watchAsk(runtime, context, signal);
11199
- }, 250)
11200
- );
11201
11303
  }
11202
11304
  };
11203
- watch(path, askWatchers, askWatchRetries, context, check, () => {
11204
- if (active && runtimes.get(runtime.label) === runtime)
11205
- watchAsk(runtime, context, signal);
11206
- });
11305
+ const stop = observeMailbox(
11306
+ runtime.mailboxPath,
11307
+ (name) => name === "state.json" || name === "ask.json",
11308
+ check,
11309
+ (error2) => options.reportWatcherError(context, error2)
11310
+ );
11311
+ askWatchers.set(path, stop);
11312
+ check();
11207
11313
  };
11208
11314
  const resultIdentity = (runtime, requestId) => ({
11209
11315
  runId: runtime.runId,
@@ -11717,6 +11823,8 @@ function createAgentController(pi, options) {
11717
11823
  runtime.task = void 0;
11718
11824
  runtime.startedAt = void 0;
11719
11825
  runtime.contextPercent = void 0;
11826
+ if (pendingStarts.get(runtime.label)?.requestId === result.requestId)
11827
+ pendingStarts.delete(runtime.label);
11720
11828
  if (!await finalizeDeliveredResult(runtime, result, context, signal))
11721
11829
  scheduleResultCleanupRetry(runtime, result, context, signal);
11722
11830
  clearCleanupError(runtime, "Result delivery failed; retrying:");
@@ -12025,6 +12133,7 @@ function createAgentController(pi, options) {
12025
12133
  const pendingStart = {
12026
12134
  label,
12027
12135
  definition: agentDefinition,
12136
+ requestId: assignment.requestId,
12028
12137
  ...p.task !== void 0 ? { task: p.task } : {},
12029
12138
  startedAt: Date.now(),
12030
12139
  ...scope.kind === "managed-agent" && process.env.PI_HERDSMAN_LABEL ? { parentLabel: process.env.PI_HERDSMAN_LABEL } : {}
@@ -12265,7 +12374,6 @@ ${result.stderr ?? ""}`.trim(),
12265
12374
  p.action,
12266
12375
  assignmentInput.resultBindings
12267
12376
  );
12268
- pendingStart.requestId = requestId;
12269
12377
  accepted = true;
12270
12378
  options.onChanged();
12271
12379
  return {
@@ -12277,7 +12385,7 @@ ${result.stderr ?? ""}`.trim(),
12277
12385
  pane_id: runtime.paneId,
12278
12386
  session_id: runtime.piSessionId,
12279
12387
  session_path: runtime.piSessionFile,
12280
- request_id: runtime.activeRequestId
12388
+ request_id: requestId
12281
12389
  };
12282
12390
  } catch (caught) {
12283
12391
  if (promptWriteFailed) throw caught;
@@ -12943,7 +13051,7 @@ ${result.stderr ?? ""}`.trim(),
12943
13051
  ...snapshot2,
12944
13052
  visible: visibleAgentSnapshots(snapshot2, options.scope, ownerSessionId)
12945
13053
  };
12946
- const unresolvedMailboxState = options.scope?.kind === "lead" && listAgentStateIssues().length > 0;
13054
+ const unresolvedMailboxState = options.scope?.kind === "lead" && snapshot2.stateIssues.length > 0;
12947
13055
  const now = Date.now();
12948
13056
  const ownedRuns = new Set(
12949
13057
  snapshot2.agents.filter(({ state }) => state.ownerSessionId === ownerSessionId).map(({ state }) => state.runId)
@@ -13418,11 +13526,15 @@ ${result.stderr ?? ""}`.trim(),
13418
13526
  };
13419
13527
  requestHealthScan();
13420
13528
  if (process.env.HERDR_SOCKET_PATH) {
13421
- watchHerdrLifecycle(process.env.HERDR_SOCKET_PATH, signal, (removed) => {
13422
- options.onChanged();
13423
- requestHealthScan();
13424
- if (removed) options.onWorktreeRemoved?.(removed, ctx, signal);
13425
- });
13529
+ watchHerdrLifecycle(
13530
+ process.env.HERDR_SOCKET_PATH,
13531
+ signal,
13532
+ (removed, category) => {
13533
+ options.onChanged();
13534
+ if (category !== "presentation") requestHealthScan();
13535
+ if (removed) options.onWorktreeRemoved?.(removed, ctx, signal);
13536
+ }
13537
+ );
13426
13538
  }
13427
13539
  schedule();
13428
13540
  };
@@ -13524,14 +13636,10 @@ ${result.stderr ?? ""}`.trim(),
13524
13636
  },
13525
13637
  shutdown() {
13526
13638
  active = false;
13527
- for (const [path, watcher] of resultWatchers) unwatchFile(path, watcher);
13528
- for (const [path, watcher] of askWatchers) unwatchFile(path, watcher);
13639
+ for (const watcher of resultWatchers.values()) watcher();
13640
+ for (const watcher of askWatchers.values()) watcher();
13529
13641
  resultWatchers.clear();
13530
13642
  askWatchers.clear();
13531
- for (const timer of resultWatchRetries.values()) clearTimeout(timer);
13532
- for (const timer of askWatchRetries.values()) clearTimeout(timer);
13533
- resultWatchRetries.clear();
13534
- askWatchRetries.clear();
13535
13643
  for (const timer of resultDeliveryRetries.values()) clearTimeout(timer);
13536
13644
  for (const timer of resultCleanupRetries.values()) clearTimeout(timer);
13537
13645
  for (const timer of askDeliveryRetries.values()) clearTimeout(timer);
@@ -13874,7 +13982,7 @@ function createManagedAgentExecutionState() {
13874
13982
  resultWriteAttempts: 0,
13875
13983
  retryTimer: void 0,
13876
13984
  stateRetryTimer: void 0,
13877
- requestPumpTimer: void 0,
13985
+ stopRequestObserver: void 0,
13878
13986
  stateErrorReported: false,
13879
13987
  resultErrorReported: false,
13880
13988
  agentContext: void 0,
@@ -14202,7 +14310,7 @@ function createManagedAgentLeafStatus(options) {
14202
14310
  else next.dispose();
14203
14311
  return next;
14204
14312
  });
14205
- timer = setInterval(() => void refresh(ctx, activeGeneration), 2e3);
14313
+ timer = setInterval(() => void refresh(ctx, activeGeneration), 1e4);
14206
14314
  void refresh(ctx, activeGeneration);
14207
14315
  }
14208
14316
  };
@@ -15007,8 +15115,8 @@ This is the session-start definition snapshot. Use list_agents for live Agent st
15007
15115
  }));
15008
15116
  };
15009
15117
  const resetRequestPump = () => {
15010
- if (execution.requestPumpTimer) clearInterval(execution.requestPumpTimer);
15011
- execution.requestPumpTimer = void 0;
15118
+ execution.stopRequestObserver?.();
15119
+ execution.stopRequestObserver = void 0;
15012
15120
  requestState.reset();
15013
15121
  };
15014
15122
  const clearAgentRuntimes = () => controller?.clearRuntimes();
@@ -15573,12 +15681,13 @@ function registerManagedAgentSessionStartHandler(pi, execution, options) {
15573
15681
  if (options.delegationEnabled)
15574
15682
  options.startControllerHealthScanner(ctx, metadataSignal);
15575
15683
  execution.initialized = true;
15576
- options.pumpRequest(ctx);
15577
- execution.requestPumpTimer = setInterval(
15684
+ execution.stopRequestObserver = observeMailbox(
15685
+ mailbox,
15686
+ (name) => /^request-.*\.json$/.test(name),
15578
15687
  () => options.pumpRequest(ctx),
15579
- 250
15688
+ (error2) => options.appendError(ctx, error2)
15580
15689
  );
15581
- execution.requestPumpTimer.unref?.();
15690
+ options.pumpRequest(ctx);
15582
15691
  if (!options.delegationEnabled && ctx.mode === "tui" && ctx.hasUI)
15583
15692
  options.startLeafStatus(ctx);
15584
15693
  const model = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : void 0;
@@ -15596,6 +15705,7 @@ function registerManagedAgentSessionStartHandler(pi, execution, options) {
15596
15705
  );
15597
15706
  } catch (error2) {
15598
15707
  execution.initialized = false;
15708
+ options.resetRequestPump();
15599
15709
  options.statusRuntime.clear();
15600
15710
  options.setControllerReady(false);
15601
15711
  execution.assignment = void 0;
@@ -16107,19 +16217,21 @@ function createLeadCommandRuntime(host) {
16107
16217
  async openDefinitionsMenu(ctx) {
16108
16218
  let selectedDefinition;
16109
16219
  const loadDefinitions = async () => {
16110
- const { definitions, projectTrusted } = await host.contextAgentDefinitions(ctx);
16220
+ const { definitions, leadDefinitions } = await host.contextAgentDefinitions(ctx);
16111
16221
  return {
16112
16222
  definitions,
16113
16223
  leadDefinitions: [
16114
16224
  FLEXIBLE_LEAD_DEFINITION_NAME,
16115
16225
  ORCHESTRATOR_LEAD_DEFINITION_NAME,
16116
16226
  MANAGED_LEAD_DEFINITION_NAME
16117
- ].map(
16118
- (name) => host.discoverLeadDefinition(
16119
- name,
16120
- projectTrusted ? { projectRoot: ctx.cwd } : {}
16121
- )
16122
- )
16227
+ ].map((name) => {
16228
+ const definition = leadDefinitions.find(
16229
+ (candidate) => candidate.name === name
16230
+ );
16231
+ if (!definition)
16232
+ throw new Error(`Lead definition ${name} not found`);
16233
+ return definition;
16234
+ })
16123
16235
  };
16124
16236
  };
16125
16237
  const resolveDefinition = async (name) => {
@@ -16790,6 +16902,14 @@ function registerLeadRuntime(pi, options) {
16790
16902
  return !!file2?.isFile() && file2.size > 0;
16791
16903
  })
16792
16904
  });
16905
+ const writeProjectAssignment2 = (...args) => {
16906
+ options.projectHost.writeProjectAssignment(...args);
16907
+ supervisionUiRuntime.requestRefresh();
16908
+ };
16909
+ const removeProjectAssignment2 = (...args) => {
16910
+ options.roleTransitionHost.removeProjectAssignment(...args);
16911
+ supervisionUiRuntime.requestRefresh();
16912
+ };
16793
16913
  const clearSupervisionUI = (removeWidget = true) => {
16794
16914
  supervisionUiRuntime.stopPeriodic();
16795
16915
  if (removeWidget && leadRuntime.leadContext)
@@ -16939,6 +17059,7 @@ function registerLeadRuntime(pi, options) {
16939
17059
  };
16940
17060
  const roleTransitionServices = {
16941
17061
  ...roleHost,
17062
+ removeProjectAssignment: removeProjectAssignment2,
16942
17063
  controllerScope: { kind: "lead" },
16943
17064
  activationGuard,
16944
17065
  advancePeerPresenceGeneration: () => coordinationRuntime.advancePresenceGeneration(),
@@ -17018,7 +17139,10 @@ function registerLeadRuntime(pi, options) {
17018
17139
  ...options.controllerServices,
17019
17140
  scope: { kind: "lead" },
17020
17141
  build: options.build,
17021
- onChanged: () => statusRuntime.requestRefresh(),
17142
+ onChanged: () => {
17143
+ statusRuntime.requestRefresh();
17144
+ supervisionUiRuntime.requestRefresh();
17145
+ },
17022
17146
  onWorkChanged: (ctx) => herdRunRuntime?.maybeFinish(ctx),
17023
17147
  onWorktreeRemoved: (removed, ctx, signal) => {
17024
17148
  void roleTransitions.retireRemovedProjectWork(removed, ctx, signal).then(async () => {
@@ -17111,6 +17235,7 @@ function registerLeadRuntime(pi, options) {
17111
17235
  const projectHost = options.projectHost;
17112
17236
  const projectServices = {
17113
17237
  ...projectHost,
17238
+ writeProjectAssignment: writeProjectAssignment2,
17114
17239
  liveAgent,
17115
17240
  liveLead,
17116
17241
  currentManager: (...args) => roleTransitions.currentManager(...args),
@@ -17891,6 +18016,8 @@ function createLeadSupervisionRuntime(host) {
17891
18016
  let generationId;
17892
18017
  let stale = false;
17893
18018
  let timer;
18019
+ let redrawTimer;
18020
+ let uiContext;
17894
18021
  let inFlight;
17895
18022
  let pending;
17896
18023
  let overviewGeneration = 0;
@@ -18355,15 +18482,35 @@ function createLeadSupervisionRuntime(host) {
18355
18482
  },
18356
18483
  startPeriodic: (ctx) => {
18357
18484
  if (timer) clearInterval(timer);
18358
- timer = setInterval(() => void refresh(ctx), 2e3);
18485
+ if (redrawTimer) clearInterval(redrawTimer);
18486
+ timer = void 0;
18487
+ redrawTimer = void 0;
18488
+ uiContext = void 0;
18489
+ if (ctx.mode !== "tui" || !ctx.hasUI) return;
18490
+ uiContext = ctx;
18491
+ timer = setInterval(() => {
18492
+ if (uiContext === ctx) void refresh(ctx);
18493
+ }, 1e4);
18359
18494
  timer.unref?.();
18495
+ if (host.activeRole() === "manager") {
18496
+ redrawTimer = setInterval(() => {
18497
+ if (uiContext === ctx) renderWidget?.();
18498
+ }, 2e3);
18499
+ redrawTimer.unref?.();
18500
+ }
18501
+ },
18502
+ requestRefresh: () => {
18503
+ if (uiContext) void refresh(uiContext);
18360
18504
  },
18361
18505
  stopPeriodic: () => {
18362
18506
  pending = void 0;
18507
+ uiContext = void 0;
18363
18508
  renderOverview = void 0;
18364
18509
  renderWidget = void 0;
18365
18510
  if (timer) clearInterval(timer);
18366
18511
  timer = void 0;
18512
+ if (redrawTimer) clearInterval(redrawTimer);
18513
+ redrawTimer = void 0;
18367
18514
  }
18368
18515
  };
18369
18516
  }
@@ -23310,7 +23457,6 @@ ${summary}`,
23310
23457
  appendDurableError
23311
23458
  },
23312
23459
  statusSnapshotHost: {
23313
- hasStateIssues: () => listAgentStateIssues().length > 0,
23314
23460
  environmentIdentity: (ctx) => managedAgentEnvironmentIdentity(ctx, HERDSMAN_BUILD),
23315
23461
  identityFromEnvironment: () => ({
23316
23462
  definition: process.env.PI_HERDSMAN_AGENT_DEFINITION,