@narumitw/pi-subagents 1.0.0 → 1.0.2
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 +12 -4
- package/package.json +7 -7
- package/src/automation-registration.ts +137 -0
- package/src/automation-tool.ts +40 -0
- package/src/automation.ts +6 -156
- package/src/cached-module-loader.ts +18 -0
- package/src/config-registration.ts +114 -0
- package/src/config-ui.ts +2 -2
- package/src/consult-registration.ts +132 -0
- package/src/consult-render.ts +1 -1
- package/src/consult-tool.ts +95 -0
- package/src/consult.ts +37 -204
- package/src/create-stateful-transport.ts +106 -18
- package/src/inspect-registration.ts +64 -0
- package/src/inspect-tool.ts +43 -0
- package/src/inspect.ts +7 -69
- package/src/panel-planning.ts +2 -2
- package/src/panel-presets.ts +3 -0
- package/src/params.ts +2 -2
- package/src/pi-args.ts +41 -0
- package/src/render.ts +4 -47
- package/src/runner-outcome.ts +31 -0
- package/src/runner.ts +18 -79
- package/src/settings.ts +4 -1
- package/src/stateful.ts +254 -98
- package/src/subagents.ts +82 -29
- package/src/usage-format.ts +42 -0
- package/src/verified-execution-contract.ts +3 -31
- package/src/verified-execution-schema.ts +32 -0
package/src/stateful.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { randomUUID } from "node:crypto";
|
|
|
7
7
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
8
8
|
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { Type } from "typebox";
|
|
10
|
-
import { discoverAgents } from "./agents/discovery.js";
|
|
11
10
|
import {
|
|
12
11
|
type AgentScope,
|
|
13
12
|
type CompletionDelivery,
|
|
@@ -17,17 +16,10 @@ import {
|
|
|
17
16
|
type SubagentTransportKind,
|
|
18
17
|
THINKING_LEVELS,
|
|
19
18
|
} from "./agents/types.js";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
assertDelegationTargetAllowed,
|
|
26
|
-
resolveSubagentTarget,
|
|
27
|
-
targetPolicyAudit,
|
|
28
|
-
} from "./cwd-policy.js";
|
|
29
|
-
import { DelegationContractSchema, normalizeDelegationContract } from "./delegation-contract.js";
|
|
30
|
-
import { assertSubagentDepthAllowed } from "./execution/runtime-policy.js";
|
|
19
|
+
import type { CompletionDeliveryBroker } from "./completion-delivery.js";
|
|
20
|
+
import type { ContextMode } from "./context.js";
|
|
21
|
+
import type { CreateStatefulTransportOptions } from "./create-stateful-transport.js";
|
|
22
|
+
import { DelegationContractSchema } from "./delegation-contract.js";
|
|
31
23
|
import type { ChildSessionFactory, ParentRuntimeSnapshot } from "./in-process-transport.js";
|
|
32
24
|
import {
|
|
33
25
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
@@ -35,32 +27,24 @@ import {
|
|
|
35
27
|
MAX_TOOL_MESSAGE_BYTES,
|
|
36
28
|
truncateUtf8,
|
|
37
29
|
} from "./limits.js";
|
|
38
|
-
import { AgentPersistence } from "./persistence.js";
|
|
39
|
-
import {
|
|
30
|
+
import type { AgentPersistence } from "./persistence.js";
|
|
31
|
+
import type {
|
|
40
32
|
AgentRegistry,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
AgentRunInspectionDetail,
|
|
34
|
+
AgentRunInspectionSummary,
|
|
35
|
+
ManagedAgent,
|
|
44
36
|
} from "./registry.js";
|
|
45
37
|
import { SUBAGENT_RESULT_FORMATS, type SubagentResultFormat } from "./result-contract.js";
|
|
46
|
-
import { buildRetainedSemanticState } from "./retained-semantic-state.js";
|
|
47
|
-
import { evaluateSemanticCompatibility } from "./semantic-snapshot.js";
|
|
48
38
|
import { DEFAULT_DELEGATION_CWD_POLICY } from "./settings/inspection.js";
|
|
49
39
|
import { readSubagentSettings } from "./settings.js";
|
|
50
40
|
import {
|
|
51
41
|
assertSpawnIdempotencyKey,
|
|
52
|
-
hashSpawnRequest,
|
|
53
42
|
MAX_SPAWN_IDEMPOTENCY_KEY_LENGTH,
|
|
54
43
|
} from "./spawn-idempotency.js";
|
|
55
44
|
import { summarizeStatefulAgent } from "./stateful-agent-view.js";
|
|
56
45
|
import { resolveCompletionDelivery, resolveStatefulTransportKind } from "./stateful-config.js";
|
|
57
46
|
import { createSpawnPromptGuidelines } from "./stateful-guidance.js";
|
|
58
|
-
import {
|
|
59
|
-
assertCurrentSpawn,
|
|
60
|
-
cleanupPersistedWorkspaces,
|
|
61
|
-
disposeStatefulRuntime,
|
|
62
|
-
waitForOwnedSpawn,
|
|
63
|
-
} from "./stateful-lifecycle.js";
|
|
47
|
+
import { assertCurrentSpawn, waitForOwnedSpawn } from "./stateful-lifecycle.js";
|
|
64
48
|
import { resolveStatefulLimits, type StatefulLimits } from "./stateful-limits.js";
|
|
65
49
|
import { createStatefulToolRenderer } from "./stateful-render.js";
|
|
66
50
|
import {
|
|
@@ -82,7 +66,112 @@ import {
|
|
|
82
66
|
validateMailboxParams,
|
|
83
67
|
validateManageParams,
|
|
84
68
|
} from "./stateful-tool-params.js";
|
|
85
|
-
import { WorkspaceManager } from "./workspace.js";
|
|
69
|
+
import type { WorkspaceManager } from "./workspace.js";
|
|
70
|
+
|
|
71
|
+
type CwdPolicyModule = typeof import("./cwd-policy.js");
|
|
72
|
+
type StateLifecycleModule = typeof import("./stateful-lifecycle.js");
|
|
73
|
+
|
|
74
|
+
type StatefulSessionModules = {
|
|
75
|
+
broker: typeof import("./completion-delivery.js");
|
|
76
|
+
context: typeof import("./context.js");
|
|
77
|
+
transport: typeof import("./create-stateful-transport.js");
|
|
78
|
+
cwdPolicy: CwdPolicyModule;
|
|
79
|
+
persistence: typeof import("./persistence.js");
|
|
80
|
+
registry: typeof import("./registry.js");
|
|
81
|
+
lifecycle: StateLifecycleModule;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
type StatefulSpawnModules = {
|
|
85
|
+
agents: typeof import("./agents/discovery.js");
|
|
86
|
+
capabilityGrant: typeof import("./capability-grant.js");
|
|
87
|
+
context: typeof import("./context.js");
|
|
88
|
+
cwdPolicy: CwdPolicyModule;
|
|
89
|
+
delegationContract: typeof import("./delegation-contract.js");
|
|
90
|
+
runtimePolicy: typeof import("./execution/runtime-policy.js");
|
|
91
|
+
retainedSemanticState: typeof import("./retained-semantic-state.js");
|
|
92
|
+
semanticSnapshot: typeof import("./semantic-snapshot.js");
|
|
93
|
+
spawnIdempotency: typeof import("./spawn-idempotency.js");
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
let statefulSessionModules: Promise<StatefulSessionModules> | undefined;
|
|
97
|
+
let statefulSpawnModules: Promise<StatefulSpawnModules> | undefined;
|
|
98
|
+
let workspaceModule: Promise<typeof import("./workspace.js")> | undefined;
|
|
99
|
+
|
|
100
|
+
function loadStatefulSessionModules(): Promise<StatefulSessionModules> {
|
|
101
|
+
statefulSessionModules ??= Promise.all([
|
|
102
|
+
import("./completion-delivery.js"),
|
|
103
|
+
import("./context.js"),
|
|
104
|
+
import("./create-stateful-transport.js"),
|
|
105
|
+
import("./cwd-policy.js"),
|
|
106
|
+
import("./persistence.js"),
|
|
107
|
+
import("./registry.js"),
|
|
108
|
+
import("./stateful-lifecycle.js"),
|
|
109
|
+
])
|
|
110
|
+
.then(([broker, context, transport, cwdPolicy, persistence, registry, lifecycle]) => ({
|
|
111
|
+
broker,
|
|
112
|
+
context,
|
|
113
|
+
transport,
|
|
114
|
+
cwdPolicy,
|
|
115
|
+
persistence,
|
|
116
|
+
registry,
|
|
117
|
+
lifecycle,
|
|
118
|
+
}))
|
|
119
|
+
.catch((error: unknown) => {
|
|
120
|
+
statefulSessionModules = undefined;
|
|
121
|
+
throw error;
|
|
122
|
+
});
|
|
123
|
+
return statefulSessionModules;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function loadStatefulSpawnModules(): Promise<StatefulSpawnModules> {
|
|
127
|
+
statefulSpawnModules ??= Promise.all([
|
|
128
|
+
import("./agents/discovery.js"),
|
|
129
|
+
import("./capability-grant.js"),
|
|
130
|
+
import("./context.js"),
|
|
131
|
+
import("./cwd-policy.js"),
|
|
132
|
+
import("./delegation-contract.js"),
|
|
133
|
+
import("./execution/runtime-policy.js"),
|
|
134
|
+
import("./retained-semantic-state.js"),
|
|
135
|
+
import("./semantic-snapshot.js"),
|
|
136
|
+
import("./spawn-idempotency.js"),
|
|
137
|
+
])
|
|
138
|
+
.then(
|
|
139
|
+
([
|
|
140
|
+
agents,
|
|
141
|
+
capabilityGrant,
|
|
142
|
+
context,
|
|
143
|
+
cwdPolicy,
|
|
144
|
+
delegationContract,
|
|
145
|
+
runtimePolicy,
|
|
146
|
+
retainedSemanticState,
|
|
147
|
+
semanticSnapshot,
|
|
148
|
+
spawnIdempotency,
|
|
149
|
+
]) => ({
|
|
150
|
+
agents,
|
|
151
|
+
capabilityGrant,
|
|
152
|
+
context,
|
|
153
|
+
cwdPolicy,
|
|
154
|
+
delegationContract,
|
|
155
|
+
runtimePolicy,
|
|
156
|
+
retainedSemanticState,
|
|
157
|
+
semanticSnapshot,
|
|
158
|
+
spawnIdempotency,
|
|
159
|
+
}),
|
|
160
|
+
)
|
|
161
|
+
.catch((error: unknown) => {
|
|
162
|
+
statefulSpawnModules = undefined;
|
|
163
|
+
throw error;
|
|
164
|
+
});
|
|
165
|
+
return statefulSpawnModules;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function loadWorkspaceModule(): Promise<typeof import("./workspace.js")> {
|
|
169
|
+
workspaceModule ??= import("./workspace.js").catch((error: unknown) => {
|
|
170
|
+
workspaceModule = undefined;
|
|
171
|
+
throw error;
|
|
172
|
+
});
|
|
173
|
+
return workspaceModule;
|
|
174
|
+
}
|
|
86
175
|
|
|
87
176
|
const ContextModeSchema = Type.Union([
|
|
88
177
|
StringEnum(["none", "all", "summary"] as const),
|
|
@@ -133,6 +222,7 @@ export interface StatefulSubagentDependencies {
|
|
|
133
222
|
workspaceManager?: WorkspaceManager;
|
|
134
223
|
settings?: SubagentRuntimeSettings;
|
|
135
224
|
getSettings?: () => SubagentSettings | undefined;
|
|
225
|
+
loadTransport?: CreateStatefulTransportOptions["loadTransport"];
|
|
136
226
|
}
|
|
137
227
|
|
|
138
228
|
export interface StatefulSubagentRuntimeStatus {
|
|
@@ -182,7 +272,13 @@ export function registerStatefulSubagents(
|
|
|
182
272
|
let sweepTimer: NodeJS.Timeout | undefined;
|
|
183
273
|
let runtimeGeneration = 0;
|
|
184
274
|
let runtimeTransition: Promise<void> = Promise.resolve();
|
|
185
|
-
|
|
275
|
+
let workspaceManager = dependencies.workspaceManager;
|
|
276
|
+
const getWorkspaceManager = async () => {
|
|
277
|
+
if (workspaceManager) return workspaceManager;
|
|
278
|
+
const { WorkspaceManager } = await loadWorkspaceModule();
|
|
279
|
+
workspaceManager = new WorkspaceManager();
|
|
280
|
+
return workspaceManager;
|
|
281
|
+
};
|
|
186
282
|
const isolatedAgents = new Map<string, string>();
|
|
187
283
|
const seenMessageIds = new Set<string>();
|
|
188
284
|
type PendingIdempotentSpawn = {
|
|
@@ -202,11 +298,12 @@ export function registerStatefulSubagents(
|
|
|
202
298
|
const currentRegistry = registry;
|
|
203
299
|
const currentPersistence = persistence;
|
|
204
300
|
if (!currentRegistry) return 0;
|
|
301
|
+
const currentWorkspaceManager = await getWorkspaceManager();
|
|
205
302
|
const count = currentRegistry.list().length;
|
|
206
303
|
const clear = async () => {
|
|
207
304
|
await currentRegistry.closeAll();
|
|
208
305
|
if (generation !== runtimeGeneration) return;
|
|
209
|
-
await
|
|
306
|
+
await currentWorkspaceManager.cleanupAll();
|
|
210
307
|
isolatedAgents.clear();
|
|
211
308
|
seenMessageIds.clear();
|
|
212
309
|
await currentPersistence?.delete();
|
|
@@ -273,7 +370,12 @@ export function registerStatefulSubagents(
|
|
|
273
370
|
seenMessageIds.clear();
|
|
274
371
|
pendingIdempotentSpawns.clear();
|
|
275
372
|
const initialize = async () => {
|
|
276
|
-
const
|
|
373
|
+
const currentWorkspaceManager = await getWorkspaceManager();
|
|
374
|
+
const modules = await loadStatefulSessionModules();
|
|
375
|
+
const cleanupErrors = await modules.lifecycle.disposeStatefulRuntime(
|
|
376
|
+
previousRegistry,
|
|
377
|
+
currentWorkspaceManager,
|
|
378
|
+
);
|
|
277
379
|
if (generation !== runtimeGeneration) return;
|
|
278
380
|
if (cleanupErrors.length > 0 && ctx.hasUI) {
|
|
279
381
|
ctx.ui.notify(
|
|
@@ -289,38 +391,44 @@ export function registerStatefulSubagents(
|
|
|
289
391
|
ctx.sessionManager.getSessionId?.() ??
|
|
290
392
|
ctx.sessionManager.getSessionFile?.() ??
|
|
291
393
|
`ephemeral:${ctx.cwd}`;
|
|
292
|
-
const sessionPersistence = new AgentPersistence(owner, {
|
|
394
|
+
const sessionPersistence = new modules.persistence.AgentPersistence(owner, {
|
|
293
395
|
retentionDays: sessionSettings.retentionDays,
|
|
294
396
|
maxStoredAgents: nextLimits.maxStoredAgents,
|
|
295
397
|
});
|
|
296
398
|
let nextRegistry: AgentRegistry;
|
|
297
|
-
const sessionBroker = new CompletionDeliveryBroker(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
399
|
+
const sessionBroker = new modules.broker.CompletionDeliveryBroker(
|
|
400
|
+
pi,
|
|
401
|
+
ctx,
|
|
402
|
+
completionDelivery,
|
|
403
|
+
{
|
|
404
|
+
onDeliveryError: (error) => {
|
|
405
|
+
if (!ctx.hasUI) return;
|
|
406
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
407
|
+
ctx.ui.notify(`Subagent completion delivery failed: ${reason}`, "warning");
|
|
408
|
+
},
|
|
409
|
+
onAcknowledged: (completions, deliveredAt) => {
|
|
410
|
+
if (generation !== runtimeGeneration) return;
|
|
411
|
+
for (const completion of completions) {
|
|
412
|
+
void nextRegistry
|
|
413
|
+
.markCompletionDelivered(completion.completionId, deliveredAt)
|
|
414
|
+
.catch((error: unknown) => {
|
|
415
|
+
if (!ctx.hasUI || generation !== runtimeGeneration) return;
|
|
416
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
417
|
+
ctx.ui.notify(`Subagent completion acknowledgement failed: ${reason}`, "warning");
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
},
|
|
314
421
|
},
|
|
315
|
-
|
|
316
|
-
const transport = createStatefulTransport({
|
|
422
|
+
);
|
|
423
|
+
const transport = modules.transport.createStatefulTransport({
|
|
317
424
|
kind: transportKind,
|
|
318
425
|
modelRegistry: ctx.modelRegistry,
|
|
319
426
|
getParentRuntime: () => ({ ...parentRuntime }),
|
|
320
427
|
getSettings: getCurrentSettings,
|
|
321
428
|
createInProcessSession: dependencies.createInProcessSession,
|
|
429
|
+
loadTransport: dependencies.loadTransport,
|
|
322
430
|
});
|
|
323
|
-
nextRegistry = new AgentRegistry(transport, {
|
|
431
|
+
nextRegistry = new modules.registry.AgentRegistry(transport, {
|
|
324
432
|
maxAgents: nextLimits.maxAgents,
|
|
325
433
|
maxActiveTurns: nextLimits.maxActiveTurns,
|
|
326
434
|
maxDepth: nextLimits.maxDepth,
|
|
@@ -339,7 +447,7 @@ export function registerStatefulSubagents(
|
|
|
339
447
|
pi.appendEntry("pi-subagent-message", {
|
|
340
448
|
senderId: message.senderId,
|
|
341
449
|
recipientId: message.recipientId,
|
|
342
|
-
content: redactPrivateText(message.content).slice(0, 160),
|
|
450
|
+
content: modules.context.redactPrivateText(message.content).slice(0, 160),
|
|
343
451
|
});
|
|
344
452
|
}
|
|
345
453
|
}
|
|
@@ -349,7 +457,10 @@ export function registerStatefulSubagents(
|
|
|
349
457
|
},
|
|
350
458
|
});
|
|
351
459
|
const persisted = sessionPersistence.load();
|
|
352
|
-
const orphanCleanupFailures = await cleanupPersistedWorkspaces(
|
|
460
|
+
const orphanCleanupFailures = await modules.lifecycle.cleanupPersistedWorkspaces(
|
|
461
|
+
persisted,
|
|
462
|
+
currentWorkspaceManager,
|
|
463
|
+
);
|
|
353
464
|
if (ctx.hasUI && orphanCleanupFailures > 0) {
|
|
354
465
|
ctx.ui.notify("Some orphaned subagent worktrees could not be cleaned", "warning");
|
|
355
466
|
}
|
|
@@ -362,12 +473,14 @@ export function registerStatefulSubagents(
|
|
|
362
473
|
)
|
|
363
474
|
.flatMap((agent) => {
|
|
364
475
|
try {
|
|
365
|
-
const target = resolveSubagentTarget({
|
|
476
|
+
const target = modules.cwdPolicy.resolveSubagentTarget({
|
|
366
477
|
workspace: ctx.cwd,
|
|
367
478
|
requestedCwd: agent.cwd,
|
|
368
479
|
currentProjectTrusted: ctx.isProjectTrusted(),
|
|
369
480
|
});
|
|
370
|
-
return [
|
|
481
|
+
return [
|
|
482
|
+
{ ...agent, cwd: target.cwd, target: modules.cwdPolicy.targetPolicyAudit(target) },
|
|
483
|
+
];
|
|
371
484
|
} catch {
|
|
372
485
|
return [];
|
|
373
486
|
}
|
|
@@ -378,7 +491,7 @@ export function registerStatefulSubagents(
|
|
|
378
491
|
nextRegistry.restore(restored);
|
|
379
492
|
if (generation !== runtimeGeneration) {
|
|
380
493
|
sessionBroker.close();
|
|
381
|
-
await disposeStatefulRuntime(nextRegistry,
|
|
494
|
+
await modules.lifecycle.disposeStatefulRuntime(nextRegistry, currentWorkspaceManager);
|
|
382
495
|
return;
|
|
383
496
|
}
|
|
384
497
|
registry = nextRegistry;
|
|
@@ -440,7 +553,9 @@ export function registerStatefulSubagents(
|
|
|
440
553
|
seenMessageIds.clear();
|
|
441
554
|
pendingIdempotentSpawns.clear();
|
|
442
555
|
const shutdown = async () => {
|
|
443
|
-
const
|
|
556
|
+
const currentWorkspaceManager = await getWorkspaceManager();
|
|
557
|
+
const { disposeStatefulRuntime } = await import("./stateful-lifecycle.js");
|
|
558
|
+
const errors = await disposeStatefulRuntime(previousRegistry, currentWorkspaceManager);
|
|
444
559
|
if (errors.length > 0 && ctx.hasUI) {
|
|
445
560
|
ctx.ui.notify(`Subagent shutdown cleanup reported ${errors.length} error(s).`, "warning");
|
|
446
561
|
}
|
|
@@ -497,30 +612,47 @@ export function registerStatefulSubagents(
|
|
|
497
612
|
}),
|
|
498
613
|
...createStatefulToolRenderer("spawn"),
|
|
499
614
|
async execute(_id, params, signal, _update, ctx) {
|
|
615
|
+
const generation = runtimeGeneration;
|
|
616
|
+
const capturedRegistry = registry;
|
|
617
|
+
let modules: StatefulSpawnModules;
|
|
618
|
+
try {
|
|
619
|
+
modules = await loadStatefulSpawnModules();
|
|
620
|
+
} catch (error) {
|
|
621
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
622
|
+
throw error;
|
|
623
|
+
}
|
|
624
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
625
|
+
let currentWorkspaceManager: WorkspaceManager;
|
|
626
|
+
try {
|
|
627
|
+
currentWorkspaceManager = await getWorkspaceManager();
|
|
628
|
+
} catch (error) {
|
|
629
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
630
|
+
throw error;
|
|
631
|
+
}
|
|
632
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
500
633
|
const scope = (params.agentScope ?? "user") as AgentScope;
|
|
501
634
|
const resultFormat = (params.resultFormat ?? "text") as SubagentResultFormat;
|
|
502
|
-
const contract = normalizeDelegationContract(params.contract);
|
|
635
|
+
const contract = modules.delegationContract.normalizeDelegationContract(params.contract);
|
|
503
636
|
if (params.contract !== undefined && !contract) {
|
|
504
637
|
throw new Error(
|
|
505
638
|
"subagent_spawn contract must be a valid pi-subagents:delegation:v2 object",
|
|
506
639
|
);
|
|
507
640
|
}
|
|
508
|
-
assertSubagentDepthAllowed();
|
|
641
|
+
modules.runtimePolicy.assertSubagentDepthAllowed();
|
|
509
642
|
assertSpawnIdempotencyKey(params.idempotencyKey);
|
|
510
|
-
const generation = runtimeGeneration;
|
|
511
643
|
const currentSettings = getCurrentSettings();
|
|
512
|
-
const target = resolveSubagentTarget({
|
|
644
|
+
const target = modules.cwdPolicy.resolveSubagentTarget({
|
|
513
645
|
workspace: ctx.cwd,
|
|
514
646
|
requestedCwd: params.cwd,
|
|
515
647
|
currentProjectTrusted: ctx.isProjectTrusted(),
|
|
516
648
|
});
|
|
517
|
-
assertDelegationTargetAllowed(
|
|
649
|
+
modules.cwdPolicy.assertDelegationTargetAllowed(
|
|
518
650
|
target,
|
|
519
651
|
currentSettings?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY,
|
|
520
652
|
);
|
|
521
653
|
const cwd = target.cwd;
|
|
522
654
|
const mode = resolveSpawnContextMode(params.context, params.contextEntryIds);
|
|
523
|
-
const snapshot = buildContextSnapshot(
|
|
655
|
+
const snapshot = modules.context.buildContextSnapshot(
|
|
524
656
|
ctx.sessionManager.getBranch(),
|
|
525
657
|
mode,
|
|
526
658
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
@@ -529,27 +661,28 @@ export function registerStatefulSubagents(
|
|
|
529
661
|
if ((scope === "project" || scope === "both") && !ctx.isProjectTrusted()) {
|
|
530
662
|
throw new Error("Project-local subagent definitions require a trusted project");
|
|
531
663
|
}
|
|
532
|
-
const resolvedAgents = discoverAgents(cwd, scope, currentSettings).agents;
|
|
664
|
+
const resolvedAgents = modules.agents.discoverAgents(cwd, scope, currentSettings).agents;
|
|
533
665
|
const resolvedAgent = resolvedAgents.find((agent) => agent.name === params.agent);
|
|
534
666
|
if (!resolvedAgent) {
|
|
535
667
|
const available = resolvedAgents.map((agent) => agent.name).join(", ") || "none";
|
|
536
668
|
throw new Error(`Unknown subagent ${params.agent}. Available agents: ${available}`);
|
|
537
669
|
}
|
|
538
|
-
const targetSnapshot = targetPolicyAudit(target);
|
|
539
|
-
const { executionPlan, semanticSnapshot } =
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
670
|
+
const targetSnapshot = modules.cwdPolicy.targetPolicyAudit(target);
|
|
671
|
+
const { executionPlan, semanticSnapshot } =
|
|
672
|
+
await modules.retainedSemanticState.buildRetainedSemanticState({
|
|
673
|
+
agent: resolvedAgent,
|
|
674
|
+
contract,
|
|
675
|
+
target: targetSnapshot,
|
|
676
|
+
cwd,
|
|
677
|
+
workspaceMode: params.workspaceMode === "worktree" ? "worktree" : "shared",
|
|
678
|
+
transport: transportKind,
|
|
679
|
+
resultFormat,
|
|
680
|
+
thinkingLevel: params.thinkingLevel,
|
|
681
|
+
timeoutMs: params.timeoutMs,
|
|
682
|
+
taskGeneration: 1,
|
|
683
|
+
});
|
|
551
684
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
552
|
-
const requestHash = hashSpawnRequest({
|
|
685
|
+
const requestHash = modules.spawnIdempotency.hashSpawnRequest({
|
|
553
686
|
agent: params.agent,
|
|
554
687
|
task: params.task,
|
|
555
688
|
cwd,
|
|
@@ -567,7 +700,10 @@ export function registerStatefulSubagents(
|
|
|
567
700
|
contract,
|
|
568
701
|
resultFormat,
|
|
569
702
|
});
|
|
570
|
-
|
|
703
|
+
if (!capturedRegistry) {
|
|
704
|
+
throw new Error("Stateful subagents are not initialized for this session");
|
|
705
|
+
}
|
|
706
|
+
const ownedRegistry = capturedRegistry;
|
|
571
707
|
const retained = ownedRegistry.findBySpawnIdempotencyKey(params.idempotencyKey, requestHash);
|
|
572
708
|
if (retained) return result(retained, `Reused ${retained.agent} as ${retained.id}.`);
|
|
573
709
|
const foundPending = params.idempotencyKey
|
|
@@ -628,17 +764,17 @@ export function registerStatefulSubagents(
|
|
|
628
764
|
const workspaceOwner = `pending-${randomUUID()}`;
|
|
629
765
|
const workspace =
|
|
630
766
|
params.workspaceMode === "worktree"
|
|
631
|
-
? await
|
|
767
|
+
? await currentWorkspaceManager.create(workspaceOwner, requestedCwd)
|
|
632
768
|
: undefined;
|
|
633
769
|
try {
|
|
634
770
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
635
771
|
} catch (error) {
|
|
636
|
-
if (workspace) await
|
|
772
|
+
if (workspace) await currentWorkspaceManager.cleanup(workspaceOwner);
|
|
637
773
|
throw error;
|
|
638
774
|
}
|
|
639
775
|
let agent: ManagedAgent | undefined;
|
|
640
776
|
try {
|
|
641
|
-
const capabilityGrant = issueCapabilityGrant(
|
|
777
|
+
const capabilityGrant = modules.capabilityGrant.issueCapabilityGrant(
|
|
642
778
|
executionPlan,
|
|
643
779
|
Date.now(),
|
|
644
780
|
Math.max(1, (params.timeoutMs ?? resolvedAgent.timeoutMs ?? 600_000) + 60_000),
|
|
@@ -673,12 +809,12 @@ export function registerStatefulSubagents(
|
|
|
673
809
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
674
810
|
} catch (error) {
|
|
675
811
|
if (agent) await ownedRegistry.closeTree(agent.id).catch(() => undefined);
|
|
676
|
-
if (workspace) await
|
|
812
|
+
if (workspace) await currentWorkspaceManager.cleanup(workspaceOwner);
|
|
677
813
|
throw error;
|
|
678
814
|
}
|
|
679
815
|
if (!agent) throw new Error("Subagent spawn completed without a retained agent");
|
|
680
816
|
if (workspace && agent.cwd === workspace.path) isolatedAgents.set(agent.id, workspaceOwner);
|
|
681
|
-
else if (workspace) await
|
|
817
|
+
else if (workspace) await currentWorkspaceManager.cleanup(workspaceOwner);
|
|
682
818
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
683
819
|
resolvePending?.(agent);
|
|
684
820
|
const deliveryNote =
|
|
@@ -742,25 +878,31 @@ export function registerStatefulSubagents(
|
|
|
742
878
|
async execute(_id, params, signal, _update, ctx) {
|
|
743
879
|
const generation = runtimeGeneration;
|
|
744
880
|
const ownedRegistry = requireRegistry();
|
|
881
|
+
let modules: StatefulSpawnModules;
|
|
882
|
+
try {
|
|
883
|
+
modules = await loadStatefulSpawnModules();
|
|
884
|
+
} catch (error) {
|
|
885
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
886
|
+
throw error;
|
|
887
|
+
}
|
|
888
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
745
889
|
const currentSettings = getCurrentSettings();
|
|
746
890
|
const existing = ownedRegistry.get(params.agentId);
|
|
747
891
|
if (!existing) throw new Error(`Unknown subagent: ${params.agentId}`);
|
|
748
|
-
const currentAgent =
|
|
749
|
-
existing.cwd,
|
|
750
|
-
|
|
751
|
-
currentSettings,
|
|
752
|
-
).agents.find((agent) => agent.name === existing.agent);
|
|
892
|
+
const currentAgent = modules.agents
|
|
893
|
+
.discoverAgents(existing.cwd, existing.agentScope ?? "user", currentSettings)
|
|
894
|
+
.agents.find((agent) => agent.name === existing.agent);
|
|
753
895
|
if (!currentAgent) throw new Error(`Unknown retained subagent definition: ${existing.agent}`);
|
|
754
896
|
const resolvedFollowUpTarget =
|
|
755
897
|
existing.workspaceMode === "worktree"
|
|
756
898
|
? undefined
|
|
757
|
-
: resolveSubagentTarget({
|
|
899
|
+
: modules.cwdPolicy.resolveSubagentTarget({
|
|
758
900
|
workspace: ctx.cwd,
|
|
759
901
|
requestedCwd: existing.cwd,
|
|
760
902
|
currentProjectTrusted: ctx.isProjectTrusted(),
|
|
761
903
|
});
|
|
762
904
|
if (resolvedFollowUpTarget) {
|
|
763
|
-
assertDelegationTargetAllowed(
|
|
905
|
+
modules.cwdPolicy.assertDelegationTargetAllowed(
|
|
764
906
|
resolvedFollowUpTarget,
|
|
765
907
|
currentSettings?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY,
|
|
766
908
|
);
|
|
@@ -771,9 +913,11 @@ export function registerStatefulSubagents(
|
|
|
771
913
|
const currentTarget =
|
|
772
914
|
existing.workspaceMode === "worktree" && existing.target
|
|
773
915
|
? existing.target
|
|
774
|
-
: targetPolicyAudit(
|
|
916
|
+
: modules.cwdPolicy.targetPolicyAudit(
|
|
917
|
+
resolvedFollowUpTarget as NonNullable<typeof resolvedFollowUpTarget>,
|
|
918
|
+
);
|
|
775
919
|
const { executionPlan: currentPlan, semanticSnapshot: currentSnapshot } =
|
|
776
|
-
await buildRetainedSemanticState({
|
|
920
|
+
await modules.retainedSemanticState.buildRetainedSemanticState({
|
|
777
921
|
agent: currentAgent,
|
|
778
922
|
contract: existing.contract,
|
|
779
923
|
target: currentTarget,
|
|
@@ -793,7 +937,10 @@ export function registerStatefulSubagents(
|
|
|
793
937
|
});
|
|
794
938
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
795
939
|
const compatibility = existing.semanticSnapshot
|
|
796
|
-
? evaluateSemanticCompatibility(
|
|
940
|
+
? modules.semanticSnapshot.evaluateSemanticCompatibility(
|
|
941
|
+
existing.semanticSnapshot,
|
|
942
|
+
currentSnapshot,
|
|
943
|
+
)
|
|
797
944
|
: { status: "warning" as const, changedComponents: ["legacy-missing-snapshot"] };
|
|
798
945
|
if (
|
|
799
946
|
(compatibility.status === "needs-revalidation" || compatibility.status === "rejected") &&
|
|
@@ -819,7 +966,7 @@ export function registerStatefulSubagents(
|
|
|
819
966
|
isolatedAgents.has(existing.id),
|
|
820
967
|
currentSettings,
|
|
821
968
|
);
|
|
822
|
-
const currentGrant = issueCapabilityGrant(
|
|
969
|
+
const currentGrant = modules.capabilityGrant.issueCapabilityGrant(
|
|
823
970
|
currentPlan,
|
|
824
971
|
Date.now(),
|
|
825
972
|
Math.max(1, (params.timeoutMs ?? existing.timeoutMs ?? 600_000) + 60_000),
|
|
@@ -833,6 +980,7 @@ export function registerStatefulSubagents(
|
|
|
833
980
|
? { status: "warning", changedComponents: compatibility.changedComponents }
|
|
834
981
|
: compatibility,
|
|
835
982
|
);
|
|
983
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
836
984
|
const agent = await ownedRegistry.followUp(params.agentId, params.task, {
|
|
837
985
|
timeoutMs: params.timeoutMs,
|
|
838
986
|
idleTimeoutMs: params.idleTimeoutMs,
|
|
@@ -855,6 +1003,14 @@ export function registerStatefulSubagents(
|
|
|
855
1003
|
async execute(_id, params, signal): Promise<StatefulActionToolResult> {
|
|
856
1004
|
const generation = runtimeGeneration;
|
|
857
1005
|
const ownedRegistry = requireRegistry();
|
|
1006
|
+
let currentWorkspaceManager: WorkspaceManager;
|
|
1007
|
+
try {
|
|
1008
|
+
currentWorkspaceManager = await getWorkspaceManager();
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
1011
|
+
throw error;
|
|
1012
|
+
}
|
|
1013
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
858
1014
|
const ownedAgent = (agentId: string): ManagedAgent => {
|
|
859
1015
|
const value = ownedRegistry.get(agentId);
|
|
860
1016
|
if (!value) throw new Error(`Unknown subagent: ${agentId}`);
|
|
@@ -881,7 +1037,7 @@ export function registerStatefulSubagents(
|
|
|
881
1037
|
const existing = ownedRegistry.get(agentId);
|
|
882
1038
|
if (existing?.state === "closed" && !operation.subtree) {
|
|
883
1039
|
const pendingOwner = isolatedAgents.get(existing.id);
|
|
884
|
-
if (pendingOwner) await
|
|
1040
|
+
if (pendingOwner) await currentWorkspaceManager.cleanup(pendingOwner);
|
|
885
1041
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
886
1042
|
isolatedAgents.delete(existing.id);
|
|
887
1043
|
return result(existing, `Closed ${existing.id}.`);
|
|
@@ -891,7 +1047,7 @@ export function registerStatefulSubagents(
|
|
|
891
1047
|
try {
|
|
892
1048
|
agents = await ownedRegistry.closeTree(agentId);
|
|
893
1049
|
} finally {
|
|
894
|
-
await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents,
|
|
1050
|
+
await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents, currentWorkspaceManager);
|
|
895
1051
|
}
|
|
896
1052
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
897
1053
|
return {
|
|
@@ -906,7 +1062,7 @@ export function registerStatefulSubagents(
|
|
|
906
1062
|
try {
|
|
907
1063
|
agent = await ownedRegistry.close(agentId);
|
|
908
1064
|
} finally {
|
|
909
|
-
await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents,
|
|
1065
|
+
await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents, currentWorkspaceManager);
|
|
910
1066
|
}
|
|
911
1067
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
912
1068
|
return result(agent, `Closed ${agent.id}.`);
|