pi-herdsman 0.20.1 → 0.21.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/SKILL.md CHANGED
@@ -375,10 +375,10 @@ Chief supervises independent leads and never changes ownership. Use
375
375
  supervisor's attention, decision, or action, such as a blocker, warning, scope
376
376
  change, risk, or important evidence. Ordinary Leads without a verified
377
377
  supervisor continue independently; assigned project Leads may message the
378
- Manager role while its process is unavailable, and those messages remain
379
- retained with the project. For an assigned project, Herdsman automatically
380
- records the Lead's normal response to each project-assignment delivery as a
381
- project message for the current or a replacement Manager. If the Lead
378
+ Manager role while its process is unavailable. Messages remain pending during
379
+ Manager absence and are delivered to the next Manager that can receive them. For an assigned project, Herdsman
380
+ automatically records the Lead's normal response to each project-assignment
381
+ delivery as a project message for the Manager role. If the Lead
382
382
  successfully delegates or continues managed Agent work, the herd run owns that
383
383
  handoff until it settles; summarize the outcome, validation, and important
384
384
  unresolved points in the settled response. An unsuccessful delegation leaves
@@ -389,8 +389,9 @@ Descendants use `ask_owner`, not supervisor tools. Messages are coordination
389
389
  data, not assignments or terminal project results.
390
390
 
391
391
  `supervisor_message` is available to ordinary Leads and Managers. Assigned Lead
392
- messages are retained with the project and remain available to a replacement
393
- Manager, including when no Manager is active at send time. Manager messages to
392
+ messages remain pending while Manager is absent and are delivered to the next
393
+ Manager that can receive them; delivered messages are Pi conversation history,
394
+ not pending Herdsman coordination. Manager messages to
394
395
  Leads use `staff_message`. For files, these tools accept ordinary paths,
395
396
  reusable direct-agent result refs, and already-supplied canonical result refs
396
397
  through `files`.
@@ -451,17 +452,17 @@ Pi Herdsman uses one durable vocabulary:
451
452
  through implementation and review iterations;
452
453
  - `staff_stop` stops an exact Lead and its owned Agent tree while preserving
453
454
  the assignment, Pi session, branch, and worktree. Successful Herdr worktree
454
- removal retires the matching assignment and retained project messages while
455
- preserving the Git branch. A missing worktree alone does not retire the
455
+ removal retires the matching assignment and any still-pending project
456
+ messages while preserving the Git branch. A missing worktree alone does not retire the
456
457
  assignment; `staff_resume` can reconstruct it. Never infer ownership from
457
458
  worktree membership or start an overlapping writer beside another Lead;
458
459
  - `/manager leave` preserves project work; a later Manager can resume the same
459
460
  branch-based assignment;
460
461
  - a **herd** is one Lead and the complete Agent tree it owns;
461
462
  - a **Lead** owns its Agents. An assigned project Lead sends
462
- `supervisor_message` to project-scoped storage for the current or a
463
- replacement Manager, even when no Manager is active; it does not route those
464
- messages to Chief. An unassigned Lead routes to its active Manager when one
463
+ `supervisor_message` to project-scoped storage; pending messages survive
464
+ Manager absence and go to the next Manager that can receive them. Delivered
465
+ messages are not replayed; messages do not route to Chief. An unassigned Lead routes to its active Manager when one
465
466
  exists, otherwise to Chief;
466
467
  - an **Agent** handles one bounded assignment and may delegate only when its
467
468
  definition allows it;
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.20.1",
18
+ version: "0.21.0",
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",
@@ -57,7 +57,8 @@ var package_default = {
57
57
  "./dist/index.js"
58
58
  ],
59
59
  skills: [
60
- "."
60
+ "./SKILL.md",
61
+ "./skills"
61
62
  ],
62
63
  image: "https://raw.githubusercontent.com/boadij/pi-herdsman/main/docs/assets/thumbnail.webp"
63
64
  },
