@henryqw/pi-subagent 18.0.0 → 22.0.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.
Files changed (47) hide show
  1. package/CONTEXT.md +76 -24
  2. package/README.md +116 -142
  3. package/dist/composition.d.ts +11 -0
  4. package/dist/composition.js +107 -0
  5. package/dist/direct-herdr.d.ts +17 -0
  6. package/dist/direct-herdr.js +219 -0
  7. package/dist/ephemeral.d.ts +2 -2
  8. package/dist/ephemeral.js +7 -7
  9. package/dist/git-runtime.d.ts +110 -0
  10. package/dist/git-runtime.js +472 -0
  11. package/dist/herdr-runtime.d.ts +137 -0
  12. package/dist/herdr-runtime.js +1228 -0
  13. package/dist/index.d.ts +11 -5
  14. package/dist/index.js +20 -16
  15. package/dist/integration-git.d.ts +59 -0
  16. package/dist/integration-git.js +484 -0
  17. package/dist/launch-runtime.d.ts +54 -0
  18. package/dist/launch-runtime.js +195 -0
  19. package/dist/process.d.ts +9 -0
  20. package/dist/process.js +11 -0
  21. package/dist/runner.d.ts +356 -0
  22. package/dist/runner.js +2710 -0
  23. package/dist/schema.d.ts +2234 -0
  24. package/dist/schema.js +1236 -0
  25. package/dist/store.d.ts +50 -0
  26. package/dist/store.js +288 -0
  27. package/dist/working-evidence.d.ts +25 -0
  28. package/dist/working-evidence.js +261 -0
  29. package/docs/adr/001-composable-ephemeral-execution.md +3 -3
  30. package/docs/delegation-routing.html +89 -0
  31. package/docs/delegation-routing.svg +78 -0
  32. package/docs/orchestration-architecture.html +238 -0
  33. package/docs/orchestration-architecture.svg +189 -0
  34. package/docs/orchestration.md +122 -331
  35. package/example.png +0 -0
  36. package/examples/roles/implementer.md +2 -3
  37. package/extensions/admission.ts +66 -0
  38. package/extensions/config.ts +51 -37
  39. package/extensions/isolated.ts +638 -0
  40. package/extensions/result-transport.ts +13 -103
  41. package/extensions/role-tools.ts +11 -11
  42. package/extensions/subagent-command.ts +167 -0
  43. package/extensions/subagent.ts +264 -540
  44. package/extensions/task-name.ts +1 -2
  45. package/extensions/workflow.ts +60 -96
  46. package/package.json +19 -11
  47. package/skills/pi-subagent/SKILL.md +35 -0
package/dist/index.d.ts CHANGED
@@ -5,15 +5,16 @@ export { parseRoleMcpAllowlist, roleMcpFlagValue, selectRoleMcpConfig, type Role
5
5
  export { addUsage, capEphemeralSubagentOutput, createEphemeralSubagentExecutor, DEFAULT_MAX_TURNS, EphemeralSubagentError, EXECUTION_BUDGET_ENV, formatDuration, type EphemeralSubagentActivityEvent, type EphemeralSubagentErrorCode, type EphemeralSubagentExecutionBudget, type EphemeralSubagentExecutor, type EphemeralSubagentExecutorOptions, type EphemeralSubagentResult, type EphemeralSubagentRunInput, type EphemeralSubagentTimeout, } from "./ephemeral.ts";
6
6
  export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError, worktreeContextNote, type WorktreeDirtyInspection, type WorktreeInfo, type WorktreePayload, } from "./worktree.ts";
7
7
  export { prepareExactReviewEvidence, REVIEW_MAX_PATCH_BYTES, REVIEW_MAX_PATHS, type PreparedReviewEvidence, type PrepareExactReviewEvidenceInput, } from "./review-evidence.ts";
