@henryqw/pi-subagent 27.5.3 → 27.6.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/README.md +11 -3
- package/dist/direct-herdr.d.ts +10 -5
- package/dist/direct-herdr.js +9 -4
- package/dist/integration-git.d.ts +3 -1
- package/dist/integration-git.js +2 -1
- package/dist/launch-runtime.d.ts +3 -1
- package/dist/launch-runtime.js +7 -4
- package/dist/runner.d.ts +17 -0
- package/dist/runner.js +138 -88
- package/dist/schema.d.ts +143 -2
- package/dist/schema.js +38 -6
- package/docs/orchestration.md +37 -6
- package/extensions/isolated.ts +56 -3
- package/extensions/result-transport.ts +55 -2
- package/extensions/subagent-command.ts +8 -4
- package/extensions/subagent.ts +89 -23
- package/extensions/workflow.ts +144 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Commands and UI are for you. Tools and the packaged skill are for Main, except `
|
|
|
33
33
|
| `/subagent roles` | command | List Role sources, resources, resolved model and thinking level, route errors, and file errors. Read-only; requires no Herdr pane. |
|
|
34
34
|
| `/subagent prune` | command | Review remaining worker and integration branches and worktrees. Remove merged resources after confirmation. Requires a running Herdr server; without interactive UI, only reports. |
|
|
35
35
|
| `/subagent recover` | command | Classify interrupted isolated work in the canonical repository. Send Main request IDs, blockers, and next actions. Requires no UI or Herdr pane. |
|
|
36
|
-
| `delegate_task` | tool | Start authorized shared-checkout work or a durable isolated checked graph. |
|
|
36
|
+
| `delegate_task` | tool | Start authorized shared-checkout work or a durable isolated checked graph. With `dryRun: true`, check the request and show the resolved plan without a launch. |
|
|
37
37
|
| `subagent_status` | tool | Read saved isolated state, exact identities, failed review verdicts, and allowed continuation. Does not replay work. Supports structured output for `codemode`. |
|
|
38
38
|
| `subagent_resume` | tool | Perform the reported `retry`, `verify`, or `finalize` continuation, not staging or integration. Same-worker correction retries accept optional `instruction` text. |
|
|
39
39
|
| `subagent_stage` | tool | Stage, resolve, reject, or revise an exact candidate. Rejecting a staged candidate freezes its generation. |
|
|
@@ -41,7 +41,7 @@ Commands and UI are for you. Tools and the packaged skill are for Main, except `
|
|
|
41
41
|
| `subagent_abort` | tool | Terminate an isolated request without discarding retained work. Retained candidates and integration work require release or proof of absence. |
|
|
42
42
|
| `git_read` | tool | Let Role-declared children inspect bounded local Git evidence without Bash, tests, network access, or writes. Not available to Main. |
|
|
43
43
|
| `pi-subagent` | skill | Guide Main through authorization, delegation, checks, integration, and recovery. |
|
|
44
|
-
| Direct status widget (`D`) | ui | Show direct references, names, Role badges, status, routes, elapsed time, measured task tokens, and tool activity in the TUI. |
|
|
44
|
+
| Direct status widget (`D`) | ui | Show direct references, names, Role badges, status, routes, elapsed time, measured task tokens, cost, and tool activity in the TUI. |
|
|
45
45
|
| Isolated status widget (`I`) | ui | Show recorded task state, Role/model-class badges, and retained workspaces in the TUI. Readiness does not mean promotion. |
|
|
46
46
|
|
|
47
47
|
`delegate_task`, `subagent_resume`, `subagent_stage`, `subagent_integrate`, and `subagent_abort` are model-only. Main must make these decisions visibly; `codemode` scripts can call only `subagent_status`.
|
|
@@ -82,7 +82,11 @@ Tasks in write requests must return one JSON completion object:
|
|
|
82
82
|
|
|
83
83
|
`outcome` accepts `succeeded`, `failed`, or `blocked`; `answer` must be non-empty. Plain text, malformed completion, blocked work, or failure stops later dispatch. Success is the worker's report, not independent validation. Checkout admission depends on proved termination, not the reported outcome.
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
A write task can declare `"paths": ["src/**"]` with repo-relative globs. For each task in a write request, the result lists new commits and changed paths, and it flags changed paths outside `paths`. This is a report only, not enforcement, and it cannot exclude changes from other processes. For a read request, the result warns `checkout changed during task; result may be stale` when the checkout changed while the task ran. Outside a Git repository, the extension makes no report. See [Checkout change reports](./docs/orchestration.md#checkout-change-reports).
|
|
86
|
+
|
|
87
|
+
To check a request before a launch, add `"dryRun": true`. The dry run does the launch checks and shows each task's Role, model, thinking level, access, and dependencies. It starts no worker and opens no tab. It also reserves no checkout and sends no follow-up. A failed check gives the same error as a launch. For the full list of checks, see [Dry run](./docs/orchestration.md#dry-run).
|
|
88
|
+
|
|
89
|
+
The launch returns a task ID and first Herdr tab, not the answer. One follow-up delivers results when the workflow finishes. The results show the tokens and cost of each worker and the total cost of the request. Busy Main receives it after its current turn; idle Main starts a turn. Failed partial output stays marked as partial, never success. Worker output carries no user authority.
|
|
86
90
|
|
|
87
91
|
To continue a finished worker, add `"continueFrom": "D7.1"` with the same `role`. This opens a new tab with the saved conversation and a new reference. Continue the newest reference from the original checkout and workspace. For eligibility and stop behavior, see [Continue or stop one direct worker](./docs/orchestration.md#continue-or-stop-one-direct-worker).
|
|
88
92
|
|
|
@@ -125,6 +129,10 @@ Keep the durable ID; Main can work or end its turn without polling. Inspect read
|
|
|
125
129
|
|
|
126
130
|
Readiness requires preliminary checks and any task `judgment`; it does not permit promotion. Checks and judgments bind to exact Git identities. Staging, combined validation, and promotion require unchanged, clean Main at the pinned identity. Changed identities, conflicts, or uncertain outcomes block promotion and preserve evidence. A definitive combined check or review failure permits one committed integration correction, recorded with `subagent_integrate correct`, and another validation.
|
|
127
131
|
|
|
132
|
+
Combined validation can reuse a passed preliminary check of a staged candidate. The candidate must have the same tree as the combined tip, and the check must have the identical command and argv. Then the final check does not run again. This usually occurs when you stage one candidate onto unchanged Main. Status shows each reused result and its candidate. Final judgment still runs.
|
|
133
|
+
|
|
134
|
+
Reuse assumes that checks use only tracked content. Ignored files, for example `node_modules`, can be different between worktrees.
|
|
135
|
+
|
|
128
136
|
The extension sends follow-ups when a candidate becomes ready, a worker needs attention, and the wave finishes. A missed follow-up does not authorize replay. Read [Isolated contract](./docs/orchestration.md#isolated-contract) and [Exact integration actions](./docs/orchestration.md#exact-integration-actions) for graph, judgment, revision, refresh, and release details.
|
|
129
137
|
|
|
130
138
|
### Inspect work
|
package/dist/direct-herdr.d.ts
CHANGED
|
@@ -8,10 +8,10 @@ export interface DirectHandle {
|
|
|
8
8
|
sessionFile: string;
|
|
9
9
|
prompt: string;
|
|
10
10
|
answer(maxBytes: number): Promise<string>;
|
|
11
|
-
|
|
11
|
+
usage(): Promise<DirectUsage>;
|
|
12
12
|
/** Bounded assistant text from this turn so far; failure evidence only, never completion. */
|
|
13
13
|
partialText(maxChars: number): Promise<string | undefined>;
|
|
14
|
-
/** Display-only activity from the last
|
|
14
|
+
/** Display-only activity from the last usage() sample. */
|
|
15
15
|
readonly activity?: DirectToolActivity;
|
|
16
16
|
cancel(): Promise<void>;
|
|
17
17
|
}
|
|
@@ -19,6 +19,12 @@ export declare function exactDirectTerminalTurn(jsonl: string, prompt: string):
|
|
|
19
19
|
/** Refuse continuation unless the saved session's exact recorded turn is terminal and no later user turn exists. */
|
|
20
20
|
export declare function assertSettledDirectSession(sessionFile: string, turn: string): Promise<void>;
|
|
21
21
|
export declare function exactDirectAnswer(jsonl: string, prompt: string, maxBytes?: number, requireSuccess?: boolean): string;
|
|
22
|
+
/** Measured from one session read; each field is absent when no turn record carried it. */
|
|
23
|
+
export interface DirectUsage {
|
|
24
|
+
tokens?: number;
|
|
25
|
+
/** Sum of assistant `usage.cost.total`. */
|
|
26
|
+
cost?: number;
|
|
27
|
+
}
|
|
22
28
|
export interface DirectToolActivity {
|
|
23
29
|
pending: string[];
|
|
24
30
|
latest?: {
|
|
@@ -26,14 +32,13 @@ export interface DirectToolActivity {
|
|
|
26
32
|
durationMs?: number;
|
|
27
33
|
};
|
|
28
34
|
}
|
|
29
|
-
export declare function directSessionUsage(jsonl: string, prompt: string): {
|
|
30
|
-
tokens?: number;
|
|
35
|
+
export declare function directSessionUsage(jsonl: string, prompt: string): DirectUsage & {
|
|
31
36
|
activity?: DirectToolActivity;
|
|
32
37
|
};
|
|
33
38
|
/** The last `maxChars` of assistant text in the exact prompt's turn; ignores a partial trailing entry. */
|
|
34
39
|
export declare function directPartialText(jsonl: string, prompt: string, maxChars: number): string | undefined;
|
|
35
40
|
/** One reader per handle; only complete, valid lines advance the byte offset. */
|
|
36
|
-
export declare function createDirectSessionReader(sessionFile: string, prompt: string): Pick<DirectHandle, "
|
|
41
|
+
export declare function createDirectSessionReader(sessionFile: string, prompt: string): Pick<DirectHandle, "usage" | "partialText" | "activity">;
|
|
37
42
|
/** `turn` is the prompt's random turn identity; it proves which session turn a record owns.
|
|
38
43
|
* `workspaceId` and `cwd` bind the record to its launching Herdr workspace and checkout. */
|
|
39
44
|
export type DirectTab = Pick<DirectHandle, "name" | "tabId" | "paneId" | "sessionFile"> & {
|
package/dist/direct-herdr.js
CHANGED
|
@@ -126,6 +126,7 @@ class DirectSessionState {
|
|
|
126
126
|
active = false;
|
|
127
127
|
total = 0;
|
|
128
128
|
observed = false;
|
|
129
|
+
cost;
|
|
129
130
|
pending = new Map();
|
|
130
131
|
latest;
|
|
131
132
|
texts = [];
|
|
@@ -150,6 +151,7 @@ class DirectSessionState {
|
|
|
150
151
|
if (this.active) {
|
|
151
152
|
this.total = 0;
|
|
152
153
|
this.observed = false;
|
|
154
|
+
this.cost = undefined;
|
|
153
155
|
this.pending.clear();
|
|
154
156
|
this.latest = undefined;
|
|
155
157
|
this.texts = [];
|
|
@@ -163,6 +165,9 @@ class DirectSessionState {
|
|
|
163
165
|
this.total += values.reduce((sum, value) => sum + value, 0);
|
|
164
166
|
this.observed = true;
|
|
165
167
|
}
|
|
168
|
+
const cost = usage.cost?.total;
|
|
169
|
+
if (typeof cost === "number" && Number.isFinite(cost) && cost >= 0)
|
|
170
|
+
this.cost = (this.cost ?? 0) + cost;
|
|
166
171
|
}
|
|
167
172
|
if (Array.isArray(message.content))
|
|
168
173
|
for (const part of message.content) {
|
|
@@ -187,7 +192,7 @@ class DirectSessionState {
|
|
|
187
192
|
return true;
|
|
188
193
|
}
|
|
189
194
|
usage() {
|
|
190
|
-
return { tokens: this.observed ? this.total : undefined,
|
|
195
|
+
return { tokens: this.observed ? this.total : undefined, cost: this.cost,
|
|
191
196
|
activity: this.pending.size || this.latest ? { pending: [...this.pending.values()], latest: this.latest } : undefined };
|
|
192
197
|
}
|
|
193
198
|
partialText(maxChars) {
|
|
@@ -258,9 +263,9 @@ export function createDirectSessionReader(sessionFile, prompt) {
|
|
|
258
263
|
const sample = () => reading ??= read().finally(() => { reading = undefined; });
|
|
259
264
|
return {
|
|
260
265
|
get activity() { return lastUsage.activity; },
|
|
261
|
-
async
|
|
266
|
+
async usage() {
|
|
262
267
|
lastUsage = await sample() ? state.usage() : {};
|
|
263
|
-
return lastUsage.tokens;
|
|
268
|
+
return { tokens: lastUsage.tokens, cost: lastUsage.cost };
|
|
264
269
|
},
|
|
265
270
|
async partialText(maxChars) {
|
|
266
271
|
return await sample() ? state.partialText(maxChars) : undefined;
|
|
@@ -426,7 +431,7 @@ export function createDirectHerdr(pi, cwd, idleMs) {
|
|
|
426
431
|
return {
|
|
427
432
|
name, tabId, paneId, sessionFile, prompt,
|
|
428
433
|
get activity() { return session.activity; },
|
|
429
|
-
|
|
434
|
+
usage: session.usage,
|
|
430
435
|
partialText: session.partialText,
|
|
431
436
|
async answer(maxBytes) {
|
|
432
437
|
const readAnswer = async () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type WorktreeInfo } from "./worktree.ts";
|
|
2
|
-
import { type CheckBatchEvidence, type CheckCommand, type ReviewEvidence, type WorkspaceIdentity } from "./schema.ts";
|
|
2
|
+
import { type CheckBatchEvidence, type CheckCommand, type IntegrationCandidate, type ReviewEvidence, type WorkspaceIdentity } from "./schema.ts";
|
|
3
3
|
export type StageReceipt = {
|
|
4
4
|
previous: WorkspaceIdentity;
|
|
5
5
|
worker: WorkspaceIdentity;
|
|
@@ -42,6 +42,8 @@ export declare class IntegrationGit {
|
|
|
42
42
|
stages: readonly StageReceipt[];
|
|
43
43
|
checks: CheckBatchEvidence;
|
|
44
44
|
commands: readonly CheckCommand[];
|
|
45
|
+
/** Staged candidates that may supply reused final results; omitted accepts no reuse. */
|
|
46
|
+
sources?: readonly IntegrationCandidate[];
|
|
45
47
|
review?: ReviewEvidence;
|
|
46
48
|
criterion?: string;
|
|
47
49
|
correction?: {
|
package/dist/integration-git.js
CHANGED
|
@@ -317,7 +317,8 @@ export class IntegrationGit {
|
|
|
317
317
|
return { outcome: "blocked", failure: "No worker stages to promote." };
|
|
318
318
|
const tip = input.correction ? (await this.inspectCorrection(root, integration, base, stages, input.correction.from, input.correction.to, signal), input.correction.to)
|
|
319
319
|
: await this.inspectCombined(root, integration, base, stages, signal);
|
|
320
|
-
|
|
320
|
+
const sources = (input.sources ?? []).filter((source) => stages.some((stage) => sameIdentity(stage.worker, source.tip)));
|
|
321
|
+
if (input.checks.phase !== "final" || !checkBatchPasses(input.checks, input.commands, tip, sources)
|
|
321
322
|
|| (input.criterion !== undefined && !reviewEvidencePasses(input.review, "final", input.criterion, base, tip))
|
|
322
323
|
|| (input.criterion === undefined && input.review !== undefined)) {
|
|
323
324
|
return { outcome: "blocked", failure: "Exact combined-tip final checks/review evidence is missing or failed." };
|
package/dist/launch-runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { CoordinatorRuntime, OperationContext, TransientLaunchHandle, VerifiedLaunch } from "./runner.ts";
|
|
2
|
+
import type { CoordinatorRuntime, OperationContext, ResolvedRoute, TransientLaunchHandle, VerifiedLaunch } from "./runner.ts";
|
|
3
3
|
import { type ExecuteRequest, type ModelClass, type WorkspaceIdentity } from "./schema.ts";
|
|
4
4
|
export declare const ISOLATED_MODEL_TASK: {
|
|
5
5
|
readonly id: "pi-subagent/isolatedRoleLaunch";
|
|
@@ -43,9 +43,11 @@ export declare class RoleLaunchRuntime implements CoordinatorRuntime {
|
|
|
43
43
|
preflight(input: {
|
|
44
44
|
request: ExecuteRequest;
|
|
45
45
|
cwd: string;
|
|
46
|
+
dryRun?: boolean;
|
|
46
47
|
}, context: OperationContext): Promise<{
|
|
47
48
|
root: string;
|
|
48
49
|
main: WorkspaceIdentity;
|
|
50
|
+
routes: ResolvedRoute[];
|
|
49
51
|
}>;
|
|
50
52
|
acquireLaunch(role: string, modelClass: ModelClass, context: OperationContext): Promise<TransientLaunchHandle<VerifiedLaunch>>;
|
|
51
53
|
}
|
package/dist/launch-runtime.js
CHANGED
|
@@ -150,7 +150,7 @@ export class RoleLaunchRuntime {
|
|
|
150
150
|
if (!rootInfo.isDirectory())
|
|
151
151
|
throw new Error("Pi Subagent root must be an existing local directory.");
|
|
152
152
|
context.signal?.throwIfAborted();
|
|
153
|
-
if (input.request.tasks.some((task) => task.kind === "changeset")) {
|
|
153
|
+
if (!input.dryRun && input.request.tasks.some((task) => task.kind === "changeset")) {
|
|
154
154
|
await this.options.preflightHost?.({ request: input.request, cwd: input.cwd, root }, context);
|
|
155
155
|
}
|
|
156
156
|
const main = await this.options.inspectMainBase({ root }, context);
|
|
@@ -168,11 +168,14 @@ export class RoleLaunchRuntime {
|
|
|
168
168
|
if (input.request.finalJudgment) {
|
|
169
169
|
addRequired(input.request.finalJudgment.role, input.request.finalJudgment.modelClass);
|
|
170
170
|
}
|
|
171
|
+
const routes = [];
|
|
171
172
|
for (const [role, modelClasses] of required) {
|
|
172
|
-
for (const modelClass of modelClasses)
|
|
173
|
-
await this.prepareLaunch(role, modelClass, context);
|
|
173
|
+
for (const modelClass of modelClasses) {
|
|
174
|
+
const { launch } = await this.prepareLaunch(role, modelClass, context);
|
|
175
|
+
routes.push({ role, modelClass, model: launch.model, thinkingLevel: launch.thinkingLevel });
|
|
176
|
+
}
|
|
174
177
|
}
|
|
175
|
-
return { root, main };
|
|
178
|
+
return { root, main, routes };
|
|
176
179
|
}
|
|
177
180
|
async acquireLaunch(role, modelClass, context) {
|
|
178
181
|
const prepared = await this.prepareLaunch(role, modelClass, context);
|
package/dist/runner.d.ts
CHANGED
|
@@ -96,6 +96,14 @@ export interface VerifiedLaunch {
|
|
|
96
96
|
readonly env: Readonly<Record<string, string>>;
|
|
97
97
|
readonly tools: readonly string[];
|
|
98
98
|
}
|
|
99
|
+
export type ResolvedRoute = Pick<VerifiedLaunch, "role" | "modelClass" | "model" | "thinkingLevel">;
|
|
100
|
+
/** A dry-run result: the normalized request and the resolved routes; no state exists for it. */
|
|
101
|
+
export interface ExecutePlan {
|
|
102
|
+
request: ExecuteRequest;
|
|
103
|
+
root: string;
|
|
104
|
+
main: WorkspaceIdentity;
|
|
105
|
+
routes: readonly ResolvedRoute[];
|
|
106
|
+
}
|
|
99
107
|
export interface TransientLaunchHandle<Launch extends VerifiedLaunch = VerifiedLaunch> {
|
|
100
108
|
readonly launch: Launch;
|
|
101
109
|
cleanup(): Promise<void>;
|
|
@@ -109,12 +117,16 @@ export declare function withTransientLaunch<Launch extends VerifiedLaunch, Resul
|
|
|
109
117
|
export interface CoordinatorRuntime {
|
|
110
118
|
now(): number;
|
|
111
119
|
randomToken(): string;
|
|
120
|
+
/** `dryRun` skips checks that call Herdr; every other check runs. */
|
|
112
121
|
preflight(input: {
|
|
113
122
|
request: ExecuteRequest;
|
|
114
123
|
cwd: string;
|
|
124
|
+
dryRun?: boolean;
|
|
115
125
|
}, context: OperationContext): Promise<{
|
|
116
126
|
root: string;
|
|
117
127
|
main: WorkspaceIdentity;
|
|
128
|
+
/** Resolved route of each distinct Role and model class, when the coordinator reports it. */
|
|
129
|
+
routes?: readonly ResolvedRoute[];
|
|
118
130
|
}>;
|
|
119
131
|
acquireLaunch(role: string, modelClass: ModelClass, context: OperationContext): Promise<TransientLaunchHandle<VerifiedLaunch>>;
|
|
120
132
|
}
|
|
@@ -340,6 +352,10 @@ export declare class IsolatedRunner {
|
|
|
340
352
|
private saveProductive;
|
|
341
353
|
private callProductive;
|
|
342
354
|
private rethrowStopped;
|
|
355
|
+
/** Every execute check before durable state; a dry run also skips the Herdr host preflight. */
|
|
356
|
+
private admitExecute;
|
|
357
|
+
/** Dry run of execute: no state, lock, lease, worktree, or Herdr call. */
|
|
358
|
+
plan(value: unknown, cwd: string, outerSignal?: AbortSignal): Promise<ExecutePlan>;
|
|
343
359
|
execute(value: unknown, cwd: string, outerSignal?: AbortSignal): Promise<RunResponse>;
|
|
344
360
|
resume(value: unknown, root: string, outerSignal?: AbortSignal): Promise<RunResponse>;
|
|
345
361
|
/** Main selects an exact retained candidate; only the owned integration checkout is writable. */
|
|
@@ -350,6 +366,7 @@ export declare class IsolatedRunner {
|
|
|
350
366
|
private releaseRejected;
|
|
351
367
|
private releaseSuperseded;
|
|
352
368
|
private cleanupPromoted;
|
|
369
|
+
private cleanupPromotedStage;
|
|
353
370
|
private requireExactIntegration;
|
|
354
371
|
listOrphanRequests(cwd: string): Promise<{
|
|
355
372
|
invalidIds: string[];
|