@@ -106,7 +107,8 @@ var package_default = {
106
107
  "SKILL.md",
107
108
  "LICENSE",
108
109
  "docs",
109
- "dist"
110
+ "dist",
111
+ "skills"
110
112
  ],
111
113
  publishConfig: {
112
114
  registry: "https://registry.npmjs.org/"
@@ -5455,6 +5457,11 @@ function expandedResultLines(tool, action, details, args, theme) {
5455
5457
  "session",
5456
5458
  details.session ?? details.session_id ?? details.pi_session_id ?? details.chiefSessionId
5457
5459
  ],
5460
+ ["branch", tool === "staff" ? details.branch : void 0],
5461
+ [
5462
+ "task",
5463
+ tool === "staff" && action === "resume" ? details.presentation_task : void 0
5464
+ ],
5458
5465
  ["request", details.request_id],
5459
5466
  ["assignment request", details.assignment_request_id],
5460
5467
  ["pane", details.pane_id],
@@ -5511,8 +5518,26 @@ function expandedResultLines(tool, action, details, args, theme) {
5511
5518
  return lines;
5512
5519
  }
5513
5520
  const reports = Array.isArray(details.reports) ? details.reports : [];
5521
+ const work = Array.isArray(details.work) ? details.work : [];
5514
5522
  lines.push(
5515
5523
  "",
5524
+ ...work.length ? [
5525
+ "project work:",
5526
+ ...work.flatMap((item) => {
5527
+ if (!item || typeof item !== "object") return [];
5528
+ const branch = value(item.branch) || "unknown";
5529
+ const status = value(item.status) || "unknown";
5530
+ const runtime = value(item.runtime_state);
5531
+ const task = value(item.task);
5532
+ const issue = value(item.issue);
5533
+ return [
5534
+ ` ${branch} \xB7 ${status}${runtime ? ` \xB7 runtime=${runtime}` : ""}`,
5535
+ ...task ? [` task: ${task}`] : [],
5536
+ ...issue ? [` issue: ${issue}`] : []
5537
+ ];
5538
+ }),
5539
+ ""
5540
+ ] : [],
5516
5541
  ...reports.flatMap((lead) => {
5517
5542
  if (!lead || typeof lead !== "object") return [];
5518
5543
  const counts = lead.agent_counts && typeof lead.agent_counts === "object" ? lead.agent_counts : {};
@@ -5526,13 +5551,10 @@ function expandedResultLines(tool, action, details, args, theme) {
5526
5551
  ` agent counts: ${Object.entries(counts).filter(([, count]) => count !== void 0).map(([name, count]) => `${name}=${String(count)}`).join(" \xB7 ")}`
5527
5552
  ] : [],
5528
5553
  ...Array.isArray(lead.available_tools) ? [
5529
- ` can: ${lead.available_tools.map((tool2) => String(tool2).replace(/^staff_/, "")).join(", ")}`
5554
+ ` can: ${lead.available_tools.map((tool2) => String(tool2).replace(/^staff_/, "")).join(", ") || "nothing"}`
5530
5555
  ] : []
5531
5556
  ];
5532
- }),
5533
- ...Array.isArray(details.assignments) ? details.assignments.map(
5534
- (assignment) => `assignment: ${value(assignment?.id)} \xB7 ${value(assignment?.phase)}${value(assignment?.branch) ? ` \xB7 branch: ${value(assignment.branch)}` : ""}${value(assignment?.workspace_id) ? ` \xB7 workspace: ${value(assignment.workspace_id)}` : ""}${value(assignment?.pane_id) ? ` \xB7 pane: ${value(assignment.pane_id)}` : ""}${value(assignment?.session) ? ` \xB7 session: ${value(assignment.session)}` : ""}`
5535
- ) : []
5557
+ })
5536
5558
  );
5537
5559
  }
5538
5560
  if (action === "inspect")
@@ -6780,6 +6802,8 @@ async function drainCoordinationInbox(options) {
6780
6802
  }
6781
6803
  continue;
6782
6804
  }
