@narumitw/pi-subagents 1.0.1 → 2.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/README.md +198 -188
- package/package.json +8 -8
- package/src/agents/built-ins.ts +13 -66
- package/src/agents/catalog.ts +19 -2
- package/src/agents/discovery.ts +31 -15
- package/src/auto-transport.ts +7 -1
- package/src/child-peer-bridge.ts +124 -0
- package/src/child-peer-tools.ts +132 -0
- package/src/completion-delivery.ts +19 -5
- package/src/completion-render.ts +189 -0
- package/src/completion-routing.ts +24 -0
- package/src/config-registration.ts +29 -4
- package/src/config-ui.ts +11 -17
- package/src/consult-registration.ts +3 -2
- package/src/consult-render.ts +1 -1
- package/src/create-stateful-transport.ts +15 -2
- package/src/execution-ui.ts +0 -72
- package/src/in-process-transport.ts +39 -7
- package/src/inspect-tool.ts +3 -1
- package/src/panel-planning.ts +2 -2
- package/src/panel-presets.ts +3 -0
- package/src/params.ts +1 -1
- package/src/peer-communication.ts +352 -0
- package/src/peer-transport.ts +49 -0
- package/src/persistence.ts +26 -1
- package/src/pi-args.ts +2 -0
- package/src/registry-types.ts +7 -0
- package/src/registry.ts +240 -41
- package/src/render.ts +2 -41
- package/src/result-contract.ts +20 -5
- package/src/rpc-transport.ts +56 -26
- package/src/runner.ts +13 -1
- package/src/settings.ts +4 -1
- package/src/spawn-idempotency.ts +2 -0
- package/src/stateful-agent-view.ts +3 -1
- package/src/stateful-guidance.ts +11 -11
- package/src/stateful-safety.ts +0 -45
- package/src/stateful-tool-params.ts +11 -3
- package/src/stateful.ts +359 -136
- package/src/subagents.ts +7 -9
- package/src/subprocess-transport.ts +49 -28
- package/src/task-path.ts +65 -0
- package/src/transport-ui.ts +0 -6
- package/src/transport.ts +2 -1
- package/src/usage-format.ts +42 -0
- package/src/workflow-ui.ts +4 -4
- package/src/automation-contract.ts +0 -709
- package/src/automation-planner.ts +0 -65
- package/src/automation-registration.ts +0 -137
- package/src/automation-tool.ts +0 -40
- package/src/automation.ts +0 -435
- package/src/execution-profiles.ts +0 -95
- package/src/workflow-plan-compiler.ts +0 -618
- package/src/workflow-plan-patch.ts +0 -636
- package/src/workflow-planning-benchmark.ts +0 -95
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,20 +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
|
-
type CreateStatefulTransportOptions,
|
|
25
|
-
createStatefulTransport,
|
|
26
|
-
} from "./create-stateful-transport.js";
|
|
27
|
-
import {
|
|
28
|
-
assertDelegationTargetAllowed,
|
|
29
|
-
resolveSubagentTarget,
|
|
30
|
-
targetPolicyAudit,
|
|
31
|
-
} from "./cwd-policy.js";
|
|
32
|
-
import { DelegationContractSchema, normalizeDelegationContract } from "./delegation-contract.js";
|
|
33
|
-
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";
|
|
34
23
|
import type { ChildSessionFactory, ParentRuntimeSnapshot } from "./in-process-transport.js";
|
|
35
24
|
import {
|
|
36
25
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
@@ -38,46 +27,31 @@ import {
|
|
|
38
27
|
MAX_TOOL_MESSAGE_BYTES,
|
|
39
28
|
truncateUtf8,
|
|
40
29
|
} from "./limits.js";
|
|
41
|
-
import { AgentPersistence } from "./persistence.js";
|
|
42
|
-
import {
|
|
30
|
+
import type { AgentPersistence } from "./persistence.js";
|
|
31
|
+
import type {
|
|
43
32
|
AgentRegistry,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
AgentRunInspectionDetail,
|
|
34
|
+
AgentRunInspectionSummary,
|
|
35
|
+
ManagedAgent,
|
|
47
36
|
} from "./registry.js";
|
|
48
37
|
import { SUBAGENT_RESULT_FORMATS, type SubagentResultFormat } from "./result-contract.js";
|
|
49
|
-
import { buildRetainedSemanticState } from "./retained-semantic-state.js";
|
|
50
|
-
import { evaluateSemanticCompatibility } from "./semantic-snapshot.js";
|
|
51
38
|
import { DEFAULT_DELEGATION_CWD_POLICY } from "./settings/inspection.js";
|
|
52
39
|
import { readSubagentSettings } from "./settings.js";
|
|
53
40
|
import {
|
|
54
41
|
assertSpawnIdempotencyKey,
|
|
55
|
-
hashSpawnRequest,
|
|
56
42
|
MAX_SPAWN_IDEMPOTENCY_KEY_LENGTH,
|
|
57
43
|
} from "./spawn-idempotency.js";
|
|
58
44
|
import { summarizeStatefulAgent } from "./stateful-agent-view.js";
|
|
59
45
|
import { resolveCompletionDelivery, resolveStatefulTransportKind } from "./stateful-config.js";
|
|
60
46
|
import { createSpawnPromptGuidelines } from "./stateful-guidance.js";
|
|
61
|
-
import {
|
|
62
|
-
assertCurrentSpawn,
|
|
63
|
-
cleanupPersistedWorkspaces,
|
|
64
|
-
disposeStatefulRuntime,
|
|
65
|
-
waitForOwnedSpawn,
|
|
66
|
-
} from "./stateful-lifecycle.js";
|
|
47
|
+
import { assertCurrentSpawn, waitForOwnedSpawn } from "./stateful-lifecycle.js";
|
|
67
48
|
import { resolveStatefulLimits, type StatefulLimits } from "./stateful-limits.js";
|
|
68
49
|
import { createStatefulToolRenderer } from "./stateful-render.js";
|
|
69
|
-
import {
|
|
70
|
-
|
|
71
|
-
assertNoSharedWriteConflict,
|
|
72
|
-
confirmProjectAgent,
|
|
73
|
-
} from "./stateful-safety.js";
|
|
50
|
+
import { confirmProjectAgent } from "./stateful-safety.js";
|
|
51
|
+
import { MAX_TASK_NAME_LENGTH } from "./task-path.js";
|
|
74
52
|
import { MAX_SUBAGENT_TOOL_CALLS, MAX_SUBAGENT_TURNS } from "./turn-budget.js";
|
|
75
53
|
|
|
76
|
-
export {
|
|
77
|
-
assertFollowUpWriteAllowed,
|
|
78
|
-
assertNoSharedWriteConflict,
|
|
79
|
-
isWriteCapable,
|
|
80
|
-
} from "./stateful-safety.js";
|
|
54
|
+
export { isWriteCapable } from "./stateful-safety.js";
|
|
81
55
|
|
|
82
56
|
import {
|
|
83
57
|
MailboxParamsSchema,
|
|
@@ -85,7 +59,126 @@ import {
|
|
|
85
59
|
validateMailboxParams,
|
|
86
60
|
validateManageParams,
|
|
87
61
|
} from "./stateful-tool-params.js";
|
|
88
|
-
import { WorkspaceManager } from "./workspace.js";
|
|
62
|
+
import type { WorkspaceManager } from "./workspace.js";
|
|
63
|
+
|
|
64
|
+
type CwdPolicyModule = typeof import("./cwd-policy.js");
|
|
65
|
+
type StateLifecycleModule = typeof import("./stateful-lifecycle.js");
|
|
66
|
+
|
|
67
|
+
type StatefulSessionModules = {
|
|
68
|
+
broker: typeof import("./completion-delivery.js");
|
|
69
|
+
peerCommunication: typeof import("./peer-communication.js");
|
|
70
|
+
context: typeof import("./context.js");
|
|
71
|
+
transport: typeof import("./create-stateful-transport.js");
|
|
72
|
+
cwdPolicy: CwdPolicyModule;
|
|
73
|
+
persistence: typeof import("./persistence.js");
|
|
74
|
+
registry: typeof import("./registry.js");
|
|
75
|
+
lifecycle: StateLifecycleModule;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type StatefulSpawnModules = {
|
|
79
|
+
agents: typeof import("./agents/discovery.js");
|
|
80
|
+
capabilityGrant: typeof import("./capability-grant.js");
|
|
81
|
+
context: typeof import("./context.js");
|
|
82
|
+
cwdPolicy: CwdPolicyModule;
|
|
83
|
+
delegationContract: typeof import("./delegation-contract.js");
|
|
84
|
+
runtimePolicy: typeof import("./execution/runtime-policy.js");
|
|
85
|
+
retainedSemanticState: typeof import("./retained-semantic-state.js");
|
|
86
|
+
semanticSnapshot: typeof import("./semantic-snapshot.js");
|
|
87
|
+
spawnIdempotency: typeof import("./spawn-idempotency.js");
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
let statefulSessionModules: Promise<StatefulSessionModules> | undefined;
|
|
91
|
+
let statefulSpawnModules: Promise<StatefulSpawnModules> | undefined;
|
|
92
|
+
let workspaceModule: Promise<typeof import("./workspace.js")> | undefined;
|
|
93
|
+
|
|
94
|
+
function loadStatefulSessionModules(): Promise<StatefulSessionModules> {
|
|
95
|
+
statefulSessionModules ??= Promise.all([
|
|
96
|
+
import("./completion-delivery.js"),
|
|
97
|
+
import("./peer-communication.js"),
|
|
98
|
+
import("./context.js"),
|
|
99
|
+
import("./create-stateful-transport.js"),
|
|
100
|
+
import("./cwd-policy.js"),
|
|
101
|
+
import("./persistence.js"),
|
|
102
|
+
import("./registry.js"),
|
|
103
|
+
import("./stateful-lifecycle.js"),
|
|
104
|
+
])
|
|
105
|
+
.then(
|
|
106
|
+
([
|
|
107
|
+
broker,
|
|
108
|
+
peerCommunication,
|
|
109
|
+
context,
|
|
110
|
+
transport,
|
|
111
|
+
cwdPolicy,
|
|
112
|
+
persistence,
|
|
113
|
+
registry,
|
|
114
|
+
lifecycle,
|
|
115
|
+
]) => ({
|
|
116
|
+
broker,
|
|
117
|
+
peerCommunication,
|
|
118
|
+
context,
|
|
119
|
+
transport,
|
|
120
|
+
cwdPolicy,
|
|
121
|
+
persistence,
|
|
122
|
+
registry,
|
|
123
|
+
lifecycle,
|
|
124
|
+
}),
|
|
125
|
+
)
|
|
126
|
+
.catch((error: unknown) => {
|
|
127
|
+
statefulSessionModules = undefined;
|
|
128
|
+
throw error;
|
|
129
|
+
});
|
|
130
|
+
return statefulSessionModules;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function loadStatefulSpawnModules(): Promise<StatefulSpawnModules> {
|
|
134
|
+
statefulSpawnModules ??= Promise.all([
|
|
135
|
+
import("./agents/discovery.js"),
|
|
136
|
+
import("./capability-grant.js"),
|
|
137
|
+
import("./context.js"),
|
|
138
|
+
import("./cwd-policy.js"),
|
|
139
|
+
import("./delegation-contract.js"),
|
|
140
|
+
import("./execution/runtime-policy.js"),
|
|
141
|
+
import("./retained-semantic-state.js"),
|
|
142
|
+
import("./semantic-snapshot.js"),
|
|
143
|
+
import("./spawn-idempotency.js"),
|
|
144
|
+
])
|
|
145
|
+
.then(
|
|
146
|
+
([
|
|
147
|
+
agents,
|
|
148
|
+
capabilityGrant,
|
|
149
|
+
context,
|
|
150
|
+
cwdPolicy,
|
|
151
|
+
delegationContract,
|
|
152
|
+
runtimePolicy,
|
|
153
|
+
retainedSemanticState,
|
|
154
|
+
semanticSnapshot,
|
|
155
|
+
spawnIdempotency,
|
|
156
|
+
]) => ({
|
|
157
|
+
agents,
|
|
158
|
+
capabilityGrant,
|
|
159
|
+
context,
|
|
160
|
+
cwdPolicy,
|
|
161
|
+
delegationContract,
|
|
162
|
+
runtimePolicy,
|
|
163
|
+
retainedSemanticState,
|
|
164
|
+
semanticSnapshot,
|
|
165
|
+
spawnIdempotency,
|
|
166
|
+
}),
|
|
167
|
+
)
|
|
168
|
+
.catch((error: unknown) => {
|
|
169
|
+
statefulSpawnModules = undefined;
|
|
170
|
+
throw error;
|
|
171
|
+
});
|
|
172
|
+
return statefulSpawnModules;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function loadWorkspaceModule(): Promise<typeof import("./workspace.js")> {
|
|
176
|
+
workspaceModule ??= import("./workspace.js").catch((error: unknown) => {
|
|
177
|
+
workspaceModule = undefined;
|
|
178
|
+
throw error;
|
|
179
|
+
});
|
|
180
|
+
return workspaceModule;
|
|
181
|
+
}
|
|
89
182
|
|
|
90
183
|
const ContextModeSchema = Type.Union([
|
|
91
184
|
StringEnum(["none", "all", "summary"] as const),
|
|
@@ -180,13 +273,20 @@ export function registerStatefulSubagents(
|
|
|
180
273
|
let runtimeLimits = resolveStatefulLimits(settings);
|
|
181
274
|
let agentCatalog = "";
|
|
182
275
|
let completionBroker: CompletionDeliveryBroker | undefined;
|
|
276
|
+
let peerBroker: import("./peer-communication.js").PeerCommunicationBroker | undefined;
|
|
183
277
|
let refreshSpawnToolRegistration: (() => void) | undefined;
|
|
184
278
|
let registry: AgentRegistry | undefined;
|
|
185
279
|
let persistence: AgentPersistence | undefined;
|
|
186
280
|
let sweepTimer: NodeJS.Timeout | undefined;
|
|
187
281
|
let runtimeGeneration = 0;
|
|
188
282
|
let runtimeTransition: Promise<void> = Promise.resolve();
|
|
189
|
-
|
|
283
|
+
let workspaceManager = dependencies.workspaceManager;
|
|
284
|
+
const getWorkspaceManager = async () => {
|
|
285
|
+
if (workspaceManager) return workspaceManager;
|
|
286
|
+
const { WorkspaceManager } = await loadWorkspaceModule();
|
|
287
|
+
workspaceManager = new WorkspaceManager();
|
|
288
|
+
return workspaceManager;
|
|
289
|
+
};
|
|
190
290
|
const isolatedAgents = new Map<string, string>();
|
|
191
291
|
const seenMessageIds = new Set<string>();
|
|
192
292
|
type PendingIdempotentSpawn = {
|
|
@@ -206,11 +306,12 @@ export function registerStatefulSubagents(
|
|
|
206
306
|
const currentRegistry = registry;
|
|
207
307
|
const currentPersistence = persistence;
|
|
208
308
|
if (!currentRegistry) return 0;
|
|
309
|
+
const currentWorkspaceManager = await getWorkspaceManager();
|
|
209
310
|
const count = currentRegistry.list().length;
|
|
210
311
|
const clear = async () => {
|
|
211
312
|
await currentRegistry.closeAll();
|
|
212
313
|
if (generation !== runtimeGeneration) return;
|
|
213
|
-
await
|
|
314
|
+
await currentWorkspaceManager.cleanupAll();
|
|
214
315
|
isolatedAgents.clear();
|
|
215
316
|
seenMessageIds.clear();
|
|
216
317
|
await currentPersistence?.delete();
|
|
@@ -268,6 +369,8 @@ export function registerStatefulSubagents(
|
|
|
268
369
|
const generation = ++runtimeGeneration;
|
|
269
370
|
completionBroker?.close();
|
|
270
371
|
completionBroker = undefined;
|
|
372
|
+
const previousPeerBroker = peerBroker;
|
|
373
|
+
peerBroker = undefined;
|
|
271
374
|
if (sweepTimer) clearInterval(sweepTimer);
|
|
272
375
|
sweepTimer = undefined;
|
|
273
376
|
const previousRegistry = registry;
|
|
@@ -277,7 +380,13 @@ export function registerStatefulSubagents(
|
|
|
277
380
|
seenMessageIds.clear();
|
|
278
381
|
pendingIdempotentSpawns.clear();
|
|
279
382
|
const initialize = async () => {
|
|
280
|
-
|
|
383
|
+
await previousPeerBroker?.close();
|
|
384
|
+
const currentWorkspaceManager = await getWorkspaceManager();
|
|
385
|
+
const modules = await loadStatefulSessionModules();
|
|
386
|
+
const cleanupErrors = await modules.lifecycle.disposeStatefulRuntime(
|
|
387
|
+
previousRegistry,
|
|
388
|
+
currentWorkspaceManager,
|
|
389
|
+
);
|
|
281
390
|
if (generation !== runtimeGeneration) return;
|
|
282
391
|
if (cleanupErrors.length > 0 && ctx.hasUI) {
|
|
283
392
|
ctx.ui.notify(
|
|
@@ -293,39 +402,75 @@ export function registerStatefulSubagents(
|
|
|
293
402
|
ctx.sessionManager.getSessionId?.() ??
|
|
294
403
|
ctx.sessionManager.getSessionFile?.() ??
|
|
295
404
|
`ephemeral:${ctx.cwd}`;
|
|
296
|
-
const sessionPersistence = new AgentPersistence(owner, {
|
|
405
|
+
const sessionPersistence = new modules.persistence.AgentPersistence(owner, {
|
|
297
406
|
retentionDays: sessionSettings.retentionDays,
|
|
298
407
|
maxStoredAgents: nextLimits.maxStoredAgents,
|
|
299
408
|
});
|
|
300
409
|
let nextRegistry: AgentRegistry;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
410
|
+
let transport: import("./transport.js").SubagentTransport;
|
|
411
|
+
const sessionBroker = new modules.broker.CompletionDeliveryBroker(
|
|
412
|
+
pi,
|
|
413
|
+
ctx,
|
|
414
|
+
completionDelivery,
|
|
415
|
+
{
|
|
416
|
+
onDeliveryError: (error) => {
|
|
417
|
+
if (!ctx.hasUI) return;
|
|
418
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
419
|
+
ctx.ui.notify(`Subagent completion delivery failed: ${reason}`, "warning");
|
|
420
|
+
},
|
|
421
|
+
onAcknowledged: (completions, deliveredAt) => {
|
|
422
|
+
if (generation !== runtimeGeneration) return;
|
|
423
|
+
for (const completion of completions) {
|
|
424
|
+
void nextRegistry
|
|
425
|
+
.markCompletionDelivered(completion.completionId, deliveredAt)
|
|
426
|
+
.catch((error: unknown) => {
|
|
427
|
+
if (!ctx.hasUI || generation !== runtimeGeneration) return;
|
|
428
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
429
|
+
ctx.ui.notify(`Subagent completion acknowledgement failed: ${reason}`, "warning");
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
},
|
|
306
433
|
},
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
434
|
+
);
|
|
435
|
+
const sessionPeerBroker = new modules.peerCommunication.PeerCommunicationBroker({
|
|
436
|
+
getRegistry: () => nextRegistry,
|
|
437
|
+
sendRoot: ({ message, senderPath }) => {
|
|
438
|
+
pi.appendEntry("pi-subagent-peer-message", {
|
|
439
|
+
messageId: message.id,
|
|
440
|
+
senderId: message.senderId,
|
|
441
|
+
senderPath,
|
|
442
|
+
content: modules.context.redactPrivateText(message.content),
|
|
443
|
+
});
|
|
444
|
+
pi.sendMessage(
|
|
445
|
+
{
|
|
446
|
+
customType: "pi-subagent-peer-message",
|
|
447
|
+
content: [
|
|
448
|
+
"Message Type: SUBAGENT_PEER_MESSAGE",
|
|
449
|
+
"Protocol: pi-subagents:v1",
|
|
450
|
+
`Message ID: ${message.id}`,
|
|
451
|
+
`Sender ID: ${message.senderId}`,
|
|
452
|
+
`Sender Path: ${senderPath}`,
|
|
453
|
+
"Payload:",
|
|
454
|
+
message.content,
|
|
455
|
+
].join("\n"),
|
|
456
|
+
display: true,
|
|
457
|
+
details: { ...message, senderPath },
|
|
458
|
+
},
|
|
459
|
+
{ deliverAs: "steer", triggerTurn: false },
|
|
460
|
+
);
|
|
318
461
|
},
|
|
462
|
+
dispatch: (recipient, message) => transport.deliverMessage?.(recipient, message) ?? false,
|
|
319
463
|
});
|
|
320
|
-
|
|
464
|
+
transport = modules.transport.createStatefulTransport({
|
|
321
465
|
kind: transportKind,
|
|
322
466
|
modelRegistry: ctx.modelRegistry,
|
|
323
467
|
getParentRuntime: () => ({ ...parentRuntime }),
|
|
324
468
|
getSettings: getCurrentSettings,
|
|
325
469
|
createInProcessSession: dependencies.createInProcessSession,
|
|
470
|
+
peerRuntime: sessionPeerBroker,
|
|
326
471
|
loadTransport: dependencies.loadTransport,
|
|
327
472
|
});
|
|
328
|
-
nextRegistry = new AgentRegistry(transport, {
|
|
473
|
+
nextRegistry = new modules.registry.AgentRegistry(transport, {
|
|
329
474
|
maxAgents: nextLimits.maxAgents,
|
|
330
475
|
maxActiveTurns: nextLimits.maxActiveTurns,
|
|
331
476
|
maxDepth: nextLimits.maxDepth,
|
|
@@ -344,17 +489,42 @@ export function registerStatefulSubagents(
|
|
|
344
489
|
pi.appendEntry("pi-subagent-message", {
|
|
345
490
|
senderId: message.senderId,
|
|
346
491
|
recipientId: message.recipientId,
|
|
347
|
-
content: redactPrivateText(message.content).slice(0, 160),
|
|
492
|
+
content: modules.context.redactPrivateText(message.content).slice(0, 160),
|
|
348
493
|
});
|
|
494
|
+
if (
|
|
495
|
+
agent.state === "running" &&
|
|
496
|
+
message.deduplicationKey?.startsWith("completion:")
|
|
497
|
+
) {
|
|
498
|
+
try {
|
|
499
|
+
await transport.deliverMessage?.(agent, {
|
|
500
|
+
...message,
|
|
501
|
+
content: modules.context.redactPrivateText(message.content),
|
|
502
|
+
});
|
|
503
|
+
} catch {
|
|
504
|
+
// The durable parent mailbox remains the retry path for the next turn.
|
|
505
|
+
}
|
|
506
|
+
if (generation !== runtimeGeneration) return;
|
|
507
|
+
}
|
|
349
508
|
}
|
|
350
509
|
}
|
|
351
510
|
},
|
|
352
511
|
onTurnComplete: (completion) => {
|
|
353
|
-
if (generation === runtimeGeneration
|
|
512
|
+
if (generation === runtimeGeneration && completion.recipientId === "root") {
|
|
513
|
+
sessionBroker.enqueue(completion);
|
|
514
|
+
}
|
|
354
515
|
},
|
|
355
516
|
});
|
|
356
517
|
const persisted = sessionPersistence.load();
|
|
357
|
-
const orphanCleanupFailures = await cleanupPersistedWorkspaces(
|
|
518
|
+
const orphanCleanupFailures = await modules.lifecycle.cleanupPersistedWorkspaces(
|
|
519
|
+
persisted,
|
|
520
|
+
currentWorkspaceManager,
|
|
521
|
+
);
|
|
522
|
+
if (generation !== runtimeGeneration) {
|
|
523
|
+
sessionBroker.close();
|
|
524
|
+
await sessionPeerBroker.close();
|
|
525
|
+
await modules.lifecycle.disposeStatefulRuntime(nextRegistry, currentWorkspaceManager);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
358
528
|
if (ctx.hasUI && orphanCleanupFailures > 0) {
|
|
359
529
|
ctx.ui.notify("Some orphaned subagent worktrees could not be cleaned", "warning");
|
|
360
530
|
}
|
|
@@ -367,12 +537,14 @@ export function registerStatefulSubagents(
|
|
|
367
537
|
)
|
|
368
538
|
.flatMap((agent) => {
|
|
369
539
|
try {
|
|
370
|
-
const target = resolveSubagentTarget({
|
|
540
|
+
const target = modules.cwdPolicy.resolveSubagentTarget({
|
|
371
541
|
workspace: ctx.cwd,
|
|
372
542
|
requestedCwd: agent.cwd,
|
|
373
543
|
currentProjectTrusted: ctx.isProjectTrusted(),
|
|
374
544
|
});
|
|
375
|
-
return [
|
|
545
|
+
return [
|
|
546
|
+
{ ...agent, cwd: target.cwd, target: modules.cwdPolicy.targetPolicyAudit(target) },
|
|
547
|
+
];
|
|
376
548
|
} catch {
|
|
377
549
|
return [];
|
|
378
550
|
}
|
|
@@ -383,14 +555,16 @@ export function registerStatefulSubagents(
|
|
|
383
555
|
nextRegistry.restore(restored);
|
|
384
556
|
if (generation !== runtimeGeneration) {
|
|
385
557
|
sessionBroker.close();
|
|
386
|
-
await
|
|
558
|
+
await sessionPeerBroker.close();
|
|
559
|
+
await modules.lifecycle.disposeStatefulRuntime(nextRegistry, currentWorkspaceManager);
|
|
387
560
|
return;
|
|
388
561
|
}
|
|
389
562
|
registry = nextRegistry;
|
|
390
563
|
persistence = sessionPersistence;
|
|
391
564
|
completionBroker = sessionBroker;
|
|
565
|
+
peerBroker = sessionPeerBroker;
|
|
392
566
|
for (const completion of nextRegistry.listPendingCompletions()) {
|
|
393
|
-
sessionBroker.enqueue(completion);
|
|
567
|
+
if (completion.recipientId === "root") sessionBroker.enqueue(completion);
|
|
394
568
|
}
|
|
395
569
|
runtimeLimits = nextLimits;
|
|
396
570
|
refreshSpawnToolRegistration?.();
|
|
@@ -436,6 +610,8 @@ export function registerStatefulSubagents(
|
|
|
436
610
|
runtimeGeneration++;
|
|
437
611
|
completionBroker?.close();
|
|
438
612
|
completionBroker = undefined;
|
|
613
|
+
const previousPeerBroker = peerBroker;
|
|
614
|
+
peerBroker = undefined;
|
|
439
615
|
if (sweepTimer) clearInterval(sweepTimer);
|
|
440
616
|
sweepTimer = undefined;
|
|
441
617
|
const previousRegistry = registry;
|
|
@@ -445,7 +621,10 @@ export function registerStatefulSubagents(
|
|
|
445
621
|
seenMessageIds.clear();
|
|
446
622
|
pendingIdempotentSpawns.clear();
|
|
447
623
|
const shutdown = async () => {
|
|
448
|
-
|
|
624
|
+
await previousPeerBroker?.close();
|
|
625
|
+
const currentWorkspaceManager = await getWorkspaceManager();
|
|
626
|
+
const { disposeStatefulRuntime } = await import("./stateful-lifecycle.js");
|
|
627
|
+
const errors = await disposeStatefulRuntime(previousRegistry, currentWorkspaceManager);
|
|
449
628
|
if (errors.length > 0 && ctx.hasUI) {
|
|
450
629
|
ctx.ui.notify(`Subagent shutdown cleanup reported ${errors.length} error(s).`, "warning");
|
|
451
630
|
}
|
|
@@ -456,7 +635,7 @@ export function registerStatefulSubagents(
|
|
|
456
635
|
});
|
|
457
636
|
|
|
458
637
|
const baseSpawnDescription = () =>
|
|
459
|
-
`Start an addressable background subagent with an optional thinking level and execution budgets chosen for the task difficulty, return immediately with an agentId, and receive its completion asynchronously. Detached capacity: ${runtimeLimits.maxAgents} retained agents, ${runtimeLimits.maxActiveTurns} active turns, ${runtimeLimits.maxChildrenPerAgent} direct children per agent, and depth ${runtimeLimits.maxDepth}. Working-directory target policy: ${dependencies.getSettings?.()?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY}. This controls launch targets and protected project resources, not filesystem access or sandboxing.`;
|
|
638
|
+
`Start an addressable background subagent with an opaque agentId and canonical taskPath, plus an optional thinking level and execution budgets chosen for the task difficulty, return immediately with an agentId, and receive its completion asynchronously. Detached capacity: ${runtimeLimits.maxAgents} retained agents, ${runtimeLimits.maxActiveTurns} active turns, ${runtimeLimits.maxChildrenPerAgent} direct children per agent, and depth ${runtimeLimits.maxDepth}. Working-directory target policy: ${dependencies.getSettings?.()?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY}. This controls launch targets and protected project resources, not filesystem access or sandboxing.`;
|
|
460
639
|
const spawnTool = defineTool({
|
|
461
640
|
name: "subagent_spawn",
|
|
462
641
|
label: "Spawn Subagent",
|
|
@@ -465,6 +644,15 @@ export function registerStatefulSubagents(
|
|
|
465
644
|
promptGuidelines: createSpawnPromptGuidelines(completionDelivery, blockingEnabled),
|
|
466
645
|
parameters: Type.Object({
|
|
467
646
|
agent: Type.String({ minLength: 1 }),
|
|
647
|
+
taskName: Type.Optional(
|
|
648
|
+
Type.String({
|
|
649
|
+
minLength: 1,
|
|
650
|
+
maxLength: MAX_TASK_NAME_LENGTH,
|
|
651
|
+
pattern: "^[a-z0-9_]+$",
|
|
652
|
+
description:
|
|
653
|
+
"Canonical path segment for this task; use lowercase letters, digits, and underscores.",
|
|
654
|
+
}),
|
|
655
|
+
),
|
|
468
656
|
task: Type.String({ minLength: 1, maxLength: DEFAULT_MAX_CONTEXT_BYTES }),
|
|
469
657
|
thinkingLevel: Type.Optional(StatefulThinkingLevelSchema),
|
|
470
658
|
timeoutMs: Type.Optional(StatefulTimeoutSchema),
|
|
@@ -476,13 +664,18 @@ export function registerStatefulSubagents(
|
|
|
476
664
|
contextEntryIds: Type.Optional(
|
|
477
665
|
Type.Array(Type.String(), { description: "Optional selected session entry IDs." }),
|
|
478
666
|
),
|
|
479
|
-
parentId: Type.Optional(
|
|
667
|
+
parentId: Type.Optional(
|
|
668
|
+
Type.String({ description: "Optional parent agent ID or canonical task path." }),
|
|
669
|
+
),
|
|
480
670
|
allowConcurrentWrites: Type.Optional(
|
|
481
|
-
Type.Boolean({
|
|
671
|
+
Type.Boolean({
|
|
672
|
+
description:
|
|
673
|
+
"Deprecated compatibility field; shared-workspace concurrency is allowed by default.",
|
|
674
|
+
}),
|
|
482
675
|
),
|
|
483
676
|
workspaceMode: Type.Optional(
|
|
484
677
|
StringEnum(["shared", "worktree"] as const, {
|
|
485
|
-
description: "Use the shared workspace or an opt-in disposable Git worktree.",
|
|
678
|
+
description: "Use the shared workspace (default) or an opt-in disposable Git worktree.",
|
|
486
679
|
}),
|
|
487
680
|
),
|
|
488
681
|
contract: Type.Optional(DelegationContractSchema),
|
|
@@ -502,30 +695,47 @@ export function registerStatefulSubagents(
|
|
|
502
695
|
}),
|
|
503
696
|
...createStatefulToolRenderer("spawn"),
|
|
504
697
|
async execute(_id, params, signal, _update, ctx) {
|
|
698
|
+
const generation = runtimeGeneration;
|
|
699
|
+
const capturedRegistry = registry;
|
|
700
|
+
let modules: StatefulSpawnModules;
|
|
701
|
+
try {
|
|
702
|
+
modules = await loadStatefulSpawnModules();
|
|
703
|
+
} catch (error) {
|
|
704
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
705
|
+
throw error;
|
|
706
|
+
}
|
|
707
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
708
|
+
let currentWorkspaceManager: WorkspaceManager;
|
|
709
|
+
try {
|
|
710
|
+
currentWorkspaceManager = await getWorkspaceManager();
|
|
711
|
+
} catch (error) {
|
|
712
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
505
716
|
const scope = (params.agentScope ?? "user") as AgentScope;
|
|
506
717
|
const resultFormat = (params.resultFormat ?? "text") as SubagentResultFormat;
|
|
507
|
-
const contract = normalizeDelegationContract(params.contract);
|
|
718
|
+
const contract = modules.delegationContract.normalizeDelegationContract(params.contract);
|
|
508
719
|
if (params.contract !== undefined && !contract) {
|
|
509
720
|
throw new Error(
|
|
510
721
|
"subagent_spawn contract must be a valid pi-subagents:delegation:v2 object",
|
|
511
722
|
);
|
|
512
723
|
}
|
|
513
|
-
assertSubagentDepthAllowed();
|
|
724
|
+
modules.runtimePolicy.assertSubagentDepthAllowed();
|
|
514
725
|
assertSpawnIdempotencyKey(params.idempotencyKey);
|
|
515
|
-
const generation = runtimeGeneration;
|
|
516
726
|
const currentSettings = getCurrentSettings();
|
|
517
|
-
const target = resolveSubagentTarget({
|
|
727
|
+
const target = modules.cwdPolicy.resolveSubagentTarget({
|
|
518
728
|
workspace: ctx.cwd,
|
|
519
729
|
requestedCwd: params.cwd,
|
|
520
730
|
currentProjectTrusted: ctx.isProjectTrusted(),
|
|
521
731
|
});
|
|
522
|
-
assertDelegationTargetAllowed(
|
|
732
|
+
modules.cwdPolicy.assertDelegationTargetAllowed(
|
|
523
733
|
target,
|
|
524
734
|
currentSettings?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY,
|
|
525
735
|
);
|
|
526
736
|
const cwd = target.cwd;
|
|
527
737
|
const mode = resolveSpawnContextMode(params.context, params.contextEntryIds);
|
|
528
|
-
const snapshot = buildContextSnapshot(
|
|
738
|
+
const snapshot = modules.context.buildContextSnapshot(
|
|
529
739
|
ctx.sessionManager.getBranch(),
|
|
530
740
|
mode,
|
|
531
741
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
@@ -534,28 +744,30 @@ export function registerStatefulSubagents(
|
|
|
534
744
|
if ((scope === "project" || scope === "both") && !ctx.isProjectTrusted()) {
|
|
535
745
|
throw new Error("Project-local subagent definitions require a trusted project");
|
|
536
746
|
}
|
|
537
|
-
const resolvedAgents = discoverAgents(cwd, scope, currentSettings).agents;
|
|
747
|
+
const resolvedAgents = modules.agents.discoverAgents(cwd, scope, currentSettings).agents;
|
|
538
748
|
const resolvedAgent = resolvedAgents.find((agent) => agent.name === params.agent);
|
|
539
749
|
if (!resolvedAgent) {
|
|
540
750
|
const available = resolvedAgents.map((agent) => agent.name).join(", ") || "none";
|
|
541
751
|
throw new Error(`Unknown subagent ${params.agent}. Available agents: ${available}`);
|
|
542
752
|
}
|
|
543
|
-
const targetSnapshot = targetPolicyAudit(target);
|
|
544
|
-
const { executionPlan, semanticSnapshot } =
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
753
|
+
const targetSnapshot = modules.cwdPolicy.targetPolicyAudit(target);
|
|
754
|
+
const { executionPlan, semanticSnapshot } =
|
|
755
|
+
await modules.retainedSemanticState.buildRetainedSemanticState({
|
|
756
|
+
agent: resolvedAgent,
|
|
757
|
+
contract,
|
|
758
|
+
target: targetSnapshot,
|
|
759
|
+
cwd,
|
|
760
|
+
workspaceMode: params.workspaceMode === "worktree" ? "worktree" : "shared",
|
|
761
|
+
transport: transportKind,
|
|
762
|
+
resultFormat,
|
|
763
|
+
thinkingLevel: params.thinkingLevel,
|
|
764
|
+
timeoutMs: params.timeoutMs,
|
|
765
|
+
taskGeneration: 1,
|
|
766
|
+
});
|
|
556
767
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
557
|
-
const requestHash = hashSpawnRequest({
|
|
768
|
+
const requestHash = modules.spawnIdempotency.hashSpawnRequest({
|
|
558
769
|
agent: params.agent,
|
|
770
|
+
taskName: params.taskName,
|
|
559
771
|
task: params.task,
|
|
560
772
|
cwd,
|
|
561
773
|
agentScope: scope,
|
|
@@ -572,7 +784,10 @@ export function registerStatefulSubagents(
|
|
|
572
784
|
contract,
|
|
573
785
|
resultFormat,
|
|
574
786
|
});
|
|
575
|
-
|
|
787
|
+
if (!capturedRegistry) {
|
|
788
|
+
throw new Error("Stateful subagents are not initialized for this session");
|
|
789
|
+
}
|
|
790
|
+
const ownedRegistry = capturedRegistry;
|
|
576
791
|
const retained = ownedRegistry.findBySpawnIdempotencyKey(params.idempotencyKey, requestHash);
|
|
577
792
|
if (retained) return result(retained, `Reused ${retained.agent} as ${retained.id}.`);
|
|
578
793
|
const foundPending = params.idempotencyKey
|
|
@@ -621,35 +836,27 @@ export function registerStatefulSubagents(
|
|
|
621
836
|
throw new Error("Project-local subagent definitions cannot run in a detached worktree");
|
|
622
837
|
}
|
|
623
838
|
const requestedCwd = cwd;
|
|
624
|
-
if ((params.workspaceMode ?? "shared") === "shared" && !params.allowConcurrentWrites) {
|
|
625
|
-
assertNoSharedWriteConflict(
|
|
626
|
-
ownedRegistry,
|
|
627
|
-
params.agent,
|
|
628
|
-
requestedCwd,
|
|
629
|
-
scope,
|
|
630
|
-
currentSettings,
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
839
|
const workspaceOwner = `pending-${randomUUID()}`;
|
|
634
840
|
const workspace =
|
|
635
841
|
params.workspaceMode === "worktree"
|
|
636
|
-
? await
|
|
842
|
+
? await currentWorkspaceManager.create(workspaceOwner, requestedCwd)
|
|
637
843
|
: undefined;
|
|
638
844
|
try {
|
|
639
845
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
640
846
|
} catch (error) {
|
|
641
|
-
if (workspace) await
|
|
847
|
+
if (workspace) await currentWorkspaceManager.cleanup(workspaceOwner);
|
|
642
848
|
throw error;
|
|
643
849
|
}
|
|
644
850
|
let agent: ManagedAgent | undefined;
|
|
645
851
|
try {
|
|
646
|
-
const capabilityGrant = issueCapabilityGrant(
|
|
852
|
+
const capabilityGrant = modules.capabilityGrant.issueCapabilityGrant(
|
|
647
853
|
executionPlan,
|
|
648
854
|
Date.now(),
|
|
649
855
|
Math.max(1, (params.timeoutMs ?? resolvedAgent.timeoutMs ?? 600_000) + 60_000),
|
|
650
856
|
);
|
|
651
857
|
agent = await ownedRegistry.spawn({
|
|
652
858
|
agent: params.agent,
|
|
859
|
+
taskName: params.taskName,
|
|
653
860
|
task: params.task,
|
|
654
861
|
cwd: workspace?.path ?? requestedCwd,
|
|
655
862
|
agentScope: scope,
|
|
@@ -678,21 +885,21 @@ export function registerStatefulSubagents(
|
|
|
678
885
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
679
886
|
} catch (error) {
|
|
680
887
|
if (agent) await ownedRegistry.closeTree(agent.id).catch(() => undefined);
|
|
681
|
-
if (workspace) await
|
|
888
|
+
if (workspace) await currentWorkspaceManager.cleanup(workspaceOwner);
|
|
682
889
|
throw error;
|
|
683
890
|
}
|
|
684
891
|
if (!agent) throw new Error("Subagent spawn completed without a retained agent");
|
|
685
892
|
if (workspace && agent.cwd === workspace.path) isolatedAgents.set(agent.id, workspaceOwner);
|
|
686
|
-
else if (workspace) await
|
|
893
|
+
else if (workspace) await currentWorkspaceManager.cleanup(workspaceOwner);
|
|
687
894
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
688
895
|
resolvePending?.(agent);
|
|
689
896
|
const deliveryNote =
|
|
690
897
|
completionDelivery === "auto-resume"
|
|
691
|
-
? "
|
|
692
|
-
: "
|
|
898
|
+
? "Auto-resume will request synthesis after completion."
|
|
899
|
+
: "The current response must not depend on the result because next-turn delivery will not wake an idle root.";
|
|
693
900
|
return result(
|
|
694
901
|
agent,
|
|
695
|
-
`Spawned ${agent.agent} as ${agent.id}.
|
|
902
|
+
`Spawned ${agent.agent} as ${agent.taskPath ?? agent.id} (${agent.id}). Continue the identified non-overlapping local work immediately; do not merely announce the spawn or end while useful local work remains. Only an explicit user-requested specialist model, tool-profile, or isolation exception may lack concurrent local work. ${deliveryNote} Do not poll for progress.`,
|
|
696
903
|
);
|
|
697
904
|
} catch (error) {
|
|
698
905
|
rejectPending?.(error);
|
|
@@ -722,7 +929,7 @@ export function registerStatefulSubagents(
|
|
|
722
929
|
"Send follow-up work to a reusable retained subagent and start a new turn. Semantic resource skew requires explicit revalidation. Use subagent_mailbox for queue-only messages.",
|
|
723
930
|
promptSnippet: "Start a new detached follow-up turn on a retained subagent",
|
|
724
931
|
parameters: Type.Object({
|
|
725
|
-
agentId: Type.String(),
|
|
932
|
+
agentId: Type.String({ description: "Retained agent ID or canonical task path." }),
|
|
726
933
|
task: Type.String({ minLength: 1, maxLength: DEFAULT_MAX_CONTEXT_BYTES }),
|
|
727
934
|
timeoutMs: Type.Optional(
|
|
728
935
|
Type.Integer({
|
|
@@ -740,32 +947,41 @@ export function registerStatefulSubagents(
|
|
|
740
947
|
}),
|
|
741
948
|
),
|
|
742
949
|
allowConcurrentWrites: Type.Optional(
|
|
743
|
-
Type.Boolean({
|
|
950
|
+
Type.Boolean({
|
|
951
|
+
description:
|
|
952
|
+
"Deprecated compatibility field; shared-workspace concurrency is allowed by default.",
|
|
953
|
+
}),
|
|
744
954
|
),
|
|
745
955
|
}),
|
|
746
956
|
...createStatefulToolRenderer("send"),
|
|
747
957
|
async execute(_id, params, signal, _update, ctx) {
|
|
748
958
|
const generation = runtimeGeneration;
|
|
749
959
|
const ownedRegistry = requireRegistry();
|
|
960
|
+
let modules: StatefulSpawnModules;
|
|
961
|
+
try {
|
|
962
|
+
modules = await loadStatefulSpawnModules();
|
|
963
|
+
} catch (error) {
|
|
964
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
965
|
+
throw error;
|
|
966
|
+
}
|
|
967
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
750
968
|
const currentSettings = getCurrentSettings();
|
|
751
969
|
const existing = ownedRegistry.get(params.agentId);
|
|
752
970
|
if (!existing) throw new Error(`Unknown subagent: ${params.agentId}`);
|
|
753
|
-
const currentAgent =
|
|
754
|
-
existing.cwd,
|
|
755
|
-
|
|
756
|
-
currentSettings,
|
|
757
|
-
).agents.find((agent) => agent.name === existing.agent);
|
|
971
|
+
const currentAgent = modules.agents
|
|
972
|
+
.discoverAgents(existing.cwd, existing.agentScope ?? "user", currentSettings)
|
|
973
|
+
.agents.find((agent) => agent.name === existing.agent);
|
|
758
974
|
if (!currentAgent) throw new Error(`Unknown retained subagent definition: ${existing.agent}`);
|
|
759
975
|
const resolvedFollowUpTarget =
|
|
760
976
|
existing.workspaceMode === "worktree"
|
|
761
977
|
? undefined
|
|
762
|
-
: resolveSubagentTarget({
|
|
978
|
+
: modules.cwdPolicy.resolveSubagentTarget({
|
|
763
979
|
workspace: ctx.cwd,
|
|
764
980
|
requestedCwd: existing.cwd,
|
|
765
981
|
currentProjectTrusted: ctx.isProjectTrusted(),
|
|
766
982
|
});
|
|
767
983
|
if (resolvedFollowUpTarget) {
|
|
768
|
-
assertDelegationTargetAllowed(
|
|
984
|
+
modules.cwdPolicy.assertDelegationTargetAllowed(
|
|
769
985
|
resolvedFollowUpTarget,
|
|
770
986
|
currentSettings?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY,
|
|
771
987
|
);
|
|
@@ -776,9 +992,11 @@ export function registerStatefulSubagents(
|
|
|
776
992
|
const currentTarget =
|
|
777
993
|
existing.workspaceMode === "worktree" && existing.target
|
|
778
994
|
? existing.target
|
|
779
|
-
: targetPolicyAudit(
|
|
995
|
+
: modules.cwdPolicy.targetPolicyAudit(
|
|
996
|
+
resolvedFollowUpTarget as NonNullable<typeof resolvedFollowUpTarget>,
|
|
997
|
+
);
|
|
780
998
|
const { executionPlan: currentPlan, semanticSnapshot: currentSnapshot } =
|
|
781
|
-
await buildRetainedSemanticState({
|
|
999
|
+
await modules.retainedSemanticState.buildRetainedSemanticState({
|
|
782
1000
|
agent: currentAgent,
|
|
783
1001
|
contract: existing.contract,
|
|
784
1002
|
target: currentTarget,
|
|
@@ -798,7 +1016,10 @@ export function registerStatefulSubagents(
|
|
|
798
1016
|
});
|
|
799
1017
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
800
1018
|
const compatibility = existing.semanticSnapshot
|
|
801
|
-
? evaluateSemanticCompatibility(
|
|
1019
|
+
? modules.semanticSnapshot.evaluateSemanticCompatibility(
|
|
1020
|
+
existing.semanticSnapshot,
|
|
1021
|
+
currentSnapshot,
|
|
1022
|
+
)
|
|
802
1023
|
: { status: "warning" as const, changedComponents: ["legacy-missing-snapshot"] };
|
|
803
1024
|
if (
|
|
804
1025
|
(compatibility.status === "needs-revalidation" || compatibility.status === "rejected") &&
|
|
@@ -817,14 +1038,7 @@ export function registerStatefulSubagents(
|
|
|
817
1038
|
currentSettings,
|
|
818
1039
|
);
|
|
819
1040
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
820
|
-
|
|
821
|
-
ownedRegistry,
|
|
822
|
-
existing,
|
|
823
|
-
params.allowConcurrentWrites ?? false,
|
|
824
|
-
isolatedAgents.has(existing.id),
|
|
825
|
-
currentSettings,
|
|
826
|
-
);
|
|
827
|
-
const currentGrant = issueCapabilityGrant(
|
|
1041
|
+
const currentGrant = modules.capabilityGrant.issueCapabilityGrant(
|
|
828
1042
|
currentPlan,
|
|
829
1043
|
Date.now(),
|
|
830
1044
|
Math.max(1, (params.timeoutMs ?? existing.timeoutMs ?? 600_000) + 60_000),
|
|
@@ -838,6 +1052,7 @@ export function registerStatefulSubagents(
|
|
|
838
1052
|
? { status: "warning", changedComponents: compatibility.changedComponents }
|
|
839
1053
|
: compatibility,
|
|
840
1054
|
);
|
|
1055
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
841
1056
|
const agent = await ownedRegistry.followUp(params.agentId, params.task, {
|
|
842
1057
|
timeoutMs: params.timeoutMs,
|
|
843
1058
|
idleTimeoutMs: params.idleTimeoutMs,
|
|
@@ -860,6 +1075,14 @@ export function registerStatefulSubagents(
|
|
|
860
1075
|
async execute(_id, params, signal): Promise<StatefulActionToolResult> {
|
|
861
1076
|
const generation = runtimeGeneration;
|
|
862
1077
|
const ownedRegistry = requireRegistry();
|
|
1078
|
+
let currentWorkspaceManager: WorkspaceManager;
|
|
1079
|
+
try {
|
|
1080
|
+
currentWorkspaceManager = await getWorkspaceManager();
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
1083
|
+
throw error;
|
|
1084
|
+
}
|
|
1085
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
863
1086
|
const ownedAgent = (agentId: string): ManagedAgent => {
|
|
864
1087
|
const value = ownedRegistry.get(agentId);
|
|
865
1088
|
if (!value) throw new Error(`Unknown subagent: ${agentId}`);
|
|
@@ -886,7 +1109,7 @@ export function registerStatefulSubagents(
|
|
|
886
1109
|
const existing = ownedRegistry.get(agentId);
|
|
887
1110
|
if (existing?.state === "closed" && !operation.subtree) {
|
|
888
1111
|
const pendingOwner = isolatedAgents.get(existing.id);
|
|
889
|
-
if (pendingOwner) await
|
|
1112
|
+
if (pendingOwner) await currentWorkspaceManager.cleanup(pendingOwner);
|
|
890
1113
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
891
1114
|
isolatedAgents.delete(existing.id);
|
|
892
1115
|
return result(existing, `Closed ${existing.id}.`);
|
|
@@ -896,7 +1119,7 @@ export function registerStatefulSubagents(
|
|
|
896
1119
|
try {
|
|
897
1120
|
agents = await ownedRegistry.closeTree(agentId);
|
|
898
1121
|
} finally {
|
|
899
|
-
await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents,
|
|
1122
|
+
await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents, currentWorkspaceManager);
|
|
900
1123
|
}
|
|
901
1124
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
902
1125
|
return {
|
|
@@ -911,7 +1134,7 @@ export function registerStatefulSubagents(
|
|
|
911
1134
|
try {
|
|
912
1135
|
agent = await ownedRegistry.close(agentId);
|
|
913
1136
|
} finally {
|
|
914
|
-
await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents,
|
|
1137
|
+
await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents, currentWorkspaceManager);
|
|
915
1138
|
}
|
|
916
1139
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
917
1140
|
return result(agent, `Closed ${agent.id}.`);
|