@henryqw/pi-subagent 18.0.1 → 22.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 +76 -24
- package/README.md +116 -142
- package/dist/composition.d.ts +11 -0
- package/dist/composition.js +107 -0
- package/dist/direct-herdr.d.ts +17 -0
- package/dist/direct-herdr.js +219 -0
- package/dist/ephemeral.d.ts +2 -2
- package/dist/ephemeral.js +7 -7
- package/dist/git-runtime.d.ts +110 -0
- package/dist/git-runtime.js +472 -0
- package/dist/herdr-runtime.d.ts +137 -0
- package/dist/herdr-runtime.js +1228 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.js +20 -16
- package/dist/integration-git.d.ts +59 -0
- package/dist/integration-git.js +484 -0
- package/dist/launch-runtime.d.ts +54 -0
- package/dist/launch-runtime.js +195 -0
- package/dist/process.d.ts +9 -0
- package/dist/process.js +11 -0
- package/dist/runner.d.ts +356 -0
- package/dist/runner.js +2710 -0
- package/dist/schema.d.ts +2234 -0
- package/dist/schema.js +1236 -0
- package/dist/store.d.ts +50 -0
- package/dist/store.js +288 -0
- package/dist/working-evidence.d.ts +25 -0
- package/dist/working-evidence.js +261 -0
- package/docs/adr/001-composable-ephemeral-execution.md +3 -3
- package/docs/delegation-routing.html +89 -0
- package/docs/delegation-routing.svg +78 -0
- package/docs/orchestration-architecture.html +238 -0
- package/docs/orchestration-architecture.svg +189 -0
- package/docs/orchestration.md +122 -331
- package/example.png +0 -0
- package/examples/roles/implementer.md +2 -3
- package/extensions/admission.ts +66 -0
- package/extensions/config.ts +51 -37
- package/extensions/isolated.ts +638 -0
- package/extensions/result-transport.ts +13 -103
- package/extensions/role-tools.ts +11 -11
- package/extensions/subagent-command.ts +167 -0
- package/extensions/subagent.ts +264 -540
- package/extensions/task-name.ts +1 -2
- package/extensions/workflow.ts +60 -96
- package/package.json +14 -6
- package/skills/pi-subagent/SKILL.md +35 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { chmod, lstat, mkdtemp, open, realpath, rmdir, unlink } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { isAbsolute, join, normalize, relative, sep } from "node:path";
|
|
6
|
+
import { resolveConfiguredRoleLaunch } from "./index.js";
|
|
7
|
+
import { EXECUTION_BUDGET_ENV } from "./ephemeral.js";
|
|
8
|
+
import { registerModelTask } from "@henryqw/pi-task-models";
|
|
9
|
+
const PROMPT_FLAG = "--append-system-prompt";
|
|
10
|
+
const DIRECTORY_MODE = 0o700;
|
|
11
|
+
const PROMPT_MODE = 0o600;
|
|
12
|
+
export const ISOLATED_MODEL_TASK = {
|
|
13
|
+
id: "pi-subagent/isolatedRoleLaunch",
|
|
14
|
+
label: "Isolated Role launch",
|
|
15
|
+
purpose: "Resolve one task-scoped isolated Role launch.",
|
|
16
|
+
defaultProfile: "balanced",
|
|
17
|
+
};
|
|
18
|
+
function abortIfNeeded(signal) {
|
|
19
|
+
signal?.throwIfAborted();
|
|
20
|
+
}
|
|
21
|
+
function isMissing(error) {
|
|
22
|
+
return Boolean(error && typeof error === "object" && error.code === "ENOENT");
|
|
23
|
+
}
|
|
24
|
+
function isWithin(root, candidate) {
|
|
25
|
+
const fromRoot = relative(root, candidate);
|
|
26
|
+
return fromRoot === "" || (fromRoot !== ".." && !fromRoot.startsWith(`..${sep}`) && !isAbsolute(fromRoot));
|
|
27
|
+
}
|
|
28
|
+
async function removeTransientLaunch(directory, promptPath, promptRequired) {
|
|
29
|
+
const failures = [];
|
|
30
|
+
try {
|
|
31
|
+
await unlink(promptPath);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (promptRequired || !isMissing(error)) {
|
|
35
|
+
failures.push(new Error(`Could not remove transient Role prompt ${promptPath}.`, { cause: error }));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
await rmdir(directory);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
failures.push(new Error(`Could not remove transient Role launch directory ${directory}.`, { cause: error }));
|
|
43
|
+
}
|
|
44
|
+
if (failures.length === 1)
|
|
45
|
+
throw failures[0];
|
|
46
|
+
if (failures.length > 1)
|
|
47
|
+
throw new AggregateError(failures, "Transient Role launch cleanup was incomplete.");
|
|
48
|
+
}
|
|
49
|
+
export async function materializeTransientLaunch(prepared, signal) {
|
|
50
|
+
abortIfNeeded(signal);
|
|
51
|
+
const promptBytes = Buffer.from(prepared.prompt, "utf8");
|
|
52
|
+
const created = await mkdtemp(join(tmpdir(), "pi-subagent-role-"));
|
|
53
|
+
let directory = normalize(created);
|
|
54
|
+
let promptPath = join(directory, "system-prompt");
|
|
55
|
+
let promptCreated = false;
|
|
56
|
+
let file;
|
|
57
|
+
try {
|
|
58
|
+
directory = normalize(await realpath(created));
|
|
59
|
+
promptPath = join(directory, "system-prompt");
|
|
60
|
+
await chmod(directory, DIRECTORY_MODE);
|
|
61
|
+
const directoryInfo = await lstat(directory);
|
|
62
|
+
if (directoryInfo.isSymbolicLink() || !directoryInfo.isDirectory()
|
|
63
|
+
|| (directoryInfo.mode & 0o7777) !== DIRECTORY_MODE
|
|
64
|
+
|| !isWithin(normalize(await realpath(tmpdir())), directory)) {
|
|
65
|
+
throw new Error("Transient Role launch directory is not unique canonical mode 0700 OS-temp storage.");
|
|
66
|
+
}
|
|
67
|
+
file = await open(promptPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, PROMPT_MODE);
|
|
68
|
+
promptCreated = true;
|
|
69
|
+
await file.chmod(PROMPT_MODE);
|
|
70
|
+
abortIfNeeded(signal);
|
|
71
|
+
await file.writeFile(promptBytes, { signal });
|
|
72
|
+
await file.sync();
|
|
73
|
+
await file.close();
|
|
74
|
+
file = undefined;
|
|
75
|
+
const promptInfo = await lstat(promptPath);
|
|
76
|
+
if (promptInfo.isSymbolicLink() || !promptInfo.isFile()
|
|
77
|
+
|| (promptInfo.mode & 0o7777) !== PROMPT_MODE
|
|
78
|
+
|| normalize(await realpath(promptPath)) !== promptPath) {
|
|
79
|
+
throw new Error("Transient Role prompt is not a canonical mode 0600 regular file.");
|
|
80
|
+
}
|
|
81
|
+
const args = [...prepared.launch.args];
|
|
82
|
+
args.splice(prepared.promptArgIndex, 0, PROMPT_FLAG, promptPath);
|
|
83
|
+
const launch = Object.freeze({
|
|
84
|
+
...prepared.launch,
|
|
85
|
+
args: Object.freeze(args),
|
|
86
|
+
});
|
|
87
|
+
let cleanup;
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
launch,
|
|
90
|
+
cleanup: async () => await (cleanup ??= removeTransientLaunch(directory, promptPath, true)),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
const failures = [error];
|
|
95
|
+
if (file) {
|
|
96
|
+
try {
|
|
97
|
+
await file.close();
|
|
98
|
+
}
|
|
99
|
+
catch (closeError) {
|
|
100
|
+
failures.push(closeError);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
await removeTransientLaunch(directory, promptPath, promptCreated);
|
|
105
|
+
}
|
|
106
|
+
catch (cleanupError) {
|
|
107
|
+
failures.push(cleanupError);
|
|
108
|
+
}
|
|
109
|
+
if (failures.length > 1) {
|
|
110
|
+
throw new AggregateError(failures, `Transient Role prompt materialization for ${prepared.launch.role} failed and cleanup was incomplete.`);
|
|
111
|
+
}
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export class RoleLaunchRuntime {
|
|
116
|
+
options;
|
|
117
|
+
constructor(options) {
|
|
118
|
+
this.options = options;
|
|
119
|
+
registerModelTask(options.pi, ISOLATED_MODEL_TASK);
|
|
120
|
+
}
|
|
121
|
+
now() {
|
|
122
|
+
return this.options.now?.() ?? Date.now();
|
|
123
|
+
}
|
|
124
|
+
randomToken() {
|
|
125
|
+
return this.options.randomToken?.() ?? randomBytes(12).toString("hex");
|
|
126
|
+
}
|
|
127
|
+
async prepareLaunch(role, modelClass, context) {
|
|
128
|
+
abortIfNeeded(context.signal);
|
|
129
|
+
const input = { role, modelClass, task: ISOLATED_MODEL_TASK };
|
|
130
|
+
const prepared = await resolveConfiguredRoleLaunch(this.options.pi, this.options.context(), input);
|
|
131
|
+
if (prepared.missingSkills.length) {
|
|
132
|
+
throw new Error(`Role ${role} requires missing Skills: ${prepared.missingSkills.join(", ")}.`);
|
|
133
|
+
}
|
|
134
|
+
const executionBudget = this.options.executionBudget?.();
|
|
135
|
+
return Object.freeze({
|
|
136
|
+
launch: Object.freeze({
|
|
137
|
+
role: prepared.role,
|
|
138
|
+
modelClass,
|
|
139
|
+
model: `${prepared.model.provider}/${prepared.model.id}`,
|
|
140
|
+
thinkingLevel: prepared.thinkingLevel,
|
|
141
|
+
args: Object.freeze([...prepared.args]),
|
|
142
|
+
env: Object.freeze({
|
|
143
|
+
...prepared.env,
|
|
144
|
+
...(executionBudget === undefined ? {} : {
|
|
145
|
+
[EXECUTION_BUDGET_ENV]: JSON.stringify({ ...executionBudget, startedAt: this.now() }),
|
|
146
|
+
}),
|
|
147
|
+
}),
|
|
148
|
+
tools: Object.freeze([...prepared.tools]),
|
|
149
|
+
}),
|
|
150
|
+
prompt: prepared.systemPrompt,
|
|
151
|
+
promptArgIndex: prepared.promptArgIndex,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async preflight(input, context) {
|
|
155
|
+
abortIfNeeded(context.signal);
|
|
156
|
+
const resolvedRoot = await this.options.resolveRoot(input.cwd, context);
|
|
157
|
+
if (typeof resolvedRoot !== "string" || !isAbsolute(resolvedRoot) || resolvedRoot.includes("\0")) {
|
|
158
|
+
throw new Error("Pi Subagent root resolver must return an absolute canonical path.");
|
|
159
|
+
}
|
|
160
|
+
const root = normalize(await realpath(resolvedRoot));
|
|
161
|
+
if (root !== resolvedRoot)
|
|
162
|
+
throw new Error("Pi Subagent root resolver returned a non-canonical path.");
|
|
163
|
+
const rootInfo = await lstat(root);
|
|
164
|
+
if (!rootInfo.isDirectory())
|
|
165
|
+
throw new Error("Pi Subagent root must be an existing local directory.");
|
|
166
|
+
abortIfNeeded(context.signal);
|
|
167
|
+
if (input.request.tasks.some((task) => task.kind === "changeset")) {
|
|
168
|
+
await this.options.preflightHost?.({ request: input.request, cwd: input.cwd, root }, context);
|
|
169
|
+
}
|
|
170
|
+
const main = await this.options.inspectMain({ root }, context);
|
|
171
|
+
const required = new Map();
|
|
172
|
+
const addRequired = (role, modelClass) => {
|
|
173
|
+
const modelClasses = required.get(role) ?? new Set();
|
|
174
|
+
modelClasses.add(modelClass);
|
|
175
|
+
required.set(role, modelClasses);
|
|
176
|
+
};
|
|
177
|
+
for (const task of input.request.tasks) {
|
|
178
|
+
addRequired(task.role, task.modelClass);
|
|
179
|
+
if (task.kind === "changeset" && task.judgment)
|
|
180
|
+
addRequired(task.judgment.role, task.judgment.modelClass);
|
|
181
|
+
}
|
|
182
|
+
if (input.request.finalJudgment) {
|
|
183
|
+
addRequired(input.request.finalJudgment.role, input.request.finalJudgment.modelClass);
|
|
184
|
+
}
|
|
185
|
+
for (const [role, modelClasses] of required) {
|
|
186
|
+
for (const modelClass of modelClasses)
|
|
187
|
+
await this.prepareLaunch(role, modelClass, context);
|
|
188
|
+
}
|
|
189
|
+
return { root, main };
|
|
190
|
+
}
|
|
191
|
+
async acquireLaunch(role, modelClass, context) {
|
|
192
|
+
const prepared = await this.prepareLaunch(role, modelClass, context);
|
|
193
|
+
return await materializeTransientLaunch(prepared, context.signal);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type ProcessOptions = {
|
|
2
|
+
cwd: string;
|
|
3
|
+
signal: AbortSignal;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
stdin?: string;
|
|
6
|
+
};
|
|
7
|
+
/** Infrastructure errors reject; only ordinary command exits return a result. */
|
|
8
|
+
export declare const runProcess: (command: string, args: string[], options: ProcessOptions) => Promise<import("@henryqw/pi-process").ExecResult>;
|
|
9
|
+
export {};
|
package/dist/process.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { spawnBounded } from "@henryqw/pi-process";
|
|
2
|
+
const OUTPUT_LIMIT_BYTES = 1024 * 1024;
|
|
3
|
+
/** Infrastructure errors reject; only ordinary command exits return a result. */
|
|
4
|
+
export const runProcess = async (command, args, options) => await spawnBounded(command, args, {
|
|
5
|
+
cwd: options.cwd,
|
|
6
|
+
signal: options.signal,
|
|
7
|
+
timeoutMs: options.timeoutMs ?? null,
|
|
8
|
+
...(options.stdin === undefined ? {} : { stdin: options.stdin }),
|
|
9
|
+
stdoutLimitBytes: OUTPUT_LIMIT_BYTES,
|
|
10
|
+
stderrLimitBytes: OUTPUT_LIMIT_BYTES,
|
|
11
|
+
});
|
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import type { EphemeralSubagentExecutor } from "./ephemeral.ts";
|
|
2
|
+
import { IntegrationGit } from "./integration-git.ts";
|
|
3
|
+
import { type AllocationKind, type CheckCommand, type ChangesetTaskRequest, type CleanupKind, type ExecuteRequest, type ExecutionPolicySnapshot, type HostAllocationIntent, type HostAllocationPlan, type ModelClass, type ReviewEvidence, type ResumeRequest, type RunState, type TaskAttempt, type TaskRequest, type TaskState, type WorktreeAllocationIntent, type WorktreeAllocationPlan, type WorkspaceIdentity } from "./schema.ts";
|
|
4
|
+
import { FileRunStore } from "./store.ts";
|
|
5
|
+
export declare const CLEANUP_SAFETY_BUDGET_MS = 30000;
|
|
6
|
+
export declare const TERMINATION_SAFETY_BUDGET_MS = 15000;
|
|
7
|
+
export declare const STATUS_INSPECTION_BUDGET_MS = 5000;
|
|
8
|
+
export interface OperationContext {
|
|
9
|
+
readonly signal: AbortSignal;
|
|
10
|
+
readonly timeoutMs?: number;
|
|
11
|
+
readonly deadline?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface CommandResult extends CheckCommand {
|
|
14
|
+
code: number;
|
|
15
|
+
killed?: boolean;
|
|
16
|
+
stdout: string;
|
|
17
|
+
stderr: string;
|
|
18
|
+
}
|
|
19
|
+
type AllocationFailureResult<Kind extends AllocationKind> = {
|
|
20
|
+
kind: Kind;
|
|
21
|
+
outcome: "absent";
|
|
22
|
+
failure: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: Kind;
|
|
25
|
+
outcome: "unknown";
|
|
26
|
+
failure: string;
|
|
27
|
+
possibleResources?: string[];
|
|
28
|
+
};
|
|
29
|
+
export type WorktreeAllocationResult = {
|
|
30
|
+
kind: "worktree";
|
|
31
|
+
outcome: "owned";
|
|
32
|
+
} | AllocationFailureResult<"worktree">;
|
|
33
|
+
export type WorkspaceAllocationResult = {
|
|
34
|
+
kind: "workspace";
|
|
35
|
+
outcome: "owned";
|
|
36
|
+
workspaceId: string;
|
|
37
|
+
rootTabId: string;
|
|
38
|
+
rootPaneId: string;
|
|
39
|
+
} | AllocationFailureResult<"workspace">;
|
|
40
|
+
export type WorkerTabAllocationResult = {
|
|
41
|
+
kind: "worker_tab";
|
|
42
|
+
outcome: "owned";
|
|
43
|
+
tabId: string;
|
|
44
|
+
paneId: string;
|
|
45
|
+
} | AllocationFailureResult<"worker_tab">;
|
|
46
|
+
export type AgentAllocationResult = {
|
|
47
|
+
kind: "agent";
|
|
48
|
+
outcome: "owned";
|
|
49
|
+
} | AllocationFailureResult<"agent">;
|
|
50
|
+
export type HostAllocationResult = WorkspaceAllocationResult | WorkerTabAllocationResult | AgentAllocationResult;
|
|
51
|
+
export type AllocationResult = WorktreeAllocationResult | HostAllocationResult;
|
|
52
|
+
export type AllocationReconciliation<Kind extends AllocationKind = AllocationKind> = {
|
|
53
|
+
kind: Kind;
|
|
54
|
+
outcome: "absent";
|
|
55
|
+
} | {
|
|
56
|
+
kind: Kind;
|
|
57
|
+
outcome: "possible";
|
|
58
|
+
failure: string;
|
|
59
|
+
possibleResources?: string[];
|
|
60
|
+
};
|
|
61
|
+
export type WorkerResult = {
|
|
62
|
+
outcome: "candidate";
|
|
63
|
+
candidate: WorkspaceIdentity;
|
|
64
|
+
diagnostic?: string;
|
|
65
|
+
} | {
|
|
66
|
+
outcome: "blocked" | "not_prompted";
|
|
67
|
+
diagnostic: string;
|
|
68
|
+
} | {
|
|
69
|
+
outcome: "unknown" | "interrupted";
|
|
70
|
+
diagnostic: string;
|
|
71
|
+
};
|
|
72
|
+
export interface CheckRunResult {
|
|
73
|
+
results: CommandResult[];
|
|
74
|
+
identityAfter: WorkspaceIdentity;
|
|
75
|
+
}
|
|
76
|
+
export interface ReviewResult {
|
|
77
|
+
verdict: string;
|
|
78
|
+
identityAfter: WorkspaceIdentity;
|
|
79
|
+
}
|
|
80
|
+
/** A just-in-time launch whose argv contains only an ephemeral Role prompt path. */
|
|
81
|
+
export interface VerifiedLaunch {
|
|
82
|
+
readonly role: string;
|
|
83
|
+
readonly modelClass: ModelClass;
|
|
84
|
+
readonly model: string;
|
|
85
|
+
readonly thinkingLevel: string;
|
|
86
|
+
readonly args: readonly string[];
|
|
87
|
+
readonly env: Readonly<Record<string, string>>;
|
|
88
|
+
readonly tools: readonly string[];
|
|
89
|
+
}
|
|
90
|
+
export interface TransientLaunchHandle<Launch extends VerifiedLaunch = VerifiedLaunch> {
|
|
91
|
+
readonly launch: Launch;
|
|
92
|
+
cleanup(): Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
/** Run one launch action and surface cleanup failure, including after action failure or abort. */
|
|
95
|
+
export declare function withTransientLaunch<Launch extends VerifiedLaunch, Result>(handle: TransientLaunchHandle<Launch>, operation: (launch: Launch) => Promise<Result>): Promise<Result>;
|
|
96
|
+
/**
|
|
97
|
+
* Productive hooks may inspect or change implementation state. Every call receives
|
|
98
|
+
* the same request abort signal. Individual I/O operations keep their own caps.
|
|
99
|
+
*/
|
|
100
|
+
export interface CoordinatorRuntime {
|
|
101
|
+
now(): number;
|
|
102
|
+
randomToken(): string;
|
|
103
|
+
preflight(input: {
|
|
104
|
+
request: ExecuteRequest;
|
|
105
|
+
cwd: string;
|
|
106
|
+
}, context: OperationContext): Promise<{
|
|
107
|
+
root: string;
|
|
108
|
+
main: WorkspaceIdentity;
|
|
109
|
+
}>;
|
|
110
|
+
acquireLaunch(role: string, modelClass: ModelClass, context: OperationContext): Promise<TransientLaunchHandle<VerifiedLaunch>>;
|
|
111
|
+
}
|
|
112
|
+
export type HostAllocationKind = Exclude<AllocationKind, "worktree">;
|
|
113
|
+
export type HostCleanupKind = Extract<CleanupKind, "worker_tab" | "workspace">;
|
|
114
|
+
export interface HostRuntime {
|
|
115
|
+
planHostAllocation(input: {
|
|
116
|
+
readonly requestId: ExecuteRequest["id"];
|
|
117
|
+
readonly goal: ExecuteRequest["goal"];
|
|
118
|
+
kind: HostAllocationKind;
|
|
119
|
+
task: TaskRequest;
|
|
120
|
+
attempt: TaskAttempt;
|
|
121
|
+
}, context: OperationContext): Promise<HostAllocationPlan>;
|
|
122
|
+
allocateHost(input: {
|
|
123
|
+
requestId: ExecuteRequest["id"];
|
|
124
|
+
intent: HostAllocationIntent;
|
|
125
|
+
task: TaskRequest;
|
|
126
|
+
attempt: TaskAttempt;
|
|
127
|
+
/** Invoked only after pane, lease, and startability checks at the final agent-start boundary. */
|
|
128
|
+
acquireLaunch?: () => Promise<TransientLaunchHandle<VerifiedLaunch>>;
|
|
129
|
+
}, context: OperationContext): Promise<HostAllocationResult>;
|
|
130
|
+
reconcileHostAllocation(input: {
|
|
131
|
+
requestId: ExecuteRequest["id"];
|
|
132
|
+
intent: HostAllocationIntent;
|
|
133
|
+
task: TaskRequest;
|
|
134
|
+
attempt: TaskAttempt;
|
|
135
|
+
}, context: OperationContext): Promise<AllocationReconciliation<HostAllocationKind>>;
|
|
136
|
+
runWorker(input: {
|
|
137
|
+
readonly goal: ExecuteRequest["goal"];
|
|
138
|
+
readonly contexts: readonly TextTaskContext[];
|
|
139
|
+
task: TaskRequest;
|
|
140
|
+
attempt: TaskAttempt;
|
|
141
|
+
workerId: string;
|
|
142
|
+
kind: "initial" | "correction" | "followup";
|
|
143
|
+
preCandidate: WorkspaceIdentity;
|
|
144
|
+
failure?: string;
|
|
145
|
+
instruction?: string;
|
|
146
|
+
}, context: OperationContext): Promise<WorkerResult>;
|
|
147
|
+
terminateWorker(input: {
|
|
148
|
+
task: TaskRequest;
|
|
149
|
+
attempt: TaskAttempt;
|
|
150
|
+
workerId: string;
|
|
151
|
+
candidate: WorkspaceIdentity;
|
|
152
|
+
}, context: OperationContext): Promise<{
|
|
153
|
+
outcome: "terminated";
|
|
154
|
+
} | {
|
|
155
|
+
outcome: "unknown";
|
|
156
|
+
failure: string;
|
|
157
|
+
}>;
|
|
158
|
+
reconcileWorkerTermination(input: {
|
|
159
|
+
task: TaskRequest;
|
|
160
|
+
attempt: TaskAttempt;
|
|
161
|
+
workerId: string;
|
|
162
|
+
candidate: WorkspaceIdentity;
|
|
163
|
+
}, context: OperationContext): Promise<{
|
|
164
|
+
outcome: "terminated" | "active";
|
|
165
|
+
} | {
|
|
166
|
+
outcome: "unknown";
|
|
167
|
+
failure: string;
|
|
168
|
+
}>;
|
|
169
|
+
cleanupHost(input: {
|
|
170
|
+
requestId: ExecuteRequest["id"];
|
|
171
|
+
kind: HostCleanupKind;
|
|
172
|
+
task: TaskRequest;
|
|
173
|
+
attempt: TaskAttempt;
|
|
174
|
+
}, context: OperationContext): Promise<{
|
|
175
|
+
outcome: "completed" | "absent";
|
|
176
|
+
} | {
|
|
177
|
+
outcome: "blocked";
|
|
178
|
+
failure: string;
|
|
179
|
+
}>;
|
|
180
|
+
}
|
|
181
|
+
export interface TaskCandidateInspector {
|
|
182
|
+
inspectTaskCandidate(input: {
|
|
183
|
+
root: string;
|
|
184
|
+
task: TaskRequest;
|
|
185
|
+
attempt: TaskAttempt;
|
|
186
|
+
}, context: OperationContext): Promise<WorkspaceIdentity>;
|
|
187
|
+
}
|
|
188
|
+
export interface InFlightTaskCandidateInspection {
|
|
189
|
+
candidate: WorkspaceIdentity;
|
|
190
|
+
clean: boolean;
|
|
191
|
+
valid: boolean;
|
|
192
|
+
}
|
|
193
|
+
export interface InFlightTaskCandidateInspector {
|
|
194
|
+
inspectInFlightTaskCandidate(input: {
|
|
195
|
+
root: string;
|
|
196
|
+
task: TaskRequest;
|
|
197
|
+
attempt: TaskAttempt;
|
|
198
|
+
}, context: OperationContext): Promise<InFlightTaskCandidateInspection>;
|
|
199
|
+
}
|
|
200
|
+
export interface GitRuntime {
|
|
201
|
+
inspectMain(input: {
|
|
202
|
+
root: string;
|
|
203
|
+
}, context: OperationContext): Promise<WorkspaceIdentity>;
|
|
204
|
+
allocateWorktree(input: {
|
|
205
|
+
root: string;
|
|
206
|
+
baseRoot?: string;
|
|
207
|
+
intent: WorktreeAllocationIntent;
|
|
208
|
+
task: TaskRequest;
|
|
209
|
+
attempt: TaskAttempt;
|
|
210
|
+
onPrepared(worktree: WorktreeAllocationPlan): Promise<void>;
|
|
211
|
+
}, context: OperationContext): Promise<WorktreeAllocationResult>;
|
|
212
|
+
reconcileWorktreeAllocation(input: {
|
|
213
|
+
root: string;
|
|
214
|
+
intent: WorktreeAllocationIntent;
|
|
215
|
+
task: TaskRequest;
|
|
216
|
+
attempt: TaskAttempt;
|
|
217
|
+
}, context: OperationContext): Promise<AllocationReconciliation<"worktree">>;
|
|
218
|
+
runChecks(input: {
|
|
219
|
+
root: string;
|
|
220
|
+
scope: "task" | "final";
|
|
221
|
+
taskId?: string;
|
|
222
|
+
attempt?: TaskAttempt;
|
|
223
|
+
checks: CheckCommand[];
|
|
224
|
+
candidate: WorkspaceIdentity;
|
|
225
|
+
}, context: OperationContext): Promise<CheckRunResult>;
|
|
226
|
+
review(input: {
|
|
227
|
+
root: string;
|
|
228
|
+
scope: "task" | "final";
|
|
229
|
+
phase: ReviewEvidence["phase"];
|
|
230
|
+
taskId?: string;
|
|
231
|
+
attempt?: TaskAttempt;
|
|
232
|
+
criterion: string;
|
|
233
|
+
base: WorkspaceIdentity;
|
|
234
|
+
tip: WorkspaceIdentity;
|
|
235
|
+
acquireLaunch(): Promise<TransientLaunchHandle<VerifiedLaunch>>;
|
|
236
|
+
}, context: OperationContext): Promise<ReviewResult>;
|
|
237
|
+
inspectRetainedTask(input: {
|
|
238
|
+
root: string;
|
|
239
|
+
task: TaskRequest;
|
|
240
|
+
attempt: TaskAttempt;
|
|
241
|
+
}, context: OperationContext): Promise<WorkspaceIdentity>;
|
|
242
|
+
}
|
|
243
|
+
export type MainStatus = {
|
|
244
|
+
status: "current" | "drifted";
|
|
245
|
+
expected: WorkspaceIdentity;
|
|
246
|
+
actual: WorkspaceIdentity;
|
|
247
|
+
} | {
|
|
248
|
+
status: "unavailable";
|
|
249
|
+
expected: WorkspaceIdentity;
|
|
250
|
+
failure: string;
|
|
251
|
+
};
|
|
252
|
+
export interface RunResponse {
|
|
253
|
+
text: string;
|
|
254
|
+
state: RunState;
|
|
255
|
+
main?: MainStatus;
|
|
256
|
+
continuation?: ResumeRequest;
|
|
257
|
+
}
|
|
258
|
+
export type TextTaskContext = {
|
|
259
|
+
taskId: string;
|
|
260
|
+
text: string;
|
|
261
|
+
};
|
|
262
|
+
/** Build one bounded changeset assignment. Call this before acquiring its Role launch. */
|
|
263
|
+
export declare function buildChangesetTaskPrompt(input: {
|
|
264
|
+
readonly goal: ExecuteRequest["goal"];
|
|
265
|
+
readonly contexts: readonly TextTaskContext[];
|
|
266
|
+
task: ChangesetTaskRequest;
|
|
267
|
+
kind: "initial" | "correction" | "followup";
|
|
268
|
+
worktreeCwd: string;
|
|
269
|
+
failure?: string;
|
|
270
|
+
instruction?: string;
|
|
271
|
+
}): string;
|
|
272
|
+
/** Resolve completed text outputs in the consumer's declared context order. */
|
|
273
|
+
export declare function resolveTextTaskContexts(state: {
|
|
274
|
+
readonly tasks: readonly TaskState[];
|
|
275
|
+
}, task: Pick<TaskRequest, "id" | "contextFrom">): TextTaskContext[];
|
|
276
|
+
/** Format complete context blocks or fail; prompt data is never truncated. */
|
|
277
|
+
export declare function formatTextTaskContexts(contexts: readonly TextTaskContext[], maxBytes: number): string;
|
|
278
|
+
/** Build a bounded text-task assignment with upstream output marked as task data. */
|
|
279
|
+
export declare function buildTextTaskPrompt(goal: ExecuteRequest["goal"], task: Pick<TaskRequest, "id" | "requirements" | "deliverable">, contexts: readonly TextTaskContext[]): string;
|
|
280
|
+
export declare function readyPendingTasks(state: RunState): TaskState[];
|
|
281
|
+
export declare class IsolatedRunner {
|
|
282
|
+
private readonly coordinatorRuntime;
|
|
283
|
+
private readonly hostRuntime;
|
|
284
|
+
private readonly gitRuntime;
|
|
285
|
+
private readonly store;
|
|
286
|
+
private readonly integrationGit;
|
|
287
|
+
private readonly textExecutor;
|
|
288
|
+
private readonly currentPolicy;
|
|
289
|
+
private readonly productiveLifecycles;
|
|
290
|
+
private readonly followupControls;
|
|
291
|
+
constructor(coordinatorRuntime: CoordinatorRuntime, hostRuntime: HostRuntime, gitRuntime: GitRuntime & TaskCandidateInspector, store: FileRunStore | undefined, textExecutor: EphemeralSubagentExecutor, policy?: ExecutionPolicySnapshot, currentPolicy?: () => ExecutionPolicySnapshot, integrationGit?: IntegrationGit);
|
|
292
|
+
listRequests(root: string): Promise<{
|
|
293
|
+
requests: Array<{
|
|
294
|
+
id: string;
|
|
295
|
+
name: string;
|
|
296
|
+
status: string;
|
|
297
|
+
tasks: Array<{
|
|
298
|
+
id: string;
|
|
299
|
+
name: string;
|
|
300
|
+
status: string;
|
|
301
|
+
kind: string;
|
|
302
|
+
}>;
|
|
303
|
+
}>;
|
|
304
|
+
invalidIds: string[];
|
|
305
|
+
}>;
|
|
306
|
+
canFollowup(root: string, requestId: string, taskId: string): boolean;
|
|
307
|
+
drainFollowups(root: string, requestId: string, taskId: string): string[];
|
|
308
|
+
queueFollowup(root: string, requestId: string, taskId: string, instruction: string): string;
|
|
309
|
+
private withProductiveRun;
|
|
310
|
+
private withLifecycleLock;
|
|
311
|
+
private saveProductive;
|
|
312
|
+
private callProductive;
|
|
313
|
+
private rethrowStopped;
|
|
314
|
+
execute(value: unknown, cwd: string, outerSignal?: AbortSignal): Promise<RunResponse>;
|
|
315
|
+
resume(value: unknown, root: string, outerSignal?: AbortSignal): Promise<RunResponse>;
|
|
316
|
+
/** Main selects an exact retained candidate; only the owned integration checkout is writable. */
|
|
317
|
+
stage(value: unknown, root: string, outerSignal?: AbortSignal): Promise<RunResponse>;
|
|
318
|
+
private stageReceipts;
|
|
319
|
+
/** Only Main can validate/promote the exact selected, clean combined tip. */
|
|
320
|
+
integrate(value: unknown, root: string, outerSignal?: AbortSignal): Promise<RunResponse>;
|
|
321
|
+
private releaseRejected;
|
|
322
|
+
private releaseSuperseded;
|
|
323
|
+
private cleanupPromoted;
|
|
324
|
+
private requireExactIntegration;
|
|
325
|
+
abort(id: string, root: string, outerSignal?: AbortSignal): Promise<RunResponse>;
|
|
326
|
+
status(id: string, root: string, outerSignal?: AbortSignal): Promise<RunResponse>;
|
|
327
|
+
private run;
|
|
328
|
+
private dispatchTask;
|
|
329
|
+
private dispatchTextTask;
|
|
330
|
+
private correctionAllowed;
|
|
331
|
+
private driveWorkerSafely;
|
|
332
|
+
private driveWorker;
|
|
333
|
+
private terminationCandidate;
|
|
334
|
+
private terminateWithSafety;
|
|
335
|
+
private runCheckBatch;
|
|
336
|
+
private runReview;
|
|
337
|
+
private retainCandidate;
|
|
338
|
+
private verifyRetainedTask;
|
|
339
|
+
private retry;
|
|
340
|
+
private finalize;
|
|
341
|
+
private runFinal;
|
|
342
|
+
private markFinalFailed;
|
|
343
|
+
private markSuperseded;
|
|
344
|
+
private recoverInterrupted;
|
|
345
|
+
private ensureFollowupControl;
|
|
346
|
+
private controlKey;
|
|
347
|
+
private activeControl;
|
|
348
|
+
private activeControls;
|
|
349
|
+
private hasActiveControl;
|
|
350
|
+
private hasAnyActiveControl;
|
|
351
|
+
private closeFollowupControl;
|
|
352
|
+
private closeRequestControls;
|
|
353
|
+
private attention;
|
|
354
|
+
private response;
|
|
355
|
+
}
|
|
356
|
+
export {};
|