8
- export declare const PI_ORCHESTRATOR_PROCESS_LEASE = "PI_ORCHESTRATOR_PROCESS_LEASE";
8
+ export { captureWorkingCheckoutBaseline, captureWorkingCheckoutState, prepareWorkingChangeEvidence, sameWorkingCheckoutState, sameWorkingSnapshot, type PreparedWorkingChangeEvidence, type WorkingCheckoutBaseline, type WorkingCheckoutState, type WorkingSnapshotIdentity, } from "./working-evidence.ts";
9
+ export declare const PI_SUBAGENT_PROCESS_LEASE = "PI_SUBAGENT_PROCESS_LEASE";
9
10
  export declare const ROLE_MCP_POLICY_FLAG = "pi-subagent-role-mcps";
10
11
  export declare const ROLE_TOOL_POLICY_FLAG = "pi-subagent-role-tools";
11
- export declare const CHILD_EXCLUDED_TOOL_NAMES: readonly ["delegate_task", "ask_question", "orchestrate_execute", "orchestrate_status", "orchestrate_resume", "orchestrate_abort"];
12
+ export declare const CHILD_EXCLUDED_TOOL_NAMES: readonly ["delegate_task", "ask_question", "subagent_status", "subagent_resume", "subagent_stage", "subagent_abort"];
12
13
  export declare const CHILD_EXCLUDED_TOOLS: string;
13
14
  export declare const DELEGATE_TASK: {
14
15
  readonly id: "pi-subagent/delegateTask";
15
16
  readonly label: "Subagent delegation";
16
- readonly purpose: "Launch an isolated Pi subagent.";
17
+ readonly purpose: "Launch direct or checked isolated Pi delegation.";
17
18
  readonly defaultProfile: "fast";
18
19
  };
19
20
  export type RoleName = string;
