@henryqw/pi-subagent 23.0.2 → 24.0.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.
@@ -1,5 +1,6 @@
1
1
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
2
  import { createConfigStore, extensionConfigPath } from "@henryqw/pi-config-store";
3
+ import type { ExecutionPolicySnapshot } from "../dist/schema.js";
3
4
 
4
5
  export interface SubagentTimeoutConfig {
5
6
  /** Minutes a child may stay idle before it is asked to stop. */
@@ -23,14 +24,7 @@ export interface LoadedSubagentConfig {
23
24
  error?: string;
24
25
  }
25
26
 
26
- export interface EffectiveExecutionPolicy {
27
- maxSubagents: number;
28
- maxTurns: number;
29
- maxTokens?: number;
30
- childIdleMs: number;
31
- childMaxMs: number;
32
- maxCorrections: number;
33
- }
27
+ export type EffectiveExecutionPolicy = ExecutionPolicySnapshot;
34
28
 
35
29
  type ParsedSubagentConfig = Omit<LoadedSubagentConfig, "source">;
36
30
 
@@ -17,9 +17,9 @@ import { RoleLaunchRuntime, type LaunchRuntimeOptions } from "../dist/launch-run
17
17
  import { IsolatedRunner, type OperationContext, type RunResponse } from "../dist/runner.js";
18
18
  import {
19
19
  IdOnlySchema,
20
- ResumeRequestSchema,
20
+ ResumeRequestParameters,
21
21
  StageRequestSchema,
22
- IntegrationActionSchema,
22
+ IntegrationActionParameters,
23
23
  parseIntegrationAction,
24
24
  parseStageRequest,
25
25
  parseExecuteRequest,
@@ -77,7 +77,6 @@ export const createIsolatedComponents: CreateIsolatedComponents = ({
77
77
  resolveRoot,
78
78
  preflightHost: async ({ root }, operation) => await host.preflightHost({ root }, operation),
79
79
  inspectMainBase: async (input, operation) => await git.inspectMainBase(input, operation),
80
- executionBudget,
81
80
  });
82
81
  return {
83
82
  resolveRoot,
@@ -615,7 +614,7 @@ export function registerIsolatedExtension(pi: ExtensionAPI, options: RegisterIso
615
614
  name: "subagent_resume",
616
615
  label: "Subagent resume",
617
616
  description: "Resume one unfinished isolated request without resetting its recorded policy or correction count.",
618
- parameters: ResumeRequestSchema,
617
+ parameters: ResumeRequestParameters,
619
618
  prepareArguments: parseResumeRequest,
620
619
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
621
620
  latestCtx = ctx;
@@ -642,7 +641,7 @@ export function registerIsolatedExtension(pi: ExtensionAPI, options: RegisterIso
642
641
  name: "subagent_integrate",
643
642
  label: "Subagent integrate",
644
643
  description: "Main advances staged dependents, refreshes after clean Main drift, validates, corrects, promotes, reconciles an interrupted promotion, cleans up promoted resources, or explicitly releases rejected/superseded owned resources. Never replays an uncertain mutation.",
645
- parameters: IntegrationActionSchema,
644
+ parameters: IntegrationActionParameters,
646
645
  prepareArguments: parseIntegrationAction,
647
646
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
648
647
  latestCtx = ctx;
@@ -5,31 +5,21 @@ const EVIDENCE_PREVIEW_CODE_POINTS = 256;
5
5
 
6
6
  export type WorkflowTransportStatus = "pending" | "running" | "succeeded" | "rejected" | "skipped";
7
7
 
8
- type TransportEntryBase = {
8
+ /** `assistantOutput` is read for running/succeeded entries and `failure` for rejected ones. */
9
+ export type WorkflowTransportEntry = {
9
10
  id: WorkflowEntry["id"];
10
11
  index: WorkflowEntry["index"];
11
12
  name: WorkflowEntry["delegation"]["name"];
12
13
  role: WorkflowEntry["delegation"]["role"];
13
14
  model?: string;
14
15
  thinkingLevel?: string;
15
- };
16
-
17
- export type WorkflowTransportEntry =
18
- | TransportEntryBase & { status: "pending" | "skipped"; assistantOutput?: never; failure?: never }
19
- | TransportEntryBase & { status: "running" | "succeeded"; assistantOutput: string; failure?: never }
20
- | TransportEntryBase & { status: "rejected"; assistantOutput?: never; failure: string };
21
-
22
- export type WorkflowTransportEntryDetails = {
23
- id: string;
24
- index: number;
25
- name: string;
26
- role: string;
27
16
  status: WorkflowTransportStatus;
28
- summary?: string;
29
- model?: string;
30
- thinkingLevel?: string;
17
+ assistantOutput?: string;
18
+ failure?: string;
31
19
  };
32
20
 
21
+ export type WorkflowTransportEntryDetails = Omit<WorkflowTransportEntry, "assistantOutput" | "failure"> & { summary?: string };
22
+
33
23
  export type WorkflowTransportDetails = {
34
24
  mode: WorkflowMode;
35
25
  entries: WorkflowTransportEntryDetails[];
@@ -61,15 +51,12 @@ function workflowTitle(mode: WorkflowMode): string {
61
51
  }
62
52
 
63
53
  function statusCounts(entries: readonly WorkflowTransportEntry[]): string[] {
64
- const count = (statuses: WorkflowTransportStatus[]) => entries.filter(({ status }) => statuses.includes(status)).length;
65
- return ([
66
- [["rejected"], "failed"],
67
- [["succeeded"], "completed"],
68
- [["skipped"], "skipped"],
69
- ] as const).flatMap(([statuses, word]) => {
70
- const total = count([...statuses]);
71
- return total ? [`${total} ${word}`] : [];
72
- });
54
+ const count = (status: WorkflowTransportStatus) => entries.filter((entry) => entry.status === status).length;
55
+ return [
56
+ ...(count("rejected") ? [`${count("rejected")} failed`] : []),
57
+ ...(count("succeeded") ? [`${count("succeeded")} completed`] : []),
58
+ ...(count("skipped") ? [`${count("skipped")} skipped`] : []),
59
+ ];
73
60
  }
74
61
 
75
62
  export function displaySummary(text: string): string {
@@ -80,17 +67,13 @@ export function displaySummary(text: string): string {
80
67
 
81
68
  export type WorkflowEntryStatusPresentation = { glyph: string; fallback: string };
82
69
 
83
- const ENTRY_STATUS_PRESENTATION = {
70
+ export const ENTRY_STATUS_PRESENTATION = {
84
71
  pending: { glyph: "○", fallback: "queued" },
85
72
  running: { glyph: "◌", fallback: "working" },
86
73
  succeeded: { glyph: "✓", fallback: "completed" },
87
74
  rejected: { glyph: "✗", fallback: "failed" },
88
75
  skipped: { glyph: "–", fallback: "skipped" },
89
- } as const satisfies Record<WorkflowTransportEntryDetails["status"], WorkflowEntryStatusPresentation>;
90
-
91
- export function presentWorkflowEntryStatus(status: WorkflowTransportEntryDetails["status"]): WorkflowEntryStatusPresentation {
92
- return ENTRY_STATUS_PRESENTATION[status];
93
- }
76
+ } as const satisfies Record<WorkflowTransportStatus, WorkflowEntryStatusPresentation>;
94
77
 
95
78
  function sourceFor(entry: WorkflowTransportEntry): string | undefined {
96
79
  if (entry.status === "rejected") return entry.failure;
@@ -122,7 +105,7 @@ export function formatWorkflowResult(mode: WorkflowMode, entries: readonly Workf
122
105
  const lines = [
123
106
  [`${workflowTitle(mode)} ${failed ? "failed" : "completed"}`, ...statusCounts(ordered)].join(" · "),
124
107
  ...positioned.map(({ entry, position }) => {
125
- const { glyph, fallback } = presentWorkflowEntryStatus(entry.status);
108
+ const { glyph, fallback } = ENTRY_STATUS_PRESENTATION[entry.status];
126
109
  const summary = displaySummary(sourceFor(entry) ?? "") || fallback;
127
110
  return `${glyph} [${position}/${ordered.length}] ${entry.name} · ${entry.role} — ${summary}`;
128
111
  }),
@@ -2,19 +2,15 @@ import { join } from "node:path";
2
2
  import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import { createMcpAdapter } from "pi-mcp-adapter";
4
4
  import { loadMcpConfig } from "pi-mcp-adapter/config";
5
- import {
6
- parseRoleMcpAllowlist,
7
- ROLE_MCP_POLICY_FLAG,
8
- roleMcpFlagValue,
9
- selectRoleMcpConfig,
10
- } from "@henryqw/pi-subagent";
5
+ import { roleMcpAllowlistFromArgv, ROLE_MCP_POLICY_FLAG, selectRoleMcpConfig } from "@henryqw/pi-subagent";
11
6
 
12
7
  export default function roleMcp(pi: ExtensionAPI): void {
13
8
  pi.registerFlag(ROLE_MCP_POLICY_FLAG, {
14
9
  description: "Internal Pi Subagent Role MCP policy",
15
10
  type: "string",
16
11
  });
17
- const allowlist = parseRoleMcpAllowlist(roleMcpFlagValue(process.argv, `--${ROLE_MCP_POLICY_FLAG}`));
12
+ // Pi binds extension flag values after factories load.
13
+ const allowlist = roleMcpAllowlistFromArgv(process.argv);
18
14
  const config = selectRoleMcpConfig(loadMcpConfig(join(getAgentDir(), "mcp.json"), process.cwd()), allowlist);
19
15
  createMcpAdapter({ config })(pi);
20
16
  }
@@ -3,7 +3,6 @@ import { isAbsolute } from "node:path";
3
3
  import { isToolCallEventType, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import {
5
5
  CHILD_EXCLUDED_TOOL_NAMES,
6
- EXECUTION_BUDGET_FLAG,
7
6
  EXECUTION_BUDGET_ENV,
8
7
  PI_SUBAGENT_PROCESS_LEASE,
9
8
  ROLE_TOOL_POLICY_FLAG,
@@ -48,9 +47,8 @@ function configuredTools(value: unknown): string[] {
48
47
  return [...new Set(parsed.map((name) => name.trim()))];
49
48
  }
50
49
 
51
- function executionBudget(value: string | boolean | undefined): EphemeralSubagentExecutionBudget | undefined {
50
+ function executionBudget(value: string | undefined): EphemeralSubagentExecutionBudget | undefined {
52
51
  if (value === undefined) return;
53
- if (typeof value !== "string") throw new Error(`${EXECUTION_BUDGET_ENV} must be a JSON execution budget.`);
54
52
  let parsed: unknown;
55
53
  try {
56
54
  parsed = JSON.parse(value);
@@ -106,11 +104,7 @@ export default function roleTools(pi: ExtensionAPI): void {
106
104
  description: "Internal Pi Subagent Role tool policy",
107
105
  type: "string",
108
106
  });
109
- pi.registerFlag(EXECUTION_BUDGET_FLAG, {
110
- description: "Internal Pi Subagent execution budget",
111
- type: "string",
112
- });
113
- const budget = executionBudget(pi.getFlag(EXECUTION_BUDGET_FLAG) ?? process.env[EXECUTION_BUDGET_ENV]);
107
+ const budget = executionBudget(process.env[EXECUTION_BUDGET_ENV]);
114
108
  let handoffSent = false;
115
109
  pi.on("session_start", () => {
116
110
  const selected = configuredTools(pi.getFlag(ROLE_TOOL_POLICY_FLAG));
@@ -45,8 +45,8 @@ const errorText = (error: unknown): string => clean(error instanceof Error ? err
45
45
  const completed = (status: string) => ["completed", "success", "failed", "failure", "aborted", "integrated"].includes(status);
46
46
  const priority = (status: string) => /attention|blocked|retained|failed/i.test(status) ? 0 : completed(status) ? 2 : 1;
47
47
 
48
- type Pick<T> = { label: string; value: T };
49
- async function choose<T>(ctx: ExtensionContext, title: string, items: readonly Pick<T>[]): Promise<T | undefined> {
48
+ type Choice<T> = { label: string; value: T };
49
+ async function choose<T>(ctx: ExtensionContext, title: string, items: readonly Choice<T>[]): Promise<T | undefined> {
50
50
  // Numeric suffixes are UI-only. Never parse display text into an identity.
51
51
  const labels = items.map((item, index) => `${clean(item.label, 145)} [${index + 1}]`);
52
52
  const selected = await ctx.ui.select(title, labels);
@@ -100,7 +100,7 @@ export function registerSubagentCommand(pi: ExtensionAPI, adapter: SubagentComma
100
100
  if (unavailable) ctx.ui.notify(`Isolated inventory unavailable (${unavailable}). Direct branch recovery remains available; check the canonical Git checkout/configuration.`, "warning");
101
101
  if (inventory?.invalidIds.length) for (const id of inventory.invalidIds) ctx.ui.notify(`Unreadable isolated state ID: ${JSON.stringify(id)}. Preserve this file; inspect the state store before making changes.`, "warning");
102
102
  type Selection = { kind: "direct"; task: DirectTask } | { kind: "isolated"; request: IsolatedRequest; root: string } | { kind: "refresh" } | { kind: "history" } | { kind: "close" };
103
- const selections: Pick<Selection>[] = [
103
+ const selections: Choice<Selection>[] = [
104
104
  ...direct.filter((task) => history || !completed(task.status)).map((task) => ({ label: `Direct · ${task.name} · ${task.status} · ${task.id}`, value: { kind: "direct" as const, task } })),
105
105
  ...(inventory?.requests ?? []).filter((request) => history || !completed(request.status) || /retained/i.test(request.status)).map((request) => ({ label: `Isolated · ${request.name} · ${request.status} · ${request.id}`, value: { kind: "isolated" as const, request, root: inventory!.root } })),
106
106
  { label: "Refresh", value: { kind: "refresh" as const } },
@@ -108,7 +108,7 @@ export function registerSubagentCommand(pi: ExtensionAPI, adapter: SubagentComma
108
108
  { label: "Close", value: { kind: "close" as const } },
109
109
  ];
110
110
  const taskChoices = selections.splice(0, selections.length - 3);
111
- const rank = ({ value }: Pick<Selection>) => value.kind === "direct" ? priority(value.task.status)
111
+ const rank = ({ value }: Choice<Selection>) => value.kind === "direct" ? priority(value.task.status)
112
112
  : value.kind === "isolated" ? priority(value.request.status) : 3;
113
113
  taskChoices.sort((a, b) => rank(a) - rank(b));
114
114
  selections.unshift(...taskChoices);
@@ -129,7 +129,7 @@ export function registerSubagentCommand(pi: ExtensionAPI, adapter: SubagentComma
129
129
  if (!valid()) return;
130
130
  type Action = "inspect" | "send" | "edit" | "back";
131
131
  const eligible = request.tasks.filter((task) => task.kind === "changeset" && adapter.canFollowup(root, request.id, task.id));
132
- const actions: Pick<Action>[] = [
132
+ const actions: Choice<Action>[] = [
133
133
  { label: "Inspect status and recovery", value: "inspect" },
134
134
  ...(eligible.length ? [{ label: "Send follow-up instructions", value: "send" as const }, { label: "Edit queued instructions (withdraw ALL pending; Cancel leaves withdrawn)", value: "edit" as const }] : []),
135
135
  { label: "Back / refresh", value: "back" },
@@ -3,13 +3,13 @@ import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-c
3
3
  import { type Component, Text, type TUI, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
4
4
  import { availableTaskModels, loadTaskModelsConfig, modelReference, registerModelTask, resolveAvailableModel, type ResolvedTaskRoute, taskThinkingLevels } from "@henryqw/pi-task-models";
5
5
  import { capEphemeralSubagentOutput as capOutput, createEphemeralSubagentExecutor, DELEGATE_TASK, formatDuration, loadRoles, prepareRoleLaunch, ROLE_TOOL_POLICY_FLAG, type Role } from "@henryqw/pi-subagent";
6
- import { DEFAULT_EXECUTION_POLICY, DEFAULT_TIMEOUT_CONFIG, readSubagentConfig, resolveExecutionPolicy, type EffectiveExecutionPolicy } from "./config.ts";
7
- import { createCheckoutAdmission, roleCanWrite, roleIsReadOnlyScout } from "./admission.ts";
6
+ import { readSubagentConfig, resolveExecutionPolicy, type EffectiveExecutionPolicy } from "./config.ts";
7
+ import { registerCheckoutAdmission, roleCanWrite, roleIsReadOnlyScout } from "./admission.ts";
8
8
  import { registerIsolatedExtension } from "./isolated.ts";
9
9
  import { registerSubagentCommand, type DirectTask } from "./subagent-command.ts";
10
10
  import { MODEL_CLASS_GUIDANCE } from "./model-class-policy.ts";
11
- import { formatWorkflowResult, presentWorkflowEntryStatus, type BackgroundWorkflowTransportDetails, type WorkflowTransportEntry } from "./result-transport.ts";
12
- import { DelegateTaskSchema, identifyWorkflowEntries, parseDelegateTask, runForegroundWorkflow, type Delegation, type ParsedWorkflow, type WorkflowEntry } from "./workflow.ts";
11
+ import { ENTRY_STATUS_PRESENTATION, formatWorkflowResult, type BackgroundWorkflowTransportDetails, type WorkflowTransportEntry } from "./result-transport.ts";
12
+ import { DelegateTaskParameters, identifyWorkflowEntries, parseDelegateTask, runForegroundWorkflow, type Delegation, type ParsedWorkflow, type WorkflowEntry } from "./workflow.ts";
13
13
  import { createDirectHerdr, type DirectHandle, type DirectTab } from "../dist/direct-herdr.js";
14
14
  import { materializeTransientLaunch } from "../dist/launch-runtime.js";
15
15
  const WIDGET_KEY = "subagent-status";
@@ -140,7 +140,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
140
140
  const glyph = details.recovery ? "■" : details.outcome === "completed" ? "✓" : "✗";
141
141
  const color = details.recovery ? "warning" : details.outcome === "completed" ? "success" : "error";
142
142
  const rows = details.entries.map(({ name, role, status, summary }) => {
143
- const { glyph, fallback } = presentWorkflowEntryStatus(status);
143
+ const { glyph, fallback } = ENTRY_STATUS_PRESENTATION[status];
144
144
  return `${glyph} ${name} · ${role} — ${details.recovery ? fallback : summary || fallback}`;
145
145
  });
146
146
  const raw = content.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, " ");
@@ -159,13 +159,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
159
159
  try {
160
160
  initialPolicy = resolveExecutionPolicy(loadedConfig);
161
161
  } catch {
162
- initialPolicy = {
163
- maxSubagents: DEFAULT_EXECUTION_POLICY.maxSubagents,
164
- maxTurns: DEFAULT_EXECUTION_POLICY.maxTurns,
165
- childIdleMs: DEFAULT_TIMEOUT_CONFIG.idleMinutes * 60_000,
166
- childMaxMs: DEFAULT_TIMEOUT_CONFIG.maxMinutes * 60_000,
167
- maxCorrections: DEFAULT_EXECUTION_POLICY.maxCorrections,
168
- };
162
+ initialPolicy = resolveExecutionPolicy({ source: "missing", config: {} });
169
163
  }
170
164
  const executor = createEphemeralSubagentExecutor({
171
165
  maxConcurrency: initialPolicy.maxSubagents,
@@ -179,13 +173,15 @@ export default function subagentExtension(pi: ExtensionAPI): void {
179
173
  const parsed = parseDelegateTask(input);
180
174
  if (parsed.mode === "isolated") return true;
181
175
  const roles = new Map(loadRoles().map((role) => [role.name, role]));
182
- return parsed.workflow.delegations.some((delegation) => roleCanWrite(roles.get(delegation.role) ?? { name: delegation.role } as Role));
176
+ return parsed.workflow.delegations.some((delegation) => {
177
+ const role = roles.get(delegation.role);
178
+ return !role || roleCanWrite(role);
179
+ });
183
180
  } catch {
184
181
  return true;
185
182
  }
186
183
  };
187
- const admission = createCheckoutAdmission();
188
- admission.register(pi, canWrite);
184
+ registerCheckoutAdmission(pi, canWrite);
189
185
  const isolatedSurface = registerIsolatedExtension(pi, {
190
186
  executor,
191
187
  policy: initialPolicy,
@@ -464,7 +460,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
464
460
  "Isolation uses typed tasks and dependencies. Keep tightly coupled changes with one owner; do not split by file count. Failures, ambiguity, limits, and conflicts retain work and never waive checks or identity guards.",
465
461
  `For delegate_task, ${MODEL_CLASS_GUIDANCE} A direct model replaces only the selected route's model; its thinking level stays unchanged.`,
466
462
  ],
467
- parameters: DelegateTaskSchema,
463
+ parameters: DelegateTaskParameters,
468
464
  prepareArguments(args) {
469
465
  try {
470
466
  const parsed = parseDelegateTask(args);
@@ -513,9 +509,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
513
509
  const prepared = entry.delegation.model === undefined ? route : prepareRoleLaunch(pi, context, {
514
510
  role, route: replaceRouteModel(context, entry.delegation.model, route),
515
511
  });
516
- const base = states.get(entry.id)!;
517
- states.set(entry.id, { id: base.id, index: base.index, name: base.name, role: base.role,
518
- model: modelReference(prepared.model), thinkingLevel: prepared.thinkingLevel, status: "running", assistantOutput: "" });
512
+ states.set(entry.id, { ...states.get(entry.id)!, model: modelReference(prepared.model), thinkingLevel: prepared.thinkingLevel, status: "running", assistantOutput: "" });
519
513
  startWidgetItem(entry.id, taskId, role.name, prepared.model.id, prepared.thinkingLevel, entry.delegation.name, ctx);
520
514
  const transient = await materializeTransientLaunch({ launch: prepared, prompt: prepared.systemPrompt,
521
515
  promptArgIndex: prepared.promptArgIndex }, activeSignal);
@@ -578,17 +572,12 @@ export default function subagentExtension(pi: ExtensionAPI): void {
578
572
  const tokens = await handle.usageTokens();
579
573
  if (tokens !== undefined && widgetItems.has(entry.id)) widgetItems.get(entry.id)!.tokens = tokens;
580
574
  } catch { /* Optional telemetry. */ }
581
- const base = states.get(entry.id)!;
582
- states.set(entry.id, { id: base.id, index: base.index, name: base.name, role: base.role,
583
- ...(base.model ? { model: base.model } : {}), ...(base.thinkingLevel ? { thinkingLevel: base.thinkingLevel } : {}),
584
- status: "succeeded", assistantOutput: answer });
575
+ states.set(entry.id, { ...states.get(entry.id)!, status: "succeeded", assistantOutput: answer });
585
576
  finishWidgetItem(entry.id, "success");
586
577
  return answer;
587
578
  } catch (error) {
588
- const base = states.get(entry.id)!;
589
- states.set(entry.id, { id: base.id, index: base.index, name: base.name, role: base.role,
590
- ...(base.model ? { model: base.model } : {}), ...(base.thinkingLevel ? { thinkingLevel: base.thinkingLevel } : {}),
591
- status: "rejected", failure: `${tabByEntry.has(entry.id) ? `recover from Herdr tab ${tabByEntry.get(entry.id)!.tabId}, agent ${tabByEntry.get(entry.id)!.name}, session ${tabByEntry.get(entry.id)!.sessionFile}: ` : ""}${capOutput(error instanceof Error ? error.message : String(error))}` });
579
+ const { assistantOutput: _partial, ...base } = states.get(entry.id)!;
580
+ states.set(entry.id, { ...base, status: "rejected", failure: `${tabByEntry.has(entry.id) ? `recover from Herdr tab ${tabByEntry.get(entry.id)!.tabId}, agent ${tabByEntry.get(entry.id)!.name}, session ${tabByEntry.get(entry.id)!.sessionFile}: ` : ""}${capOutput(error instanceof Error ? error.message : String(error))}` });
592
581
  finishWidgetItem(entry.id, "failure");
593
582
  throw error;
594
583
  } finally { release(); }
@@ -42,7 +42,20 @@ export const DirectWorkflowSchema = Type.Object({
42
42
  chain: Type.Optional(Type.Array(DelegationSchema, { minItems: 1, maxItems: MAX_WORKFLOW_ENTRIES })),
43
43
  }, { additionalProperties: false, description: "Direct mode: exactly one compact single, tasks, or chain workflow" });
44
44
 
45
- export const DelegateTaskSchema = Type.Union([DirectWorkflowSchema, ExecuteRequestSchema], { type: "object" });
45
+ export const DelegateTaskSchema = Type.Union([DirectWorkflowSchema, ExecuteRequestSchema]);
46
+ // Keep the strict mode union for parsing; expose a plain object root to tool providers.
47
+ export const DelegateTaskParameters = Type.Object({
48
+ ...DirectWorkflowSchema.properties,
49
+ mode: Type.Union([DirectWorkflowSchema.properties.mode, ExecuteRequestSchema.properties.mode]),
50
+ id: Type.Optional(ExecuteRequestSchema.properties.id),
51
+ goal: Type.Optional(ExecuteRequestSchema.properties.goal),
52
+ tasks: Type.Optional(Type.Union([
53
+ Type.Array(DelegationSchema, { minItems: 1, maxItems: MAX_WORKFLOW_ENTRIES }),
54
+ ExecuteRequestSchema.properties.tasks,
55
+ ])),
56
+ finalChecks: ExecuteRequestSchema.properties.finalChecks,
57
+ finalJudgment: ExecuteRequestSchema.properties.finalJudgment,
58
+ }, { additionalProperties: false });
46
59
  export const WorkflowSchema = DirectWorkflowSchema;
47
60
 
48
61
  export type Delegation = Static<typeof DelegationSchema> & { kind: "text" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-subagent",
3
- "version": "23.0.2",
3
+ "version": "24.0.1",
4
4
  "description": "Delegate bounded direct work or durable checked isolated task graphs to Pi roles.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,25 +0,0 @@
1
- export interface WorkingCheckoutState {
2
- head: string;
3
- indexHash: string;
4
- rawManifestHash: string;
5
- }
6
- export interface WorkingSnapshotIdentity extends WorkingCheckoutState {
7
- tree: string;
8
- }
9
- export interface WorkingCheckoutBaseline extends WorkingSnapshotIdentity {
10
- worktree: string;
11
- }
12
- export interface PreparedWorkingChangeEvidence {
13
- baseline: WorkingCheckoutBaseline;
14
- identity: WorkingSnapshotIdentity;
15
- changedPaths: string[];
16
- patchPath: string;
17
- rawEvidencePath: string;
18
- cleanup(): Promise<void>;
19
- }
20
- /** Capture mutation-detection identity without requiring a clean checkout or constructing Git objects. */
21
- export declare function captureWorkingCheckoutState(cwd: string, signal?: AbortSignal): Promise<WorkingCheckoutState>;
22
- export declare function captureWorkingCheckoutBaseline(cwd: string, signal?: AbortSignal): Promise<WorkingCheckoutBaseline>;
23
- export declare function prepareWorkingChangeEvidence(baseline: WorkingCheckoutBaseline, signal?: AbortSignal): Promise<PreparedWorkingChangeEvidence>;
24
- export declare function sameWorkingCheckoutState(left: WorkingCheckoutState, right: WorkingCheckoutState): boolean;
25
- export declare function sameWorkingSnapshot(left: WorkingSnapshotIdentity, right: WorkingSnapshotIdentity): boolean;