pi-herdr-agents 2.0.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +20 -14
- package/CHANGELOG.md +63 -1
- package/CONTEXT.md +86 -32
- package/README.md +262 -141
- package/RELEASING.md +4 -2
- package/config.json.example +0 -3
- package/docs/README.md +38 -15
- package/docs/adr/0001-btw-ephemeral-side-questions.md +1 -1
- package/docs/adr/0002-agent-workflow-skill-runtime-taxonomy.md +1 -1
- package/docs/adr/0003-installable-role-packs.md +21 -13
- package/docs/adr/0009-remove-workflow-subsystem.md +1 -1
- package/docs/adr/0011-explicit-worktree-cleanup.md +1 -1
- package/docs/adr/0012-adopt-maestro-seams-in-repo.md +45 -0
- package/docs/adr/0013-pack-neutral-execution-host.md +63 -0
- package/docs/adr/0014-operator-cancel-terminal-intent.md +134 -0
- package/docs/worktree-subagents.md +10 -6
- package/maestro/adapters/fake/fake-harness-adapter.ts +263 -0
- package/{pi-extension/subagents/activity.ts → maestro/adapters/pi/activity-file.ts} +15 -72
- package/{pi-extension/subagents → maestro/adapters/pi/child}/subagent-done.ts +30 -3
- package/{pi-extension/subagents → maestro/adapters/pi}/completion.ts +4 -4
- package/{pi-extension/subagents → maestro/adapters/pi}/launch.ts +388 -285
- package/maestro/adapters/pi/model-registry.ts +46 -0
- package/maestro/adapters/pi/pi-harness-adapter.ts +701 -0
- package/maestro/adapters/pi/process-identity.ts +303 -0
- package/{pi-extension/subagents → maestro/adapters/pi}/session.ts +1 -13
- package/maestro/adapters/pi/task-model-init.ts +55 -0
- package/maestro/core/activity.ts +29 -0
- package/maestro/core/config/model-config.ts +473 -0
- package/{pi-extension/subagents → maestro/core/config}/pane-config.ts +4 -6
- package/{pi-extension/subagents → maestro/core/config}/persistent-config.ts +4 -6
- package/{pi-extension/subagents → maestro/core/config}/role-config.ts +15 -10
- package/{pi-extension/subagents → maestro/core/config}/supervision-config.ts +4 -6
- package/{pi-extension/subagents → maestro/core/config}/task-model-init.ts +45 -58
- package/maestro/core/config/task-model-types.ts +22 -0
- package/maestro/core/harness-adapter.ts +117 -0
- package/maestro/core/index.ts +3 -0
- package/{pi-extension/subagents → maestro/core}/lifecycle.ts +27 -126
- package/maestro/core/roles/discovery.ts +555 -0
- package/{pi-extension/subagents/runtime-routing.ts → maestro/core/routing.ts} +36 -43
- package/maestro/core/shell.ts +3 -0
- package/{pi-extension/subagents → maestro/core}/status.ts +3 -7
- package/{pi-extension/subagents → maestro/core}/supervision.ts +1 -1
- package/maestro/core/surface-provider.ts +185 -0
- package/maestro/core/types.ts +449 -0
- package/maestro/core/worktree-cleanup.ts +374 -0
- package/maestro/core/worktree.ts +93 -0
- package/maestro/runtime/index.ts +44 -0
- package/maestro/runtime/pi-run-session.ts +1210 -0
- package/maestro/runtime/run-session.ts +801 -0
- package/maestro/runtime/surface-retention.ts +6 -0
- package/maestro/runtime/task-model-init.ts +22 -0
- package/maestro/runtime/worktree-operations.ts +627 -0
- package/maestro/surfaces/fake/fake-surface-provider.ts +311 -0
- package/maestro/surfaces/herdr/herdr-surface-provider.ts +200 -0
- package/{pi-extension/subagents → maestro/surfaces/herdr}/herdr.ts +65 -14
- package/{pi-extension/subagents → maestro/surfaces/herdr}/terminal.ts +7 -5
- package/package.json +10 -9
- package/pi-extension/subagents/config-path.ts +5 -1
- package/pi-extension/subagents/index.ts +848 -1805
- package/pi-extension/subagents/model-registry.ts +46 -0
- package/skills/pi-herdr-agents/SKILL.md +276 -0
- package/agents/adversarial-reviewer.md +0 -153
- package/agents/planner.md +0 -87
- package/agents/poteto.md +0 -32
- package/agents/reviewer.md +0 -132
- package/agents/scout.md +0 -105
- package/agents/visual-tester.md +0 -234
- package/agents/worker.md +0 -121
- package/docs/review-evaluation.md +0 -183
- package/pi-extension/subagents/model-config.ts +0 -305
- package/pi-extension/subagents/plan-skill.md +0 -280
- package/pi-extension/subagents/worktree-cleanup.ts +0 -789
- package/skills/orchestrate/SKILL.md +0 -91
- package/skills/orchestrate/adversarial-review-example.js +0 -400
- package/skills/orchestrate/adversarial-review.md +0 -87
- /package/{pi-extension/subagents → maestro/core/config}/type-guards.ts +0 -0
- /package/{pi-extension/subagents → maestro/core}/wake.ts +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HarnessAdapter,
|
|
3
|
+
ResumeOptions,
|
|
4
|
+
SpawnOptions,
|
|
5
|
+
} from "../../core/harness-adapter.ts";
|
|
6
|
+
import type {
|
|
7
|
+
AgentHandle,
|
|
8
|
+
AgentState,
|
|
9
|
+
CompletionEvidence,
|
|
10
|
+
} from "../../core/types.ts";
|
|
11
|
+
|
|
12
|
+
const ABORT_MESSAGE = "Aborted while waiting for subagent to finish";
|
|
13
|
+
|
|
14
|
+
interface FakeHarnessRecord {
|
|
15
|
+
handle: AgentHandle;
|
|
16
|
+
request: SpawnOptions | ResumeOptions;
|
|
17
|
+
state: AgentState;
|
|
18
|
+
output: string[];
|
|
19
|
+
inputs: string[];
|
|
20
|
+
completion?: CompletionEvidence;
|
|
21
|
+
exitCode?: number;
|
|
22
|
+
waiters: Set<Waiter>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Waiter {
|
|
26
|
+
resolve(evidence: CompletionEvidence): void;
|
|
27
|
+
reject(error: Error): void;
|
|
28
|
+
signal: AbortSignal;
|
|
29
|
+
onAbort(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class FakeHarnessAdapter implements HarnessAdapter {
|
|
33
|
+
readonly name = "fake";
|
|
34
|
+
|
|
35
|
+
#nextHandle = 1;
|
|
36
|
+
#records = new Map<string, FakeHarnessRecord>();
|
|
37
|
+
#sessionHandles = new Map<string, Set<string>>();
|
|
38
|
+
|
|
39
|
+
isAvailable(): boolean {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async spawn(opts: SpawnOptions): Promise<AgentHandle> {
|
|
44
|
+
const handle: AgentHandle = {
|
|
45
|
+
id: this.#newHandleId(),
|
|
46
|
+
name: opts.name,
|
|
47
|
+
role: opts.role.name,
|
|
48
|
+
harness: this.name,
|
|
49
|
+
cwd: opts.cwd,
|
|
50
|
+
startedAt: Date.now(),
|
|
51
|
+
sessionId: opts.sessionId,
|
|
52
|
+
surfaceId: opts.surface?.id,
|
|
53
|
+
worktree: opts.worktree,
|
|
54
|
+
};
|
|
55
|
+
this.#records.set(handle.id, {
|
|
56
|
+
handle,
|
|
57
|
+
request: cloneSpawnOptions(opts),
|
|
58
|
+
state: "working",
|
|
59
|
+
output: [],
|
|
60
|
+
inputs: [],
|
|
61
|
+
waiters: new Set(),
|
|
62
|
+
});
|
|
63
|
+
this.#rememberSession(handle);
|
|
64
|
+
return {
|
|
65
|
+
...handle,
|
|
66
|
+
worktree: handle.worktree ? { ...handle.worktree } : undefined,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async resume(opts: ResumeOptions): Promise<AgentHandle> {
|
|
71
|
+
if (!opts.name) throw new Error("resume requires name");
|
|
72
|
+
const prior = this.#latestSessionHandle(opts.sessionId);
|
|
73
|
+
const handle: AgentHandle = {
|
|
74
|
+
id: this.#newHandleId(),
|
|
75
|
+
name: opts.name,
|
|
76
|
+
role: prior?.role ?? "resumed",
|
|
77
|
+
harness: this.name,
|
|
78
|
+
cwd: prior?.cwd ?? process.cwd(),
|
|
79
|
+
startedAt: Date.now(),
|
|
80
|
+
sessionId: opts.sessionId,
|
|
81
|
+
surfaceId: opts.surface?.id,
|
|
82
|
+
};
|
|
83
|
+
this.#records.set(handle.id, {
|
|
84
|
+
handle,
|
|
85
|
+
request: { ...opts },
|
|
86
|
+
state: "working",
|
|
87
|
+
output: [],
|
|
88
|
+
inputs: opts.message ? [opts.message] : [],
|
|
89
|
+
waiters: new Set(),
|
|
90
|
+
});
|
|
91
|
+
this.#rememberSession(handle);
|
|
92
|
+
return { ...handle };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async getState(handle: AgentHandle): Promise<AgentState> {
|
|
96
|
+
return this.#records.get(handle.id)?.state ?? "unknown";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async interrupt(handle: AgentHandle): Promise<void> {
|
|
100
|
+
const record = this.#record(handle.id);
|
|
101
|
+
if (record.state !== "done") record.state = "idle";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async kill(handle: AgentHandle): Promise<void> {
|
|
105
|
+
const record = this.#record(handle.id);
|
|
106
|
+
record.state = "done";
|
|
107
|
+
this.#settle(handle.id, {
|
|
108
|
+
reason: "error",
|
|
109
|
+
exitCode: 143,
|
|
110
|
+
errorMessage: "terminated by kill",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async sendInput(handle: AgentHandle, text: string): Promise<void> {
|
|
115
|
+
this.#record(handle.id).inputs.push(text);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async readOutput(handle: AgentHandle, lines?: number): Promise<string> {
|
|
119
|
+
const output = this.#record(handle.id).output;
|
|
120
|
+
return (lines === undefined ? output : output.slice(-lines)).join("\n");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
exitCode(handle: AgentHandle): number | undefined {
|
|
124
|
+
return this.#records.get(handle.id)?.exitCode;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
awaitCompletion(
|
|
128
|
+
handle: AgentHandle,
|
|
129
|
+
signal: AbortSignal,
|
|
130
|
+
): Promise<CompletionEvidence> {
|
|
131
|
+
const record = this.#record(handle.id);
|
|
132
|
+
if (signal.aborted) return Promise.reject(new Error(ABORT_MESSAGE));
|
|
133
|
+
if (record.completion) return Promise.resolve({ ...record.completion });
|
|
134
|
+
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
const waiter: Waiter = {
|
|
137
|
+
resolve: (evidence) => {
|
|
138
|
+
signal.removeEventListener("abort", waiter.onAbort);
|
|
139
|
+
resolve({ ...evidence });
|
|
140
|
+
},
|
|
141
|
+
reject: (error) => {
|
|
142
|
+
signal.removeEventListener("abort", waiter.onAbort);
|
|
143
|
+
reject(error);
|
|
144
|
+
},
|
|
145
|
+
signal,
|
|
146
|
+
onAbort: () => {
|
|
147
|
+
record.waiters.delete(waiter);
|
|
148
|
+
waiter.reject(new Error(ABORT_MESSAGE));
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
record.waiters.add(waiter);
|
|
152
|
+
signal.addEventListener("abort", waiter.onAbort, { once: true });
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
complete(handleId: string, evidence: CompletionEvidence): void {
|
|
157
|
+
this.#settle(handleId, evidence);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fail(handleId: string, error: string): void {
|
|
161
|
+
this.#settle(handleId, {
|
|
162
|
+
reason: "error",
|
|
163
|
+
exitCode: 1,
|
|
164
|
+
errorMessage: error,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
ping(handleId: string, message: string): void {
|
|
169
|
+
const record = this.#record(handleId);
|
|
170
|
+
this.#settle(handleId, {
|
|
171
|
+
reason: "ping",
|
|
172
|
+
exitCode: 0,
|
|
173
|
+
ping: { name: record.handle.name, message },
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
spawned(): AgentHandle[] {
|
|
178
|
+
return Array.from(this.#records.values(), (record) =>
|
|
179
|
+
cloneHandle(record.handle),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
inputs(handleId: string): string[] {
|
|
184
|
+
return [...this.#record(handleId).inputs];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
request(handleId: string): SpawnOptions | ResumeOptions | undefined {
|
|
188
|
+
const request = this.#records.get(handleId)?.request;
|
|
189
|
+
return request ? cloneRequest(request) : undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
appendOutput(handleId: string, text: string): void {
|
|
193
|
+
this.#record(handleId).output.push(...text.split("\n"));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#settle(handleId: string, evidence: CompletionEvidence): void {
|
|
197
|
+
const record = this.#record(handleId);
|
|
198
|
+
if (record.completion) return;
|
|
199
|
+
const completion = {
|
|
200
|
+
...evidence,
|
|
201
|
+
sessionRef: evidence.sessionRef ?? record.handle.sessionId,
|
|
202
|
+
};
|
|
203
|
+
record.completion = completion;
|
|
204
|
+
record.exitCode = completion.exitCode;
|
|
205
|
+
record.state = "done";
|
|
206
|
+
const waiters = [...record.waiters];
|
|
207
|
+
record.waiters.clear();
|
|
208
|
+
for (const waiter of waiters) waiter.resolve(completion);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
#record(handleId: string): FakeHarnessRecord {
|
|
212
|
+
const record = this.#records.get(handleId);
|
|
213
|
+
if (!record) throw new Error(`unknown handle: ${handleId}`);
|
|
214
|
+
return record;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#newHandleId(): string {
|
|
218
|
+
const id = `fake-agent-${this.#nextHandle}`;
|
|
219
|
+
this.#nextHandle += 1;
|
|
220
|
+
return id;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#rememberSession(handle: AgentHandle): void {
|
|
224
|
+
const handles =
|
|
225
|
+
this.#sessionHandles.get(handle.sessionId) ?? new Set<string>();
|
|
226
|
+
handles.add(handle.id);
|
|
227
|
+
this.#sessionHandles.set(handle.sessionId, handles);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#latestSessionHandle(sessionId: string): AgentHandle | undefined {
|
|
231
|
+
const handles = this.#sessionHandles.get(sessionId);
|
|
232
|
+
if (!handles) return undefined;
|
|
233
|
+
const handleId = Array.from(handles).at(-1);
|
|
234
|
+
return handleId ? this.#records.get(handleId)?.handle : undefined;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function cloneRequest(request: SpawnOptions | ResumeOptions) {
|
|
239
|
+
if ("task" in request) return cloneSpawnOptions(request);
|
|
240
|
+
return {
|
|
241
|
+
...request,
|
|
242
|
+
env: request.env ? { ...request.env } : undefined,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function cloneSpawnOptions(options: SpawnOptions): SpawnOptions {
|
|
247
|
+
return {
|
|
248
|
+
...options,
|
|
249
|
+
role: { ...options.role, allowedTools: [...options.role.allowedTools] },
|
|
250
|
+
worktree: options.worktree ? { ...options.worktree } : undefined,
|
|
251
|
+
worktreeRequest: options.worktreeRequest
|
|
252
|
+
? { ...options.worktreeRequest }
|
|
253
|
+
: undefined,
|
|
254
|
+
env: options.env ? { ...options.env } : undefined,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function cloneHandle(handle: AgentHandle): AgentHandle {
|
|
259
|
+
return {
|
|
260
|
+
...handle,
|
|
261
|
+
worktree: handle.worktree ? { ...handle.worktree } : undefined,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
@@ -12,62 +12,18 @@ import {
|
|
|
12
12
|
isFiniteNumber,
|
|
13
13
|
isPlainObject,
|
|
14
14
|
isString,
|
|
15
|
-
} from "
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
| "input"
|
|
28
|
-
| "before_agent_start"
|
|
29
|
-
| "agent_start"
|
|
30
|
-
| "agent_end"
|
|
31
|
-
| "turn_start"
|
|
32
|
-
| "turn_end"
|
|
33
|
-
| "before_provider_request"
|
|
34
|
-
| "after_provider_response"
|
|
35
|
-
| "message_update"
|
|
36
|
-
| "tool_execution_start"
|
|
37
|
-
| "tool_call"
|
|
38
|
-
| "tool_execution_update"
|
|
39
|
-
| "tool_result"
|
|
40
|
-
| "tool_execution_end"
|
|
41
|
-
| "caller_ping"
|
|
42
|
-
| "subagent_done"
|
|
43
|
-
| "session_shutdown";
|
|
44
|
-
|
|
45
|
-
export interface SubagentActivityState {
|
|
46
|
-
version: 1;
|
|
47
|
-
runningChildId: string;
|
|
48
|
-
createdAt: number;
|
|
49
|
-
updatedAt: number;
|
|
50
|
-
sequence: number;
|
|
51
|
-
latestEvent: SubagentActivityEvent;
|
|
52
|
-
phase: SubagentActivityPhase;
|
|
53
|
-
agentActive: boolean;
|
|
54
|
-
turnActive: boolean;
|
|
55
|
-
providerActive: boolean;
|
|
56
|
-
toolActive: boolean;
|
|
57
|
-
activeScope?: SubagentActivityScope;
|
|
58
|
-
activeSince?: number;
|
|
59
|
-
waitingSince?: number;
|
|
60
|
-
turnIndex?: number;
|
|
61
|
-
messageEventType?: string;
|
|
62
|
-
toolCallId?: string;
|
|
63
|
-
toolName?: string;
|
|
64
|
-
toolStartedAt?: number;
|
|
65
|
-
toolEndedAt?: number;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export type ActivityReadResult =
|
|
69
|
-
| { ok: true; activity: SubagentActivityState }
|
|
70
|
-
| { ok: false; reason: "missing" | "invalid" | "wrong-id"; error?: string };
|
|
15
|
+
} from "../../core/config/type-guards.ts";
|
|
16
|
+
import {
|
|
17
|
+
isSubagentActivityScope,
|
|
18
|
+
projectActivity,
|
|
19
|
+
} from "../../core/activity.ts";
|
|
20
|
+
import type {
|
|
21
|
+
ActivityReadResult,
|
|
22
|
+
SubagentActivityEvent,
|
|
23
|
+
SubagentActivityPhase,
|
|
24
|
+
SubagentActivityScope,
|
|
25
|
+
SubagentActivityState,
|
|
26
|
+
} from "../../core/types.ts";
|
|
71
27
|
|
|
72
28
|
export type SubagentShutdownReason =
|
|
73
29
|
| "quit"
|
|
@@ -106,13 +62,6 @@ const KNOWN_PHASES = new Set<SubagentActivityPhase>([
|
|
|
106
62
|
"waiting",
|
|
107
63
|
"done",
|
|
108
64
|
]);
|
|
109
|
-
const KNOWN_SCOPES = new Set<SubagentActivityScope>([
|
|
110
|
-
"agent",
|
|
111
|
-
"turn",
|
|
112
|
-
"provider",
|
|
113
|
-
"streaming",
|
|
114
|
-
"tool",
|
|
115
|
-
]);
|
|
116
65
|
const KNOWN_EVENTS = new Set<SubagentActivityEvent>([
|
|
117
66
|
"session_start",
|
|
118
67
|
"input",
|
|
@@ -142,12 +91,6 @@ export function getSubagentActivityFile(
|
|
|
142
91
|
return join(artifactDir, "subagent-activity", `${runningChildId}.json`);
|
|
143
92
|
}
|
|
144
93
|
|
|
145
|
-
export function isSubagentActivityScope(
|
|
146
|
-
value: any,
|
|
147
|
-
): value is SubagentActivityScope {
|
|
148
|
-
return isString(value) && KNOWN_SCOPES.has(value);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
94
|
function requireObject(value: any) {
|
|
152
95
|
return isPlainObject(value) ? value : null;
|
|
153
96
|
}
|
|
@@ -225,7 +168,7 @@ function validateActivity(
|
|
|
225
168
|
}
|
|
226
169
|
if (
|
|
227
170
|
object.activeScope != null &&
|
|
228
|
-
|
|
171
|
+
!isSubagentActivityScope(object.activeScope)
|
|
229
172
|
) {
|
|
230
173
|
return invalidActivity("unknown activeScope");
|
|
231
174
|
}
|
|
@@ -249,14 +192,14 @@ function validateActivity(
|
|
|
249
192
|
].find((error) => error != null);
|
|
250
193
|
if (validationError) return invalidActivity(validationError);
|
|
251
194
|
|
|
252
|
-
return
|
|
195
|
+
return projectActivity(object);
|
|
253
196
|
}
|
|
254
197
|
|
|
255
198
|
export function readSubagentActivityFile(
|
|
256
199
|
activityFile: string,
|
|
257
200
|
expectedRunningChildId: string,
|
|
258
201
|
): ActivityReadResult {
|
|
259
|
-
if (!existsSync(activityFile)) return
|
|
202
|
+
if (!existsSync(activityFile)) return projectActivity(undefined);
|
|
260
203
|
|
|
261
204
|
let parsed: unknown;
|
|
262
205
|
try {
|
|
@@ -12,9 +12,13 @@ import {
|
|
|
12
12
|
consumePersistentTaskInbox,
|
|
13
13
|
readPersistentDeliveryLedger,
|
|
14
14
|
readPersistentTaskEvents,
|
|
15
|
-
} from "
|
|
16
|
-
import { createSubagentActivityRecorder } from "
|
|
17
|
-
import {
|
|
15
|
+
} from "../session.ts";
|
|
16
|
+
import { createSubagentActivityRecorder } from "../activity-file.ts";
|
|
17
|
+
import {
|
|
18
|
+
readOwnProcessIdentity,
|
|
19
|
+
writeProcessIdentityRecord,
|
|
20
|
+
} from "../process-identity.ts";
|
|
21
|
+
import { isString } from "../../../core/config/type-guards.ts";
|
|
18
22
|
|
|
19
23
|
export function shouldMarkUserTookOver(agentStarted: boolean): boolean {
|
|
20
24
|
return agentStarted;
|
|
@@ -130,6 +134,28 @@ export function parseDeniedTools(rawValue: string | undefined): string[] {
|
|
|
130
134
|
.filter(Boolean);
|
|
131
135
|
}
|
|
132
136
|
|
|
137
|
+
/**
|
|
138
|
+
* A managed worktree child names its own process at load from immutable
|
|
139
|
+
* kernel facts, which Pi's process-title rewrite cannot alter.
|
|
140
|
+
*/
|
|
141
|
+
export function recordProcessIdentity(
|
|
142
|
+
id: string | undefined,
|
|
143
|
+
sessionFile: string | undefined,
|
|
144
|
+
file = process.env.PI_SUBAGENT_PROCESS_FILE,
|
|
145
|
+
): void {
|
|
146
|
+
if (!file || !id || !sessionFile) return;
|
|
147
|
+
try {
|
|
148
|
+
writeProcessIdentityRecord(file, {
|
|
149
|
+
version: 1,
|
|
150
|
+
id,
|
|
151
|
+
sessionFile,
|
|
152
|
+
...readOwnProcessIdentity(),
|
|
153
|
+
});
|
|
154
|
+
} catch {
|
|
155
|
+
// Unrecorded identity leaves a worktree cancel unconfirmed, never guessed.
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
133
159
|
export default function (pi: ExtensionAPI) {
|
|
134
160
|
let toolNames: string[] = [];
|
|
135
161
|
let denied: string[] = [];
|
|
@@ -157,6 +183,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
157
183
|
runningChildId: process.env.PI_SUBAGENT_ID,
|
|
158
184
|
activityFile: process.env.PI_SUBAGENT_ACTIVITY_FILE,
|
|
159
185
|
});
|
|
186
|
+
recordProcessIdentity(process.env.PI_SUBAGENT_ID, sessionFile);
|
|
160
187
|
|
|
161
188
|
function renderWidget(ctx: { ui: { setWidget: Function } }, _theme: any) {
|
|
162
189
|
ctx.ui.setWidget(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
2
2
|
|
|
3
|
-
import { isNonEmptyString, isString } from "
|
|
3
|
+
import { isNonEmptyString, isString } from "../../core/config/type-guards.ts";
|
|
4
4
|
|
|
5
5
|
const ABORT_MESSAGE = "Aborted while waiting for subagent to finish";
|
|
6
6
|
const TERMINAL_SENTINEL = /__SUBAGENT_DONE_(\d+)__/;
|
|
@@ -15,11 +15,11 @@ export interface CompletionResult {
|
|
|
15
15
|
export interface CompletionOptions {
|
|
16
16
|
intervalMs: number;
|
|
17
17
|
readTerminalTail: () => Promise<string>;
|
|
18
|
-
inspectPane?: () => Promise<import("
|
|
18
|
+
inspectPane?: () => Promise<import("../../core/types.ts").PaneInspection>;
|
|
19
19
|
/** Bounded artifact grace after explicit pane disappearance. Default: 500ms. */
|
|
20
20
|
paneDisappearanceGraceMs?: number;
|
|
21
21
|
onPaneInspection?: (
|
|
22
|
-
inspection: import("
|
|
22
|
+
inspection: import("../../core/types.ts").PaneInspection,
|
|
23
23
|
observedAt: number,
|
|
24
24
|
) => void;
|
|
25
25
|
sessionFile?: string;
|
|
@@ -168,7 +168,7 @@ export async function waitForCompletion(
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
if (reconcile && options.inspectPane) {
|
|
171
|
-
let inspection: import("
|
|
171
|
+
let inspection: import("../../core/types.ts").PaneInspection;
|
|
172
172
|
try {
|
|
173
173
|
inspection = await options.inspectPane();
|
|
174
174
|
} catch {
|