@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.
- package/CONTEXT.md +1 -1
- package/README.md +2 -2
- package/dist/index.d.ts +5 -6
- package/dist/index.js +37 -59
- package/dist/launch-runtime.d.ts +0 -2
- package/dist/launch-runtime.js +2 -9
- package/dist/mcp-role.d.ts +0 -1
- package/dist/mcp-role.js +0 -11
- package/dist/runner.js +9 -16
- package/dist/schema.d.ts +39 -8
- package/dist/schema.js +26 -2
- package/dist/worktree.d.ts +0 -2
- package/dist/worktree.js +0 -4
- package/extensions/admission.ts +23 -31
- package/extensions/config.ts +2 -8
- package/extensions/isolated.ts +4 -5
- package/extensions/result-transport.ts +15 -32
- package/extensions/role-mcp.ts +3 -7
- package/extensions/role-tools.ts +2 -8
- package/extensions/subagent-command.ts +5 -5
- package/extensions/subagent.ts +16 -27
- package/extensions/workflow.ts +14 -1
- package/package.json +1 -1
- package/dist/working-evidence.d.ts +0 -25
- package/dist/working-evidence.js +0 -261
package/extensions/config.ts
CHANGED
|
@@ -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
|
|
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
|
|
package/extensions/isolated.ts
CHANGED
|
@@ -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
|
-
|
|
20
|
+
ResumeRequestParameters,
|
|
21
21
|
StageRequestSchema,
|
|
22
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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 = (
|
|
65
|
-
return
|
|
66
|
-
[
|
|
67
|
-
[
|
|
68
|
-
|
|
69
|
-
]
|
|
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<
|
|
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 } =
|
|
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
|
}),
|
package/extensions/role-mcp.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/extensions/role-tools.ts
CHANGED
|
@@ -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 |
|
|
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
|
-
|
|
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
|
|
49
|
-
async function choose<T>(ctx: ExtensionContext, title: string, items: readonly
|
|
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:
|
|
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 }:
|
|
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:
|
|
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" },
|
package/extensions/subagent.ts
CHANGED
|
@@ -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 {
|
|
7
|
-
import {
|
|
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 {
|
|
12
|
-
import {
|
|
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 } =
|
|
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) =>
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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(); }
|
package/extensions/workflow.ts
CHANGED
|
@@ -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]
|
|
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,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;
|