6805
+ const deliverAs = options.deliveryMode ? options.deliveryMode(record) : "followUp";
6806
+ if (!deliverAs) continue;
6783
6807
  let token;
6784
6808
  const clearTransaction = async () => {
6785
6809
  if (token === void 0) return;
@@ -6856,7 +6880,7 @@ async function drainCoordinationInbox(options) {
6856
6880
  ...record.branch ? { branch: record.branch } : {}
6857
6881
  }
6858
6882
  },
6859
- { deliverAs: "followUp", triggerTurn: true },
6883
+ { deliverAs, triggerTurn: true },
6860
6884
  record
6861
6885
  );
6862
6886
  await options.transaction?.revalidate(token, "after-send");
@@ -7594,6 +7618,16 @@ function listProjectMessages(runtime, repoKey, branch) {
7594
7618
  }
7595
7619
  }).sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id));
7596
7620
  }
7621
+ function removeProjectMessage(runtime, repoKey, branch, id) {
7622
+ if (!UUID3.test(id)) throw new Error("Invalid project message ID");
7623
+ const directory = projectMessageDirectory(runtime, repoKey, branch);
7624
+ try {
7625
+ unlinkSync6(join8(directory, `${id}.json`));
7626
+ fsyncDirectory(directory);
7627
+ } catch (error2) {
7628
+ if (error2.code !== "ENOENT") throw error2;
7629
+ }
7630
+ }
7597
7631
  function removeProjectMessages(runtime, repoKey, branch) {
7598
7632
  rmSync(projectMessageDirectory(runtime, repoKey, branch), {
7599
7633
  recursive: true,
@@ -7682,6 +7716,12 @@ guidance: continue independently; do not use supervisor_message until a supervis
7682
7716
  function supervisorStateMessage(customType, content, latestContent) {
7683
7717
  return latestContent === content ? void 0 : { customType, content, display: false };
7684
7718
  }
7719
+ function projectTaskSummary(text) {
7720
+ const task = text.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/gu, " ").trim();
7721
+ if (!task) return void 0;
7722
+ const characters = Array.from(task);
7723
+ return characters.length <= 160 ? task : `${characters.slice(0, 159).join("")}\u2026`;
7724
+ }
7685
7725
  function projectWorkSnapshot(options) {
7686
7726
  return options.assignments.map((assignment) => {
7687
7727
  const worktrees = options.worktrees.filter(
@@ -7710,16 +7750,13 @@ function projectWorkSnapshot(options) {
7710
7750
  status = "paused";
7711
7751
  }
7712
7752
  }
7713
- const task = assignment.text.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/gu, " ").trim();
7714
- const characters = Array.from(task);
7753
+ const task = projectTaskSummary(assignment.text);
7715
7754
  return {
7716
7755
  branch: assignment.branch,
7717
7756
  session: assignment.id,
7718
7757
  status,
7719
7758
  ...runtimeState ? { runtimeState } : {},
7720
- ...task ? {
7721
- task: characters.length <= 160 ? task : `${characters.slice(0, 159).join("")}\u2026`
7722
- } : {},
7759
+ ...task ? { task } : {},
7723
7760
  ...issue ? { issue } : {}
7724
7761
  };
7725
7762
  });
@@ -17803,6 +17840,7 @@ function createLeadProjectRuntime(host) {
17803
17840
  throw new Error(
17804
17841
  `No existing work was found on ${operation.branch}; start it with staff_delegate and a task.`
17805
17842
  );
17843
+ const presentationTask = operation.action === "resume" ? projectTaskSummary(existing.text) : void 0;
17806
17844
  const id = operation.action === "resume" ? existing.id : operation.id;
17807
17845
  const branch = operation.branch;
17808
17846
  const topology = await runHerdr2(
@@ -17870,7 +17908,8 @@ function createLeadProjectRuntime(host) {
17870
17908
  branch,
17871
17909
  session: id,
17872
17910
  status: "active",
17873
- already_running: true
17911
+ already_running: true,
17912
+ ...presentationTask ? { presentation_task: presentationTask } : {}
17874
17913
  }
17875
17914
  };
17876
17915
  }
@@ -18320,7 +18359,8 @@ function createLeadProjectRuntime(host) {
18320
18359
  session: leadSessionId,
18321
18360
  workspace_id: workspaceId,
18322
18361
  branch: assignment.branch,
18323
- ...operation.action === "resume" ? { recovered: true } : {}
18362
+ ...operation.action === "resume" ? { recovered: true } : {},
18363
+ ...presentationTask ? { presentation_task: presentationTask } : {}
18324
18364
  }
18325
18365
  };
18326
18366
  } catch (error2) {
@@ -18404,7 +18444,9 @@ function createLeadProjectRuntime(host) {
18404
18444
  lead_counts: "leadCounts" in report ? report.leadCounts : void 0,
18405
18445
  leads: "leads" in report ? report.leads : []
18406
18446
  } : {},
18407
- available_actions: report.availableActions
18447
+ available_tools: report.availableActions.map(
18448
+ (action) => `staff_${action}`
18449
+ )
18408
18450
  }))
