@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/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 { issueCapabilityGrant } from "./capability-grant.js";
21
- import { CompletionDeliveryBroker } from "./completion-delivery.js";
22
- import { buildContextSnapshot, type ContextMode, redactPrivateText } from "./context.js";
23
- import { createStatefulTransport } from "./create-stateful-transport.js";
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
- type AgentRunInspectionDetail,
42
- type AgentRunInspectionSummary,
43
- type ManagedAgent,
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
- const workspaceManager = dependencies.workspaceManager ?? new WorkspaceManager();
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 workspaceManager.cleanupAll();
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 cleanupErrors = await disposeStatefulRuntime(previousRegistry, workspaceManager);
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(pi, ctx, completionDelivery, {
298
- onDeliveryError: (error) => {
299
- if (!ctx.hasUI) return;
300
- const reason = error instanceof Error ? error.message : String(error);
301
- ctx.ui.notify(`Subagent completion delivery failed: ${reason}`, "warning");
302
- },
303
- onAcknowledged: (completions, deliveredAt) => {
304
- if (generation !== runtimeGeneration) return;
305
- for (const completion of completions) {
306
- void nextRegistry
307
- .markCompletionDelivered(completion.completionId, deliveredAt)
308
- .catch((error: unknown) => {
309
- if (!ctx.hasUI || generation !== runtimeGeneration) return;
310
- const reason = error instanceof Error ? error.message : String(error);
311
- ctx.ui.notify(`Subagent completion acknowledgement failed: ${reason}`, "warning");
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(persisted, workspaceManager);
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 [{ ...agent, cwd: target.cwd, target: targetPolicyAudit(target) }];
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, workspaceManager);
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 errors = await disposeStatefulRuntime(previousRegistry, workspaceManager);
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 } = await buildRetainedSemanticState({
540
- agent: resolvedAgent,
541
- contract,
542
- target: targetSnapshot,
543
- cwd,
544
- workspaceMode: params.workspaceMode === "worktree" ? "worktree" : "shared",
545
- transport: transportKind,
546
- resultFormat,
547
- thinkingLevel: params.thinkingLevel,
548
- timeoutMs: params.timeoutMs,
549
- taskGeneration: 1,
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
- const ownedRegistry = requireRegistry();
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 workspaceManager.create(workspaceOwner, requestedCwd)
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 workspaceManager.cleanup(workspaceOwner);
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 workspaceManager.cleanup(workspaceOwner);
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 workspaceManager.cleanup(workspaceOwner);
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 = discoverAgents(
749
- existing.cwd,
750
- existing.agentScope ?? "user",
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(resolvedFollowUpTarget as NonNullable<typeof resolvedFollowUpTarget>);
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(existing.semanticSnapshot, currentSnapshot)
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 workspaceManager.cleanup(pendingOwner);
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, workspaceManager);
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, workspaceManager);
1065
+ await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents, currentWorkspaceManager);
910
1066
  }
911
1067
  assertCurrentSpawn(signal, generation, runtimeGeneration);
912
1068
  return result(agent, `Closed ${agent.id}.`);