@@ -22,7 +23,6 @@ export interface Role {
22
23
  description: string;
23
24
  modelClass?: ProfileName;
24
25
  tools: string[];
25
- isolation?: string;
26
26
  extensions: string[];
27
27
  skills: string[];
28
28
  mcps?: string[];
@@ -39,7 +39,6 @@ export interface ResolvedRoleLaunch extends PiLaunch {
39
39
  }
40
40
  export interface PreparedRoleLaunch extends ResolvedRoleLaunch {
41
41
  role: RoleName;
42
- isolation?: "worktree";
43
42
  tools: readonly string[];
44
43
  systemPrompt: string;
45
44
  promptArgIndex: number;
@@ -93,3 +92,10 @@ export declare function prepareRoleLaunch(pi: Pick<ExtensionAPI, "getCommands">,
93
92
  export declare function resolveConfiguredRoleLaunch(pi: Pick<ExtensionAPI, "getCommands">, ctx: ExtensionContext, input: ResolveConfiguredRoleLaunchInput): Promise<PreparedRoleLaunch>;
94
93
  /** Restore the system prompt pair after a caller has prepared its launch argv. */
95
94
  export declare function finalizeRoleLaunch(prepared: PreparedRoleLaunch): ResolvedRoleLaunch;
95
+ export * from "./composition.ts";
96
+ export * from "./git-runtime.ts";
97
+ export * from "./herdr-runtime.ts";
98
+ export { ISOLATED_MODEL_TASK, RoleLaunchRuntime, type LaunchRuntimeOptions } from "./launch-runtime.ts";
99
+ export * from "./runner.ts";
100
+ export * from "./schema.ts";
101
+ export * from "./store.ts";
package/dist/index.js CHANGED
@@ -11,20 +11,21 @@ export { parseRoleMcpAllowlist, roleMcpFlagValue, selectRoleMcpConfig } from "./
11
11
  export { addUsage, capEphemeralSubagentOutput, createEphemeralSubagentExecutor, DEFAULT_MAX_TURNS, EphemeralSubagentError, EXECUTION_BUDGET_ENV, formatDuration, } from "./ephemeral.js";
12
12
  export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError, worktreeContextNote, } from "./worktree.js";
13
13
  export { prepareExactReviewEvidence, REVIEW_MAX_PATCH_BYTES, REVIEW_MAX_PATHS, } from "./review-evidence.js";
14
+ export { captureWorkingCheckoutBaseline, captureWorkingCheckoutState, prepareWorkingChangeEvidence, sameWorkingCheckoutState, sameWorkingSnapshot, } from "./working-evidence.js";
14
15
  const CODEX_ALIAS = /^openai-codex-(?:[2-9]|[1-9]\d+)$/;
15
16
  const MULTI_CODEX_EXTENSION = fileURLToPath(import.meta.resolve("@henryqw/pi-multi-codex/extensions/multi-codex.ts"));
16
17
  const ROLE_MCP_EXTENSION = fileURLToPath(new URL("../extensions/role-mcp.ts", import.meta.url));
17
18
  const ROLE_TOOLS_EXTENSION = fileURLToPath(new URL("../extensions/role-tools.ts", import.meta.url));
18
- export const PI_ORCHESTRATOR_PROCESS_LEASE = "PI_ORCHESTRATOR_PROCESS_LEASE";
19
+ export const PI_SUBAGENT_PROCESS_LEASE = "PI_SUBAGENT_PROCESS_LEASE";
19
20
  export const ROLE_MCP_POLICY_FLAG = "pi-subagent-role-mcps";
20
21
  export const ROLE_TOOL_POLICY_FLAG = "pi-subagent-role-tools";
21
22
  export const CHILD_EXCLUDED_TOOL_NAMES = [
22
23
  "delegate_task",
23
24
  "ask_question",
24
- "orchestrate_execute",
25
- "orchestrate_status",
26
- "orchestrate_resume",
27
- "orchestrate_abort",
25
+ "subagent_status",
26
+ "subagent_resume",
27
+ "subagent_stage",
28
+ "subagent_abort",
28
29
  ];
29
30
  export const CHILD_EXCLUDED_TOOLS = CHILD_EXCLUDED_TOOL_NAMES.join(",");
30
31
  const SYSTEM_PROMPT_FLAG = "--append-system-prompt";
@@ -32,7 +33,7 @@ const CHILD_IDENTITY_POLICY = "You are a delegated Pi Subagent, not Main. Execut
32
33
  export const DELEGATE_TASK = {
33
34
  id: "pi-subagent/delegateTask",
34
35
  label: "Subagent delegation",
35
- purpose: "Launch an isolated Pi subagent.",
36
+ purpose: "Launch direct or checked isolated Pi delegation.",
36
37
  defaultProfile: "fast",
37
38
  };
38
39
  const cleanText = (value, field, source) => {
@@ -83,7 +84,7 @@ function roleToolPolicy(role, additionalTools = []) {
83
84
  function namesMcpAdapter(extension) {
84
85
  return extension.toLowerCase().split(/[\\/:@]+/).some((component) => component === "pi-mcp-adapter" || component.startsWith("pi-mcp-adapter."));
85
86
  }
86
- const FORBIDDEN_ROLE_PACKAGE_SOURCE_NAMES = ["pi-orchestrator", "pi-mcp-adapter"];
87
+ const FORBIDDEN_ROLE_PACKAGE_SOURCE_NAMES = ["pi-subagent", "pi-mcp-adapter"];
87
88
  function rejectForbiddenRolePackageSource(value, role) {
88
89
  const components = value.toLowerCase().split(/[\\/:@]+/);
89
90
  if (FORBIDDEN_ROLE_PACKAGE_SOURCE_NAMES.some((name) => components.some((component) => component === name || component.startsWith(`${name}.`)))) {
@@ -98,13 +99,10 @@ function roleModelClass(value, source) {
98
99
  }
99
100
  return value;
100
101
  }
101
- function roleIsolation(value, source) {
102
+ function rejectRetiredRoleIsolation(value, source) {
102
103
  if (value === undefined)
103
104
  return;
104
- if (cleanText(value, "isolation", source) !== "worktree") {
105
- throw new Error(`${source}: isolation must be "worktree".`);
106
- }
107
- return "worktree";
105
+ throw new Error(`${source}: Role isolation is retired; select mode \"isolated\" on delegate_task instead.`);
108
106
  }
109
107
  // Built-in Roles resolved from the package-shipped Markdown relative to this module.
110
108
  const BUILTIN_ROLE_NAMES = ["implementer", "reviewer", "scout"];
@@ -118,14 +116,13 @@ function parseRoleFile(file, raw) {
118
116
  throw new Error(`${file}: ${error instanceof Error ? error.message : String(error)}`);
119
117
  }
120
118
  const frontmatter = parsed.frontmatter;
121
- const isolation = roleIsolation(frontmatter.isolation, file);
119
+ rejectRetiredRoleIsolation(frontmatter.isolation, file);
122
120
  const modelClass = roleModelClass(frontmatter.modelClass, file);
123
121
  return {
124
122
  name: parseRoleName(frontmatter.name, file),
125
123
  description: cleanDisplayText(frontmatter.description, "description", file),
126
124
  ...(modelClass === undefined ? {} : { modelClass }),
127
125
  tools: stringList(frontmatter.tools, "tools", file),
128
- isolation,
129
126
  extensions: extensionList(frontmatter.extensions, file),
130
127
  skills: stringList(frontmatter.skills, "skills", file),
131
128
  mcps: mcpList(frontmatter.mcps, file),
@@ -312,10 +309,10 @@ function stripRoleSystemPrompt(rawArgs) {
312
309
  }
313
310
  function prepareResolvedRoleLaunch(roleDefinition, launch, additionalTools = []) {
314
311
  const role = parseRoleName(roleDefinition.name);
315
- const isolation = roleIsolation(roleDefinition.isolation, `Role ${role}`);
312
+ rejectRetiredRoleIsolation(roleDefinition.isolation, `Role ${role}`);
316
313
  const tools = Object.freeze(roleToolPolicy(roleDefinition, additionalTools));
317
314
  const { args, systemPrompt, promptArgIndex } = stripRoleSystemPrompt(launch.args);
318
- return { ...launch, args, role, isolation, tools, systemPrompt, promptArgIndex };
315
+ return { ...launch, args, role, tools, systemPrompt, promptArgIndex };
319
316
  }
320
317
  function assertNoMissingRoleSkills(role, launch) {
321
318
  if (!launch.missingSkills.length)
@@ -389,3 +386,10 @@ export function finalizeRoleLaunch(prepared) {
389
386
  missingSkills: prepared.missingSkills,
390
387
  };
391
388
  }
389
+ export * from "./composition.js";
390
+ export * from "./git-runtime.js";
391
+ export * from "./herdr-runtime.js";
392
+ export { ISOLATED_MODEL_TASK, RoleLaunchRuntime } from "./launch-runtime.js";
393
+ export * from "./runner.js";
394
+ export * from "./schema.js";
395
+ export * from "./store.js";
@@ -0,0 +1,59 @@
1
+ import { type WorktreeInfo } from "./worktree.ts";
2
+ import { type CheckBatchEvidence, type CheckCommand, type ReviewEvidence, type WorkspaceIdentity } from "./schema.ts";
3
+ export type StageReceipt = {
4
+ previous: WorkspaceIdentity;
5
+ worker: WorkspaceIdentity;
6
+ tip: WorkspaceIdentity;
7
+ };
8
+ export type GitOutcome<T> = {
9
+ outcome: "ready";
10
+ value: T;
11
+ } | {
12
+ outcome: "blocked" | "conflict" | "drift" | "unknown";
13
+ failure: string;
14
+ possibleResources?: string[];
15
+ };
16
+ export declare class IntegrationGit {
17
+ /** Refresh is allowed only for the exact clean checked-out Main and a strict same-branch descendant. */
18
+ inspectMainAdvance(root: string, from: WorkspaceIdentity, to: WorkspaceIdentity, signal: AbortSignal): Promise<void>;
19
+ /** Persist the prepared path/branch/base before Git is allowed to create anything. */
20
+ allocate(root: string, childId: string, base: WorkspaceIdentity, onPrepared: (info: WorktreeInfo) => Promise<void>, signal: AbortSignal): Promise<GitOutcome<WorktreeInfo>>;
21
+ /** Prove each recorded merge before any new merge or final gate. */
22
+ inspectCombined(root: string, integration: WorktreeInfo, base: WorkspaceIdentity, stages: readonly StageReceipt[], signal: AbortSignal): Promise<WorkspaceIdentity>;
23
+ /** Main's correction is one exact committed child of the checked staged tip. */
24
+ inspectCorrection(root: string, integration: WorktreeInfo, base: WorkspaceIdentity, stages: readonly StageReceipt[], from: WorkspaceIdentity, to: WorkspaceIdentity, signal: AbortSignal): Promise<void>;
25
+ /** Main orders calls. On conflict leave MERGE_HEAD and the index untouched for manual resolution. */
26
+ stage(root: string, integration: WorktreeInfo, base: WorkspaceIdentity, stages: readonly StageReceipt[], worker: WorktreeInfo, candidate: WorkspaceIdentity, signal: AbortSignal): Promise<GitOutcome<StageReceipt>>;
27
+ /** After manual conflict resolution: only the exact two-parent merge is a valid receipt. */
28
+ confirmStage(root: string, integration: WorktreeInfo, base: WorkspaceIdentity, stages: readonly StageReceipt[], worker: WorktreeInfo, candidate: WorkspaceIdentity, signal: AbortSignal): Promise<GitOutcome<StageReceipt>>;
29
+ /** Reconcile an interrupted merge without retrying it or discarding conflict state. */
30
+ reconcileStage(root: string, integration: WorktreeInfo, base: WorkspaceIdentity, stages: readonly StageReceipt[], worker: WorktreeInfo, candidate: WorkspaceIdentity, signal: AbortSignal): Promise<GitOutcome<StageReceipt | "not_started">>;
31
+ inspectWorker(root: string, worker: WorktreeInfo, candidate: WorkspaceIdentity, signal: AbortSignal): Promise<void>;
32
+ /** Read-only reconciliation of a lost promotion response; never repeat the merge. */
33
+ reconcilePromotion(root: string, integration: WorktreeInfo, base: WorkspaceIdentity, stages: readonly StageReceipt[], signal: AbortSignal, correction?: {
34
+ from: WorkspaceIdentity;
35
+ to: WorkspaceIdentity;
36
+ }): Promise<GitOutcome<WorkspaceIdentity>>;
37
+ /** Checks/review must be persisted for this exact combined tip; never move Main via update-ref. */
38
+ promote(input: {
39
+ root: string;
40
+ integration: WorktreeInfo;
41
+ base: WorkspaceIdentity;
42
+ stages: readonly StageReceipt[];
43
+ checks: CheckBatchEvidence;
44
+ commands: readonly CheckCommand[];
45
+ review?: ReviewEvidence;
46
+ criterion?: string;
47
+ correction?: {
48
+ from: WorkspaceIdentity;
49
+ to: WorkspaceIdentity;
50
+ };
51
+ }, signal: AbortSignal): Promise<GitOutcome<WorkspaceIdentity>>;
52
+ /** Explicit release of an unselected clean checkout. The branch deletion is an exact ref compare-and-swap. */
53
+ release(root: string, info: WorktreeInfo, expected: WorkspaceIdentity, kind: "worktree" | "branch", signal: AbortSignal): Promise<GitOutcome<"removed" | "absent">>;
54
+ /** Cleanup is deliberately separate per resource; callers persist each proven substep. */
55
+ cleanup(root: string, integration: WorktreeInfo, info: WorktreeInfo, base: WorkspaceIdentity, stages: readonly StageReceipt[], promoted: WorkspaceIdentity, kind: "worktree" | "branch", signal: AbortSignal, correction?: {
56
+ from: WorkspaceIdentity;
57
+ to: WorkspaceIdentity;
58
+ }): Promise<GitOutcome<"removed" | "absent">>;
59
+ }