18409
18451
  });
18410
18452
  }
@@ -18645,9 +18687,11 @@ function createLeadCoordinationRuntime(host) {
18645
18687
  quarantineChiefMessage: quarantineChiefMessage2,
18646
18688
  listProjectAssignments: listProjectAssignments2,
18647
18689
  listProjectMessages: listProjectMessages2,
18690
+ removeProjectMessage: removeProjectMessage2,
18648
18691
  readProjectAssignment: readProjectAssignment2,
18649
18692
  importResultBindings: importResultBindings2
18650
18693
  } = host;
18694
+ const reportedProjectMessageCleanupErrors = /* @__PURE__ */ new Set();
18651
18695
  const readLeadSessionIdsFromHost = (inventory, mailboxes, workspaceId) => readLeadSessionIds(inventory, mailboxes, workspaceId, host);
18652
18696
  const prepareCoordinationInput = async (ctx, text, files, operation, heading, recordForInput) => {
18653
18697
  const limits = await host.messageLimits(ctx);
@@ -19224,33 +19268,46 @@ function createLeadCoordinationRuntime(host) {
19224
19268
  const drainProjectMessages = async (ctx) => {
19225
19269
  if (activeRole() !== "manager" || leadRuntime.roleSuspended || !ctx.isIdle())
19226
19270
  return 0;
19271
+ const runtime = supervisionRuntime2();
19227
19272
  const manager = await currentManager(ctx);
19228
19273
  if (!manager || !leadRuntime.managerLease || !sameManagerDescriptor2(manager, leadRuntime.managerLease.descriptor))
19229
19274
  return 0;
19230
- for (const assignment of listProjectAssignments2(
19231
- supervisionRuntime2(),
19232
- manager.repoKey
19233
- )) {
19275
+ const hasProjectMessageReceipt = (record) => ctx.sessionManager.getEntries().some(
19276
+ (entry) => entry?.customType === "pi-herdsman-project_message" && entry?.details?.id === record.id && entry?.details?.repoKey === record.repoKey && entry?.details?.branch === record.branch && entry?.details?.fromSessionId === record.fromSessionId
19277
+ );
19278
+ const consumeProjectMessage = (record) => {
19279
+ try {
19280
+ removeProjectMessage2(runtime, record.repoKey, record.branch, record.id);
19281
+ reportedProjectMessageCleanupErrors.delete(record.id);
19282
+ } catch (error2) {
19283
+ if (!reportedProjectMessageCleanupErrors.has(record.id)) {
19284
+ reportedProjectMessageCleanupErrors.add(record.id);
19285
+ appendDurableError2(pi, ctx, "pi_herdsman_state_error", error2);
19286
+ }
19287
+ }
19288
+ };
19289
+ for (const assignment of listProjectAssignments2(runtime, manager.repoKey)) {
19234
19290
  for (const record of listProjectMessages2(
19235
- supervisionRuntime2(),
19291
+ runtime,
19236
19292
  manager.repoKey,
19237
19293
  assignment.branch
19238
19294
  )) {
19239
19295
  if (record.fromSessionId !== assignment.id) continue;
19240
- const delivered = ctx.sessionManager.getEntries().some(
19241
- (entry) => entry?.customType === "pi-herdsman-project_message" && entry?.details?.id === record.id && entry?.details?.repoKey === record.repoKey && entry?.details?.branch === record.branch && entry?.details?.fromSessionId === record.fromSessionId
19242
- );
19243
- if (delivered) continue;
19296
+ if (hasProjectMessageReceipt(record)) {
19297
+ consumeProjectMessage(record);
19298
+ continue;
19299
+ }
19244
19300
  const current = await currentManager(ctx);
19245
19301
  const stillAssigned = readProjectAssignment2(
19246
- supervisionRuntime2(),
19302
+ runtime,
19247
19303
  manager.repoKey,
19248
19304
  assignment.branch
19249
19305
  );
19250
19306
  if (!ctx.isIdle() || !current || !sameManagerDescriptor2(current, manager) || !stillAssigned || stillAssigned.id !== assignment.id)
19251
19307
  return 0;
19252
19308
  importResultBindings2(pi, ctx, record.resultBindings, "project_message");
19253
- await pi.sendMessage(
19309
+ const backlog = record.createdAt < manager.createdAt;
19310
+ pi.sendMessage(
19254
19311
  {
19255
19312
  customType: "pi-herdsman-project_message",
19256
19313
  content: `Project ${record.branch} from lead ${record.fromSessionId}:
@@ -19264,8 +19321,9 @@ ${record.text}`,
19264
19321
  fromSessionId: record.fromSessionId
19265
19322
  }
19266
19323
  },
19267
- { deliverAs: "followUp", triggerTurn: true }
19324
+ backlog ? { triggerTurn: false } : { deliverAs: "followUp", triggerTurn: true }
19268
19325
  );
19326
+ if (hasProjectMessageReceipt(record)) consumeProjectMessage(record);
19269
19327
  return 1;
19270
19328
  }
19271
19329
  }
@@ -19306,16 +19364,17 @@ function projectAssignmentInstruction(assignment) {
19306
19364
 
19307
19365
  Manager supervises project scope and assignment boundaries; you own technical
19308
19366
  decisions and orchestration of execution. Herdsman automatically hands your
19309
- normal assignment response to the current or a replacement Manager. After
19310
- successfully delegating or continuing managed Agent work, the herd run owns
19311
- that handoff until it settles; summarize outcome, validation, and important
19312
- unresolved points in your response. If delegation is unsuccessful, respond
19367
+ normal assignment response to the Manager role. If no Manager is available,
19368
+ the handoff remains pending until a Manager can receive it. After successfully
19369
+ delegating or continuing managed Agent work, the herd run owns that handoff
19370
+ until it settles; summarize outcome, validation, and important unresolved
19371
+ points in your response. If delegation is unsuccessful, respond
19313
19372
  locally. Later conversational replies stay local.
19314
19373
 
19315
19374
  Use supervisor_message only for material coordination requiring Manager
19316
19375
  attention, a decision, or action\u2014not routine status or a duplicate handoff.
19317
- Messages are retained with the project while Manager is absent. Settlement is
19318
- nonterminal; do not infer project closure from runtime state.`;
19376
+ Undelivered project handoffs survive Manager absence. Once delivered, they
19377
+ are not automatically replayed to later Managers. Settlement is nonterminal; do not infer project closure from runtime state.`;
19319
19378
  }
19320
19379
  function createLeadInboxRuntime(host) {
19321
19380
  let chiefInboxTimer;
@@ -19548,6 +19607,12 @@ function createLeadInboxRuntime(host) {
19548
19607
  managerDiagnostic("inbox_catch", { category: "cleanup" });
19549
19608
  appendDurableError2(pi, ctx, "pi_herdsman_state_error", error2);
19550
19609
  },
19610
+ deliveryMode: (record) => {
19611
+ const role2 = activeRole();
19612
+ const supervisorDownlink = role2 === "lead" && (record.kind === "chief_message" || record.kind === "manager_message") || role2 === "manager" && record.kind === "chief_message";
19613
+ if (supervisorDownlink) return "steer";
19614
+ return ctx.isIdle() ? "followUp" : void 0;
19615
+ },
19551
19616
  isDelivered: (id) => {
19552
19617
  const record = transactions.get(id)?.record;
19553
19618
  return record ? messageDelivered(ctx, record) : false;
@@ -19582,10 +19647,6 @@ function createLeadInboxRuntime(host) {
19582
19647
  sendMessage: async (message, options, record) => {
19583
19648
  const projectAssignment = typeof message === "object" && message !== null && "customType" in message && message.customType === "pi-herdsman-project_assignment";
19584
19649
  try {
19585
- if (!ctx.isIdle())
19586
- throw new Error(
19587
- "Coordination delivery deferred while recipient is active"
19588
- );
19589
19650
  let payload = message;
19590
19651
  let resultBindings = record.resultBindings;
19591
19652
  if (record.kind === "project_assignment") {
@@ -19713,7 +19774,6 @@ function createLeadInboxRuntime(host) {
19713
19774
  });
19714
19775
  return count;
19715
19776
  };
19716
- if (!ctx.isIdle()) return complete(0);
19717
19777
  const held = chiefStartPreflightHeld(ctx);
19718
19778
  managerDiagnostic("inbox_preflight", { reason: held ? "held" : "pass" });
19719
19779
  if (held) return complete(0);
@@ -20970,14 +21030,16 @@ Lead while preserving its assignment. Project retirement is user-controlled
20970
21030
  through successful Herdr worktree removal.
20971
21031
 
20972
21032
  Each project-assignment delivery to a Lead is an automatic handoff to you:
20973
- Herdsman records the Lead's normal response as a project message for the current
20974
- or a replacement Manager. If the Lead successfully delegates or
21033
+ Herdsman automatically hands the Lead's normal assignment response to the
21034
+ Manager role. If no Manager is available, the handoff remains pending until a Manager
21035
+ can receive it. If the Lead successfully delegates or
20975
21036
  continues managed Agent work, the herd run owns that handoff until it settles,
20976
21037
  and the Lead's settled response should summarize the outcome, validation, and
20977
21038
  important unresolved points. An unsuccessful delegation leaves the local
20978
21039
  assignment-response path available. Later conversational replies, including
20979
21040
  routine thanks or acknowledgments, remain local and are not automatically
20980
- promoted. Review
21041
+ promoted. Undelivered project handoffs survive Manager absence. Once a handoff
21042
+ has been delivered, it is not automatically replayed to later Managers. Review
20981
21043
  received handoffs and request corrections with staff_message when needed.
20982
21044
 
20983
21045
  Project execution belongs to project Leads and their Agent trees. Your role
@@ -22181,7 +22243,7 @@ ${summary}`,
22181
22243
  ...staffTool,
22182
22244
  name: "staff_message",
22183
22245
  label: "staff message",
22184
- description: "Send a durable follow-up message to a direct report.",
22246
+ description: "Send a durable supervisor message to a direct report; active work is steered cooperatively.",
22185
22247
  parameters: staffMessageParameters,
22186
22248
  promptSnippet: void 0,
22187
22249
  promptGuidelines: [FILE_HANDOFF_GUIDANCE],
@@ -22370,6 +22432,7 @@ ${summary}`,
22370
22432
  writeChiefMessage,
22371
22433
  listProjectAssignments,
22372
22434
  listProjectMessages,
22435
+ removeProjectMessage,
22373
22436
  readProjectAssignment,
22374
22437
  importResultBindings
22375
22438
  },