pi-extended-teams 2.2.9 → 2.3.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.
Files changed (54) hide show
  1. package/README.md +182 -2
  2. package/extensions/agents/read-agent-report.ts +11 -2
  3. package/extensions/agents/read-agent-session-lifecycle.ts +25 -10
  4. package/extensions/agents/read-agent.ts +387 -65
  5. package/extensions/agents/write-agent.ts +3 -0
  6. package/extensions/events/register-events.ts +22 -10
  7. package/extensions/index.ts +132 -38
  8. package/extensions/internal/pi-check-operations.ts +24 -0
  9. package/extensions/internal/session-files.ts +10 -2
  10. package/extensions/runtime/teammate-interrupt.ts +11 -5
  11. package/extensions/runtime/types.ts +4 -0
  12. package/extensions/team/lifecycle.ts +26 -0
  13. package/extensions/team/session-cost.ts +155 -0
  14. package/extensions/tools/agent-communication-tools.ts +24 -8
  15. package/extensions/tools/agent-status-tool.ts +50 -7
  16. package/extensions/tools/coordination-tools.ts +78 -8
  17. package/extensions/tools/task-runtime-tools.ts +23 -4
  18. package/extensions/tools/team-tools.ts +287 -40
  19. package/extensions/ui/activity-colors.ts +59 -0
  20. package/extensions/ui/agent-follow-view.ts +22 -12
  21. package/extensions/ui/agent-navigation.ts +35 -1
  22. package/extensions/ui/checkpoints-command.ts +28 -0
  23. package/extensions/ui/extensions-command.ts +5 -1
  24. package/extensions/ui/onboarding-command.ts +218 -0
  25. package/extensions/ui/status-widget.ts +22 -14
  26. package/package.json +1 -1
  27. package/skills/teams.md +16 -0
  28. package/src/orchestration/index.ts +19 -1
  29. package/src/orchestration/status-projection.ts +2 -3
  30. package/src/orchestration/types.ts +9 -1
  31. package/src/results/check-journal.ts +149 -0
  32. package/src/results/check-policy.ts +86 -0
  33. package/src/results/check-runner.ts +110 -0
  34. package/src/results/checkpoint-assignment.ts +60 -0
  35. package/src/results/checkpoint-policy.ts +20 -0
  36. package/src/results/checkpoint-report.ts +109 -0
  37. package/src/results/checkpoint-retention.ts +15 -0
  38. package/src/results/completion-group-delivery.ts +66 -0
  39. package/src/results/completion-group-recovery.ts +61 -0
  40. package/src/results/completion-group-wake.ts +62 -0
  41. package/src/results/completion-group.ts +290 -0
  42. package/src/results/continuation-recipient.ts +37 -0
  43. package/src/results/durable-json.ts +29 -0
  44. package/src/results/repair-policy.ts +24 -0
  45. package/src/results/report-result.ts +77 -0
  46. package/src/results/source-identity.ts +115 -0
  47. package/src/results/specialist-checkpoint.ts +227 -0
  48. package/src/results/verification-controller.ts +308 -0
  49. package/src/utils/messaging.ts +47 -10
  50. package/src/utils/models.ts +13 -0
  51. package/src/utils/paths.ts +4 -0
  52. package/src/utils/report-events.ts +95 -10
  53. package/src/utils/teams.ts +3 -1
  54. package/src/utils/write-queue.ts +3 -0
@@ -12,12 +12,23 @@ import * as messaging from "../../src/utils/messaging";
12
12
  import * as writeQueue from "../../src/utils/write-queue";
13
13
  import { ACCEPTED_FAVORITE_MODEL_SLOTS, FAVORITE_MODEL_SLOTS, canonicalPersistedModelSlot, isFavoriteModelSlot, loadSettings, normalizeFavoriteModelSlot, requireFavoriteModelLevel, resolveModel, roleForFavoriteModelSlot, type AgentRole, type CanonicalFavoriteModelSlot } from "../../src/utils/settings";
14
14
  import type { Member } from "../../src/utils/models";
15
+ import { CheckPolicySchema, normalizeCheckPolicy } from "../../src/results/check-policy";
16
+ import { RepairPolicySchema, normalizeRepairPolicy } from "../../src/results/repair-policy";
17
+ import { CheckpointPolicySchema } from "../../src/results/checkpoint-policy";
18
+ import { createCheckpointAssignment, captureCheckpointAssignment, continuationPrompt } from "../../src/results/checkpoint-assignment";
19
+ import { assertUnusedContinuationRecipient, freshContinuationName } from "../../src/results/continuation-recipient";
20
+ import { readCheckpoint } from "../../src/results/specialist-checkpoint";
21
+ import { CompletionGroup, CompletionGroupPolicySchema, completionGroupIdentity, normalizeCompletionGroupPolicy, type CompletionGroupBinding } from "../../src/results/completion-group";
22
+ import { listStoredTeamReportEvents } from "../../src/utils/report-events";
23
+ import { enqueueCompletionGroupDeliveries } from "../../src/results/completion-group-delivery";
24
+ import { shouldSuppressLeadReportInjection } from "../../src/utils/workflow-metadata";
15
25
 
16
26
  import type { RunningReadAgent } from "../runtime/types";
17
27
  import type { ReadAgentTeardownResult } from "../agents/read-agent-session-lifecycle";
18
28
  import type { ShutdownTeammateOptions } from "../team/lifecycle";
19
29
  import {
20
30
  onLifecycleTombstoneCleared,
31
+ listLifecycleTombstones,
21
32
  readLifecycleTombstone,
22
33
  withLifecycleTombstoneLock,
23
34
  type LifecycleTombstoneLock,
@@ -55,6 +66,7 @@ export interface TeamToolsOptions {
55
66
  startWriteAgent(teamName: string, member: Member, prompt: string): Promise<string>;
56
67
  shutdownTeammate(teamName: string, member: Member, options?: ShutdownTeammateOptions): Promise<ReadAgentTeardownResult>;
57
68
  adoptTeamAsLead(teamName: string, ctx?: any): void;
69
+ onCompletionGroupUse?(groupId: string): void;
58
70
  buildRoster(teamName: string): Promise<any>;
59
71
  isTeammate: boolean;
60
72
  agentName: string;
@@ -71,13 +83,21 @@ export interface NestedReadAgentToolBinding {
71
83
  outerCtx: any;
72
84
  }
73
85
 
86
+ export interface NestedChildSnapshot {
87
+ running: number;
88
+ queued: number;
89
+ }
90
+
74
91
  export interface TeamToolsRuntime {
75
92
  createNestedReadAgentTools(binding: NestedReadAgentToolBinding): any[];
76
- cancelQueuedAgent(teamName: string, agentName: string): boolean;
93
+ nestedChildSnapshot(binding: NestedReadAgentToolBinding): NestedChildSnapshot;
94
+ cancelQueuedAgent(teamName: string, agentName: string): boolean | Promise<boolean>;
77
95
  }
78
96
 
79
97
  interface SpawnTeammateOptions {
80
98
  once?: boolean;
99
+ signal?: AbortSignal;
100
+ completionGroup?: CompletionGroupBinding;
81
101
  nestedParent?: {
82
102
  teamName: string;
83
103
  name: string;
@@ -97,15 +117,21 @@ interface QueuedReadSpawn {
97
117
  ctx: any;
98
118
  requestedAt: number;
99
119
  nameReservationId?: string;
120
+ signal?: AbortSignal;
100
121
  pendingChildAcceptance?: PendingChildAcceptance;
101
122
  admissionError?: string;
102
123
  quarantineError?: string;
103
124
  launchCommitted?: boolean;
125
+ groupSettlement?: Promise<void>;
104
126
  }
105
127
 
106
128
  export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamToolsRuntime {
107
129
  if (options.isTeammate) {
108
- return { createNestedReadAgentTools: () => [], cancelQueuedAgent: () => false };
130
+ return {
131
+ createNestedReadAgentTools: () => [],
132
+ nestedChildSnapshot: () => ({ running: 0, queued: 0 }),
133
+ cancelQueuedAgent: () => false,
134
+ };
109
135
  }
110
136
 
111
137
  function emitOrchestrationResponse(requestId: string | undefined, type: string, payload: Record<string, any>): void {
@@ -297,6 +323,20 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
297
323
  return queuedReadSpawnsByTeam.get(teamName) ?? [];
298
324
  }
299
325
 
326
+ function nestedChildSnapshot(binding: NestedReadAgentToolBinding): NestedChildSnapshot {
327
+ const parent = {
328
+ teamName: binding.teamName,
329
+ parentName: binding.parent.name,
330
+ parentRunId: binding.parentRunId,
331
+ };
332
+ const queued = readQueue(binding.teamName).filter((child) => {
333
+ const childParent = pendingParentForMember(binding.teamName, child.member);
334
+ return childParent?.parentName === parent.parentName && childParent.parentRunId === parent.parentRunId;
335
+ }).length;
336
+ const pending = pendingChildController.pendingCount(parent);
337
+ return { running: Math.max(0, pending - queued), queued };
338
+ }
339
+
300
340
  async function listQueuedAgentStatuses(teamName: string): Promise<QueuedAgentStatus[]> {
301
341
  const readers = [...readQueue(teamName), ...(failedAdmissionsByTeam.get(teamName) ?? [])].map((queued, index) => ({
302
342
  name: queued.member.name,
@@ -342,17 +382,34 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
342
382
  queued: readQueue(activeTeamName).length,
343
383
  });
344
384
  });
385
+ const checkpointAdmissions = new Map<string, { controller: AbortController; settled: Promise<void> }>();
386
+ function cancelCheckpointAdmission(teamName: string, name: string): Promise<void> | undefined {
387
+ const pending = checkpointAdmissions.get(options.readAgentKey(teamName, name));
388
+ pending?.controller.abort(new Error("Checkpoint preparation cancelled."));
389
+ return pending?.settled;
390
+ }
345
391
  let lifecycleProbeCleanedUp = false;
346
392
  let pendingChildCancelUnsubscribe = (): void => {};
347
- pi.on?.("session_shutdown", () => {
393
+ pi.on?.("session_shutdown", async () => {
348
394
  if (lifecycleProbeCleanedUp) return;
349
395
  lifecycleProbeCleanedUp = true;
396
+ const settlements: Promise<void>[] = [];
397
+ for (const pending of checkpointAdmissions.values()) {
398
+ pending.controller.abort(new Error("Checkpoint preparation cancelled: session is closing."));
399
+ settlements.push(pending.settled);
400
+ }
350
401
  for (const [teamName, queue] of queuedReadSpawnsByTeam) {
351
- for (const queued of queue) removeQueuedReadSpawnById(teamName, queued.id);
402
+ for (const queued of queue) {
403
+ const removed = removeQueuedReadSpawnById(teamName, queued.id);
404
+ if (removed?.groupSettlement) settlements.push(removed.groupSettlement);
405
+ }
406
+ }
407
+ try { await Promise.all(settlements); }
408
+ finally {
409
+ if (typeof lifecycleProbeUnsubscribe === "function") lifecycleProbeUnsubscribe();
410
+ pendingChildCancelUnsubscribe();
411
+ lifecycleFenceUnsubscribe();
352
412
  }
353
- if (typeof lifecycleProbeUnsubscribe === "function") lifecycleProbeUnsubscribe();
354
- pendingChildCancelUnsubscribe();
355
- lifecycleFenceUnsubscribe();
356
413
  });
357
414
 
358
415
  function setReadQueue(teamName: string, queue: QueuedReadSpawn[]): void {
@@ -373,6 +430,17 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
373
430
  };
374
431
  }
375
432
 
433
+ function settleQueuedGroup(queued: QueuedReadSpawn): Promise<void> | undefined {
434
+ const binding = queued.member.completionGroup;
435
+ if (!binding) return;
436
+ return Promise.resolve().then(async () => {
437
+ const group = new CompletionGroup(queued.teamName, binding.groupId);
438
+ await group.apply({ type: queued.admissionError ? "rejected" : "cancelled", ...binding,
439
+ queueId: queued.id, reason: queued.admissionError || "Queued assignment cancelled." });
440
+ await enqueueCompletionGroupDeliveries(group);
441
+ });
442
+ }
443
+
376
444
  function removeQueuedReadSpawnById(
377
445
  teamName: string,
378
446
  id: string,
@@ -386,6 +454,10 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
386
454
  if (settlePendingAcceptance && removed.pendingChildAcceptance) {
387
455
  pendingChildController.settleAcceptance(removed.pendingChildAcceptance);
388
456
  }
457
+ if (settlePendingAcceptance) {
458
+ cancelCheckpointAdmission(teamName, removed.member.name);
459
+ removed.groupSettlement = settleQueuedGroup(removed);
460
+ }
389
461
  return removed;
390
462
  }
391
463
 
@@ -397,6 +469,9 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
397
469
  for (const queued of removed) {
398
470
  releaseNestedReadName(teamName, queued.member.name, queued.nameReservationId);
399
471
  if (queued.pendingChildAcceptance) pendingChildController.settleAcceptance(queued.pendingChildAcceptance);
472
+ const pending = cancelCheckpointAdmission(teamName, queued.member.name);
473
+ const group = settleQueuedGroup(queued);
474
+ queued.groupSettlement = pending ? Promise.all([pending, group]).then(() => {}) : group;
400
475
  }
401
476
  return removed;
402
477
  }
@@ -561,6 +636,12 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
561
636
  }
562
637
 
563
638
  try {
639
+ if (member.completionGroup) {
640
+ if (!member.lifecycleRunId) throw new Error("Grouped admission requires an actual lifecycle run.");
641
+ await new CompletionGroup(teamName, member.completionGroup.groupId).apply({
642
+ type: "running", ...member.completionGroup, runId: member.lifecycleRunId,
643
+ });
644
+ }
564
645
  if (lifecycleProbeCleanedUp) throw new Error("Agent session is closing; admission cancelled.");
565
646
  assertPending?.();
566
647
  } catch (error) {
@@ -608,9 +689,10 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
608
689
  sessionContextReference = null;
609
690
  }
610
691
  }
692
+ const continuedPrompt = continuationPrompt(member.checkpointAssignment, prompt);
611
693
  const launchPrompt = sessionContextReference
612
- ? `${prompt}${sessionContextReference.promptSuffix}`
613
- : prompt;
694
+ ? `${continuedPrompt}${sessionContextReference.promptSuffix}`
695
+ : continuedPrompt;
614
696
  const removeReference = () => {
615
697
  try {
616
698
  removeSessionContextReference(sessionContextReference);
@@ -622,6 +704,7 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
622
704
  try {
623
705
  if (lifecycleProbeCleanedUp) throw new Error("Agent session is closing; admission cancelled.");
624
706
  assertPending?.();
707
+ if (member.checkpointAssignment?.parent) readCheckpoint(member.checkpointAssignment.parent.id);
625
708
  commitLaunch?.();
626
709
  const launch = options.runReadAgentInProcess(teamName, member, launchPrompt, ctx, options.readAgentOptions());
627
710
  return {
@@ -654,10 +737,21 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
654
737
  releaseNameOnFailure = true,
655
738
  queuedAcceptance?: PendingChildAcceptance,
656
739
  assertPending?: () => void,
740
+ signal?: AbortSignal,
657
741
  commitLaunch?: () => void,
658
742
  ): Promise<boolean> {
659
743
  const key = options.readAgentKey(teamName, member.name);
744
+ if (checkpointAdmissions.has(key)) throw new Error(`Checkpoint admission for ${member.name} is already in progress.`);
660
745
  reserveReadAdmission(teamName, key, member.role || "read");
746
+ const controller = member.checkpointAssignment ? new AbortController() : undefined;
747
+ let settleCheckpoint = () => {};
748
+ if (controller) checkpointAdmissions.set(key, { controller, settled: new Promise<void>(resolve => { settleCheckpoint = resolve; }) });
749
+ const checkpointSignal = controller ? AbortSignal.any([controller.signal, AbortSignal.timeout(30_000), ...(signal ? [signal] : [])]) : undefined;
750
+ const finishCheckpoint = () => {
751
+ if (controller && checkpointAdmissions.get(key)?.controller === controller) checkpointAdmissions.delete(key);
752
+ settleCheckpoint();
753
+ };
754
+ const assertAdmissionPending = () => { checkpointSignal?.throwIfAborted(); assertPending?.(); };
661
755
 
662
756
  const releaseNameReservation = () => {
663
757
  releaseNestedReadName(teamName, member.name, nameReservationId);
@@ -688,7 +782,15 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
688
782
  };
689
783
 
690
784
  try {
691
- const admitted = await admitAndLaunchReadAgentMember(teamName, member, prompt, ctx, queuedAcceptance, assertPending, commitLaunch);
785
+ if (member.checkpointAssignment && checkpointSignal) {
786
+ if (lifecycleProbeCleanedUp) throw new Error("Agent session is closing; admission cancelled.");
787
+ assertAdmissionPending();
788
+ member.checkpointAssignment = await captureCheckpointAssignment(member.checkpointAssignment, member.cwd, checkpointSignal);
789
+ }
790
+ const admitted = await admitAndLaunchReadAgentMember(teamName, member, prompt, ctx, queuedAcceptance, assertAdmissionPending, () => {
791
+ finishCheckpoint();
792
+ commitLaunch?.();
793
+ });
692
794
  releaseNameReservation();
693
795
  void Promise.resolve(admitted.launch).then(
694
796
  () => drainAfterRun(admitted.pendingChildRun),
@@ -699,7 +801,7 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
699
801
  settlePendingChild(queuedAcceptance);
700
802
  releaseReservationAndDrain();
701
803
  throw error;
702
- }
804
+ } finally { finishCheckpoint(); }
703
805
  }
704
806
 
705
807
  async function enqueueReadSpawn(
@@ -709,13 +811,10 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
709
811
  params: any,
710
812
  resolved: ReturnType<typeof resolveModel>,
711
813
  ctx: any,
712
- nameReservationId?: string
814
+ nameReservationId?: string,
815
+ signal?: AbortSignal,
713
816
  ): Promise<QueuedReadSpawn> {
714
- const append = (pendingChildAcceptance?: PendingChildAcceptance): QueuedReadSpawn => {
715
- const queue = readQueue(teamName);
716
- if (queue.some((queued) => queued.member.name === member.name)) {
717
- throw new Error(`Nested read agent ${member.name} is already active or queued; nested delegation cannot replace an existing run.`);
718
- }
817
+ const append = async (pendingChildAcceptance?: PendingChildAcceptance): Promise<QueuedReadSpawn> => {
719
818
  const queued: QueuedReadSpawn = {
720
819
  id: crypto.randomUUID(),
721
820
  teamName,
@@ -726,9 +825,21 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
726
825
  ctx,
727
826
  requestedAt: Date.now(),
728
827
  nameReservationId,
828
+ signal,
729
829
  pendingChildAcceptance,
730
830
  };
731
- setReadQueue(teamName, [...queue, queued]);
831
+ if (member.completionGroup) {
832
+ const group = new CompletionGroup(teamName, member.completionGroup.groupId);
833
+ await group.apply({ type: "queued", ...member.completionGroup, queueId: queued.id });
834
+ if (lifecycleProbeCleanedUp) {
835
+ await group.apply({ type: "cancelled", ...member.completionGroup, reason: "Agent session is closing." });
836
+ throw new Error("Agent session is closing; admission cancelled.");
837
+ }
838
+ }
839
+ if (readQueue(teamName).some(item => item.member.name === member.name)) {
840
+ throw new Error(`Nested read agent ${member.name} is already active or queued; nested delegation cannot replace an existing run.`);
841
+ }
842
+ setReadQueue(teamName, [...readQueue(teamName), queued]);
732
843
  void drainQueuedReadSpawns(teamName);
733
844
  return queued;
734
845
  };
@@ -744,7 +855,7 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
744
855
  if (!parent) throw new Error(`Nested read agent ${member.name} is missing exact parent identity.`);
745
856
  const pendingChildAcceptance = pendingChildController.acceptChild(parent, member.name);
746
857
  try {
747
- return append(pendingChildAcceptance);
858
+ return await append(pendingChildAcceptance);
748
859
  } catch (error) {
749
860
  pendingChildController.settleAcceptance(pendingChildAcceptance);
750
861
  throw error;
@@ -785,7 +896,7 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
785
896
  if (activeAgentCount(teamName, role, true) >= capacity.maxConcurrent) continue;
786
897
  queued.member.joinedAt = Date.now();
787
898
  await startReadAgentMember(teamName, queued.member, queued.prompt, queued.ctx,
788
- queued.nameReservationId, false, queued.pendingChildAcceptance, assertPending,
899
+ queued.nameReservationId, false, queued.pendingChildAcceptance, assertPending, queued.signal,
789
900
  () => { queued.launchCommitted = true; });
790
901
  removeQueuedReadSpawnById(teamName, queued.id, false);
791
902
  progressed = true;
@@ -799,10 +910,17 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
799
910
  }
800
911
  queued.admissionError = error instanceof Error ? error.message : String(error);
801
912
  failedAdmissionsByTeam.set(teamName, [...(failedAdmissionsByTeam.get(teamName) ?? []), queued].slice(-20));
802
- removeQueuedReadSpawnById(teamName, queued.id);
803
- await messaging.sendPlainMessage(teamName, "system", queued.member.parentAgentName || "team-lead",
804
- `Queued agent ${queued.member.name} failed admission: ${queued.admissionError}`,
805
- `Queued agent ${queued.member.name} failed`, "red").catch(() => {});
913
+ const removed = removeQueuedReadSpawnById(teamName, queued.id);
914
+ let groupSettlementFailed = false;
915
+ await removed?.groupSettlement?.catch(error => {
916
+ groupSettlementFailed = true;
917
+ queued.admissionError += ` Group settlement failed: ${error instanceof Error ? error.message : String(error)}`;
918
+ });
919
+ if (!queued.member.completionGroup || groupSettlementFailed) {
920
+ await messaging.sendPlainMessage(teamName, "system", queued.member.parentAgentName || "team-lead",
921
+ `Queued agent ${queued.member.name} failed admission: ${queued.admissionError}`,
922
+ `Queued agent ${queued.member.name} failed`, "red").catch(() => {});
923
+ }
806
924
  }
807
925
  }
808
926
  }
@@ -817,8 +935,38 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
817
935
  return paths.sanitizeName(`session-${sessionId}`);
818
936
  }
819
937
 
938
+ let admittingPublicTeam: string | undefined;
939
+ let publicAdmissions = 0;
940
+ async function withPublicScope<T>(teamName: string, action: () => Promise<T>): Promise<T> {
941
+ if (teamName.startsWith("prompt-build-")) return action();
942
+ if (publicAdmissions > 0 && admittingPublicTeam !== teamName) {
943
+ throw new Error(`Cannot switch to ${teamName}: ${admittingPublicTeam} has unfinished agents being admitted.`);
944
+ }
945
+ admittingPublicTeam = teamName;
946
+ publicAdmissions += 1;
947
+ try {
948
+ return await action();
949
+ } finally {
950
+ if (--publicAdmissions === 0) admittingPublicTeam = undefined;
951
+ }
952
+ }
953
+
954
+ async function requireSettledPublicScope(nextTeamName: string): Promise<void> {
955
+ if (lifecycleProbeCleanedUp) throw new Error("Agent session is closing; admission cancelled.");
956
+ const current = options.getTeamName();
957
+ if (!current || current === nextTeamName || nextTeamName.startsWith("prompt-build-")) return;
958
+ const [config, fences, queued] = await Promise.all([
959
+ teams.readConfig(current), listLifecycleTombstones(current), listQueuedAgentStatuses(current),
960
+ ]);
961
+ const unfinished = config.members.some(member => member.name !== "team-lead")
962
+ || fences.length > 0 || queued.some(item => !item.failed)
963
+ || Array.from(options.runningReadAgents.values()).some(agent => agent.teamName === current && agent.teardownState !== "finalized");
964
+ if (unfinished) throw new Error(`Cannot switch from ${current} to ${nextTeamName}: unfinished agents must remain visible. Finish or stop them first.`);
965
+ }
966
+
820
967
  async function ensureCurrentSessionAgentGroup(ctx: any, explicitDefaultModel: string): Promise<string> {
821
968
  const sessionName = currentSessionAgentGroupName(ctx);
969
+ await requireSettledPublicScope(sessionName);
822
970
  if (teams.teamExists(sessionName)) {
823
971
  options.adoptTeamAsLead(sessionName, ctx);
824
972
  return sessionName;
@@ -837,10 +985,23 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
837
985
  }
838
986
 
839
987
  async function spawnTeammate(params: any, ctx: any, spawnOptions: SpawnTeammateOptions = {}): Promise<{ content: any[]; details: any }> {
988
+ const checkpointAssignment = createCheckpointAssignment(params.checkpoint, params.prompt, params.continue_from);
989
+ const assignedChecks = normalizeCheckPolicy(params.checks);
990
+ const repairPolicy = normalizeRepairPolicy(params.repair);
991
+ if (repairPolicy && !assignedChecks?.length) throw new Error("Repair policy requires explicitly assigned checks.");
840
992
  const safeName = paths.sanitizeName(params.name);
841
993
  const safeTeamName = paths.sanitizeName(params.team_name);
842
994
  const cwd = params.cwd || ctx.cwd;
843
995
  const teamConfig = await teams.readConfig(safeTeamName);
996
+ if (checkpointAssignment?.parent) {
997
+ requireSpawnLevel(params, `Continuation ${safeName}`);
998
+ if (safeName.toLowerCase() === checkpointAssignment.parent.author.agentName.toLowerCase()
999
+ || teamConfig.members.some(member => member.name.toLowerCase() === safeName.toLowerCase())
1000
+ || options.runningReadAgents.has(options.readAgentKey(safeTeamName, safeName)) || findQueuedReadSpawn(safeTeamName, { name: safeName })) {
1001
+ throw new Error(`Continuation recipient ${safeName} is not unused; choose a new prefix and retry.`);
1002
+ }
1003
+ assertUnusedContinuationRecipient(safeTeamName, safeName);
1004
+ }
844
1005
  failedAdmissionsByTeam.set(safeTeamName, (failedAdmissionsByTeam.get(safeTeamName) ?? []).filter(item => item.member.name !== safeName));
845
1006
  let nestedNameReservationId: string | undefined;
846
1007
  let nestedNameReservationTransferred = false;
@@ -871,7 +1032,7 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
871
1032
  }
872
1033
 
873
1034
  try {
874
- if (spawnOptions.once) {
1035
+ if (spawnOptions.once && !checkpointAssignment?.parent) {
875
1036
  const existingOnceMember = teamConfig.members.find(m => m.agentType === "teammate" && (m.name === safeName || memberMatchesOperation(m, params)));
876
1037
  if (existingOnceMember) {
877
1038
  return {
@@ -910,6 +1071,9 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
910
1071
  }
911
1072
  }
912
1073
 
1074
+ if (checkpointAdmissions.has(options.readAgentKey(safeTeamName, safeName)) && !findQueuedReadSpawn(safeTeamName, { name: safeName })) {
1075
+ throw new Error(`Checkpoint admission for ${safeName} is already in progress.`);
1076
+ }
913
1077
  const existingMember = teamConfig.members.find(m => m.name === safeName && m.agentType === "teammate");
914
1078
  if (existingMember) {
915
1079
  const key = options.readAgentKey(safeTeamName, existingMember.name);
@@ -980,6 +1144,10 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
980
1144
  color: role === "read" ? "cyan" : "blue",
981
1145
  thinking: chosenThinking,
982
1146
  planModeRequired: params.plan_mode_required,
1147
+ assignedChecks,
1148
+ repairPolicy,
1149
+ checkpointAssignment,
1150
+ completionGroup: spawnOptions.completionGroup,
983
1151
  metadata: operationMetadataFromParams(params),
984
1152
  delegationDepth: spawnOptions.nestedParent ? 1 : 0,
985
1153
  allowNestedReadAgents: !spawnOptions.nestedParent && spawnOptions.allowNestedReadAgents === true,
@@ -991,7 +1159,10 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
991
1159
  };
992
1160
 
993
1161
  if (role === "write") await writeQueue.removeQueuedWriteSpawnsByName(safeTeamName, safeName);
994
- if (!spawnOptions.nestedParent) removeQueuedReadSpawnsByName(safeTeamName, safeName);
1162
+ if (!spawnOptions.nestedParent) {
1163
+ const removed = removeQueuedReadSpawnsByName(safeTeamName, safeName);
1164
+ if (removed.some(item => item.groupSettlement)) await Promise.all(removed.map(item => item.groupSettlement));
1165
+ }
995
1166
  if (lifecycleProbeCleanedUp) throw new Error("Agent session is closing; admission cancelled.");
996
1167
  const capacity = role === "write" ? settings.writeAgents : settings.readAgents;
997
1168
  const activeCount = activeAgentCount(safeTeamName, role, true);
@@ -999,7 +1170,7 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
999
1170
  if (!capacity.queueOverflow) {
1000
1171
  throw new Error(`${role === "write" ? "Edit" : "Read"}-agent capacity reached (${activeCount}/${capacity.maxConcurrent}) and queueOverflow is disabled.`);
1001
1172
  }
1002
- const queued = await enqueueReadSpawn(safeTeamName, member, params.prompt, params, resolved, ctx, nestedNameReservationId);
1173
+ const queued = await enqueueReadSpawn(safeTeamName, member, params.prompt, params, resolved, ctx, nestedNameReservationId, spawnOptions.signal);
1003
1174
  nestedNameReservationTransferred = !!nestedNameReservationId;
1004
1175
  const queuePosition = readQueue(safeTeamName).findIndex(item => item.id === queued.id) + 1;
1005
1176
  return {
@@ -1008,7 +1179,7 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
1008
1179
  };
1009
1180
  }
1010
1181
 
1011
- const sessionContextAvailable = await startReadAgentMember(safeTeamName, member, params.prompt, ctx, nestedNameReservationId);
1182
+ const sessionContextAvailable = await startReadAgentMember(safeTeamName, member, params.prompt, ctx, nestedNameReservationId, true, undefined, undefined, spawnOptions.signal);
1012
1183
  if (role === "write") {
1013
1184
  await writeTeamsDebugEvent(safeTeamName, "write-agent.spawn.success", {
1014
1185
  agentName: safeName, cwd, requestedRole: role, model: chosenModel,
@@ -1033,7 +1204,7 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
1033
1204
  }
1034
1205
  }
1035
1206
 
1036
- pi.events?.on?.("pi-extended-teams:orchestration-request", async (payload: any) => {
1207
+ async function handleOrchestrationRequest(payload: any): Promise<void> {
1037
1208
  const requestId = payload?.requestId;
1038
1209
  const type = String(payload?.type || "");
1039
1210
  const params = payload?.params || {};
@@ -1045,6 +1216,9 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
1045
1216
  if (options.isTeammate) throw new Error("Teammates cannot satisfy orchestration requests directly.");
1046
1217
  if (!ctx) throw new Error("No active lead session context is available for orchestration request. If pi-extended-teams was registered after session_start, include the current Pi command context as payload.ctx.");
1047
1218
 
1219
+ if (type === "ensure_team" || type === "spawn_teammate_once") {
1220
+ await requireSettledPublicScope(paths.sanitizeName(params.team_name));
1221
+ }
1048
1222
  if (type === "ensure_team") {
1049
1223
  const safeTeamName = paths.sanitizeName(params.team_name);
1050
1224
  if (teams.teamExists(safeTeamName)) {
@@ -1078,7 +1252,17 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
1078
1252
  const safeTeamName = paths.sanitizeName(params.team_name);
1079
1253
  if (!teams.teamExists(safeTeamName)) throw new Error(`Team ${params.team_name} does not exist`);
1080
1254
  options.adoptTeamAsLead(safeTeamName, ctx);
1081
- const result = await spawnTeammate(params, ctx, { once: true });
1255
+ const prepared = params.continue_from === undefined ? params : { ...params, name: freshContinuationName(params.name || "agent") };
1256
+ const result = await spawnTeammate(prepared, ctx, {
1257
+ once: true,
1258
+ allowNestedReadAgents: params.allow_nested_read_agents === true,
1259
+ });
1260
+ emitOrchestrationResponse(requestId, type, { ok: true, details: result.details, content: result.content });
1261
+ return;
1262
+ }
1263
+
1264
+ if (type === "spawn_agent") {
1265
+ const result = await spawnPublicAgent(params, ctx);
1082
1266
  emitOrchestrationResponse(requestId, type, { ok: true, details: result.details, content: result.content });
1083
1267
  return;
1084
1268
  }
@@ -1087,6 +1271,11 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
1087
1271
  } catch (error) {
1088
1272
  emitOrchestrationResponse(requestId, type, { ok: false, error: error instanceof Error ? error.message : String(error) });
1089
1273
  }
1274
+ }
1275
+ pi.events?.on?.("pi-extended-teams:orchestration-request", (payload: any) => {
1276
+ if (payload?.type !== "ensure_team" && payload?.type !== "spawn_teammate_once") return handleOrchestrationRequest(payload);
1277
+ return withPublicScope(String(payload?.params?.team_name || ""), () => handleOrchestrationRequest(payload))
1278
+ .catch(error => emitOrchestrationResponse(payload?.requestId, payload?.type, { ok: false, error: error instanceof Error ? error.message : String(error) }));
1090
1279
  });
1091
1280
 
1092
1281
  const levelDescription = "Required intent tier. Configured favorites take priority; an unconfigured tier inherits the current lead model and thinking. Read tiers: read-collect gathers bounded facts without owning the conclusion; read-review is the normal default for focused review, verification, and bounded synthesis; read-analyze explains behavior or root cause across connected evidence; read-critical is only for irreducible high-stakes security, architecture, concurrency, migration, or data-correctness reasoning. Write tiers: write-patch makes a narrow localized change; write-feature implements a bounded feature with a known design; write-system owns a cross-cutting integration or refactor within explicitly claimed files; write-critical is only for high-risk security, concurrency, recovery, migration, or data-integrity changes. Prefer canonical tiers; legacy reading-*/writing-* aliases remain accepted for this minor release. Do not pass role, model, or thinking directly; see README.md.";
@@ -1094,6 +1283,10 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
1094
1283
  name: Type.Optional(Type.String({ description: "Stable display name. Defaults to a generated agent name." })),
1095
1284
  prompt: Type.String({ description: "The agent's assignment, relevant prior context, evidence already gathered, constraints, and report shape." }),
1096
1285
  cwd: Type.Optional(Type.String({ description: "Working directory. Defaults to the lead session cwd." })),
1286
+ checks: Type.Optional(CheckPolicySchema),
1287
+ repair: Type.Optional(RepairPolicySchema),
1288
+ checkpoint: Type.Optional(CheckpointPolicySchema),
1289
+ continue_from: Type.Optional(Type.String({ description: "Continue this checkpoint as a fresh recipient/run with the current assignment and tier. name is a prefix, not a recipient to reuse." })),
1097
1290
  session_context: Type.Optional(StringEnum(["none", "lazy"] as const, { description: "Optional filtered snapshot of the lead's active session branch. Use lazy only when omitted session history may materially affect the lane; the child reads it on demand rather than receiving transcript content in its prompt.", default: "none" })),
1098
1291
  metadata: Type.Optional(Type.Record(Type.String(), Type.Any())),
1099
1292
  allow_nested_read_agents: Type.Optional(Type.Boolean({ description: "Opt in eligible depth-0 write-feature/write-critical agents to restricted read-only child spawning.", default: false })),
@@ -1112,18 +1305,23 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
1112
1305
  return `agent-${Date.now().toString(36)}${position}-${crypto.randomUUID().slice(0, 8)}`;
1113
1306
  }
1114
1307
 
1115
- async function spawnPublicAgent(params: any, ctx: any): Promise<{ content: any[]; details: any }> {
1308
+ function spawnPublicAgent(params: any, ctx: any, signal?: AbortSignal): Promise<{ content: any[]; details: any }> {
1309
+ return withPublicScope(currentSessionAgentGroupName(ctx), () => spawnPublicAgentInScope(params, ctx, signal));
1310
+ }
1311
+
1312
+ async function spawnPublicAgentInScope(params: any, ctx: any, signal?: AbortSignal): Promise<{ content: any[]; details: any }> {
1116
1313
  if (options.isTeammate) throw new Error("Only the lead session can spawn agents.");
1117
1314
  if (!ctx) throw new Error("No active Pi session context is available for spawn_agent.");
1118
1315
 
1316
+ params = { ...params, checkpoint: createCheckpointAssignment(params.checkpoint, params.prompt, params.continue_from)?.policy };
1317
+ const name = params.continue_from === undefined ? params.name || generatedAgentName() : freshContinuationName(params.name || "agent");
1119
1318
  const sessionDefaultModel = configuredFavoriteModelForSpawn(params, ctx);
1120
1319
  const sessionName = await ensureCurrentSessionAgentGroup(ctx, sessionDefaultModel);
1121
- const name = params.name || generatedAgentName();
1122
1320
  const result = await spawnTeammate({
1123
1321
  ...params,
1124
1322
  name,
1125
1323
  team_name: sessionName,
1126
- }, ctx, { allowNestedReadAgents: params.allow_nested_read_agents === true });
1324
+ }, ctx, { allowNestedReadAgents: params.allow_nested_read_agents === true, signal });
1127
1325
 
1128
1326
  const outcome = result.details.queued
1129
1327
  ? `Agent ${name} queued at position ${result.details.queuePosition}.`
@@ -1253,7 +1451,7 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
1253
1451
  description: "Spawn one agent by intent tier only. Configured favorites take priority; an unconfigured tier inherits the current lead model and thinking. Give it the relevant goal, decisions, prior attempts, inspected evidence, constraints, and expected delta rather than a context-free task. Use session_context=lazy only as an on-demand fallback when omitted session history may materially matter; it never replaces a good mission prompt. read-review is the normal read default; use read-collect for bounded fact gathering, read-analyze for connected explanation/root cause, and read-critical only for irreducible high-stakes reasoning. For edits, choose write-patch, write-feature, write-system, or the rare high-risk write-critical by scope and risk. After spawning, do not duplicate or take over its lane; work only on unrelated work, then end the turn so the automatic report can resume you. One get_agent_status snapshot is allowed when current status is needed; never sleep, busy-wait, repeatedly read inbox/status, or treat healthy silence as failure. Wait for the actual report before synthesizing; intervene only on a reported blocker/error, actual health failure, or explicit user cancellation. model_slot selects behavior, model, and thinking; do not pass role, model, or thinking directly.",
1254
1452
  parameters: Type.Object(publicAgentParams),
1255
1453
  async execute(_toolCallId: string, params: any, _signal: AbortSignal, _onUpdate: any, ctx: any) {
1256
- return spawnPublicAgent(params, ctx);
1454
+ return spawnPublicAgent(params, ctx, _signal);
1257
1455
  },
1258
1456
  });
1259
1457
 
@@ -1264,11 +1462,15 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
1264
1462
  parameters: Type.Object({
1265
1463
  defaults: Type.Optional(Type.Object({
1266
1464
  cwd: Type.Optional(Type.String()),
1465
+ checks: Type.Optional(CheckPolicySchema),
1466
+ repair: Type.Optional(RepairPolicySchema),
1467
+ checkpoint: Type.Optional(CheckpointPolicySchema),
1267
1468
  model_slot: Type.Optional(StringEnum(ACCEPTED_FAVORITE_MODEL_SLOTS, { description: levelDescription })),
1268
1469
  metadata: Type.Optional(Type.Record(Type.String(), Type.Any())),
1269
1470
  session_context: Type.Optional(StringEnum(["none", "lazy"] as const, { description: "Shared lazy session-reference policy.", default: "none" })),
1270
1471
  allow_nested_read_agents: Type.Optional(Type.Boolean({ description: "Shared opt-in for eligible depth-0 write-feature/write-critical agents.", default: false })),
1271
1472
  })),
1473
+ completion_group: Type.Optional(CompletionGroupPolicySchema),
1272
1474
  agents: Type.Array(Type.Object(publicSwarmAgentParams), { description: "Agents to spawn as one batch. Each one must have model_slot directly or inherit it from defaults." }),
1273
1475
  }),
1274
1476
  async execute(_toolCallId: string, params: any, _signal: AbortSignal, _onUpdate: any, ctx: any) {
@@ -1276,44 +1478,89 @@ export function registerTeamTools(pi: any, options: TeamToolsOptions): TeamTools
1276
1478
  if (!ctx) throw new Error("No active Pi session context is available for spawn_swarm_agents.");
1277
1479
  if (!Array.isArray(params.agents) || params.agents.length === 0) throw new Error("spawn_swarm_agents requires at least one agent.");
1278
1480
 
1279
- const mergedAgents = params.agents.map((agent: any) => mergeSwarmAgentParams(params.defaults || {}, agent));
1481
+ const groupPolicy = normalizeCompletionGroupPolicy(params.completion_group);
1482
+ const sessionId = getPiSessionId(ctx);
1483
+ if (groupPolicy && (!sessionId || !_toolCallId?.trim())) throw new Error("Completion groups require a bound session and runtime submission identity.");
1484
+ const mergedAgents = params.agents.map((agent: any, index: number) => {
1485
+ const merged = mergeSwarmAgentParams(params.defaults || {}, agent);
1486
+ merged.checkpoint = createCheckpointAssignment(merged.checkpoint, merged.prompt, merged.continue_from)?.policy;
1487
+ if (merged.continue_from !== undefined) merged.name = freshContinuationName(merged.name || "agent", JSON.stringify([sessionId, _toolCallId, index, merged.continue_from]));
1488
+ return merged;
1489
+ });
1280
1490
  const defaultModel = configuredFavoriteModelForSpawn(mergedAgents[0], ctx, "spawn_swarm_agents");
1281
1491
  for (let index = 0; index < mergedAgents.length; index += 1) {
1282
1492
  configuredFavoriteModelForSpawn(mergedAgents[index], ctx, `spawn_swarm_agents agent ${mergedAgents[index].name || index + 1}`);
1283
1493
  }
1284
1494
  const sessionName = await ensureCurrentSessionAgentGroup(ctx, defaultModel);
1495
+ let group: CompletionGroup | undefined;
1496
+ if (groupPolicy && sessionId) {
1497
+ const identity = { teamName: sessionName, sessionId, submissionId: `tool:${_toolCallId}` };
1498
+ const groupId = completionGroupIdentity(identity);
1499
+ options.onCompletionGroupUse?.(groupId);
1500
+ const config = await teams.readConfig(sessionName);
1501
+ const reports = await listStoredTeamReportEvents(sessionName);
1502
+ const inbox = await messaging.readInbox(sessionName, "team-lead", false, false);
1503
+ const entries = ctx.sessionManager?.getBranch?.() ?? [];
1504
+ const references = [...config.members.map(member => member.completionGroup),
1505
+ ...readQueue(sessionName).map(queued => queued.member.completionGroup),
1506
+ ...reports.map(report => report.completionGroup), ...inbox.map(message => message.metadata?.completionGroup),
1507
+ ...entries.map((entry: any) => entry.message?.details?.completionGroup)];
1508
+ if (references.some(binding => binding?.groupId === groupId)) new CompletionGroup(sessionName, groupId).read();
1509
+ group = await CompletionGroup.create({ ...identity, policy: groupPolicy, members: mergedAgents.map((agent: any, index: number) => ({
1510
+ name: paths.sanitizeName(agent.name || `agent-${crypto.createHash("sha256").update(`${_toolCallId}:${index}`).digest("hex").slice(0, 12)}`),
1511
+ assignmentKey: crypto.createHash("sha256").update(JSON.stringify([agent, ctx.cwd])).digest("hex"),
1512
+ suppressed: sessionName.startsWith("prompt-build-") || shouldSuppressLeadReportInjection(agent),
1513
+ })) });
1514
+ if (group && !group.created) return {
1515
+ content: [{ type: "text", text: `Completion group ${group.groupId} already exists; no assignments replayed. State: ${group.journalPath}\n${AGENT_WAIT_CONTRACT}` }],
1516
+ details: { session: sessionName, completionGroup: { groupId: group.groupId, journalPath: group.journalPath },
1517
+ idempotent: true, spawned: [], failed: [], members: group.read().members },
1518
+ };
1519
+ }
1285
1520
  const spawned: any[] = [];
1286
1521
  const failed: Array<{ name: string; error: string }> = [];
1287
1522
 
1288
1523
  for (let index = 0; index < mergedAgents.length; index += 1) {
1289
1524
  const merged = mergedAgents[index];
1290
- const name = merged.name || generatedAgentName(index);
1525
+ const name = group?.read().members[index].name || merged.name || generatedAgentName(index);
1291
1526
  try {
1292
1527
  const result = await spawnTeammate({
1293
1528
  ...merged,
1294
1529
  name,
1295
1530
  team_name: sessionName,
1296
- }, ctx, { allowNestedReadAgents: merged.allow_nested_read_agents === true });
1531
+ }, ctx, { allowNestedReadAgents: merged.allow_nested_read_agents === true, completionGroup: group?.binding(index), signal: _signal });
1297
1532
  spawned.push({ ...result.details, name });
1298
1533
  } catch (error) {
1299
- failed.push({ name, error: error instanceof Error ? error.message : String(error) });
1534
+ const message = error instanceof Error ? error.message : String(error);
1535
+ failed.push({ name, error: message });
1536
+ if (group) await group.apply({ type: "rejected", ...group.binding(index), runId: group.read().members[index].runId, reason: message });
1300
1537
  }
1301
1538
  }
1539
+ if (group) {
1540
+ await group.seal();
1541
+ await enqueueCompletionGroupDeliveries(group);
1542
+ }
1302
1543
 
1303
1544
  const lines = [`Spawned ${spawned.length}/${params.agents.length} agents in the current Pi session.`];
1304
1545
  for (const item of spawned) lines.push(`- ${item.name}: ${item.queued ? `queued at position ${item.queuePosition}` : `${item.role}, ${item.mode || "in-process"}`}`);
1305
1546
  for (const item of failed) lines.push(`- ${item.name}: failed — ${item.error}`);
1306
1547
  lines.push(AGENT_WAIT_CONTRACT);
1307
- return { content: [{ type: "text", text: lines.join("\n") }], details: { session: sessionName, spawned, failed } };
1548
+ return { content: [{ type: "text", text: lines.join("\n") }], details: { session: sessionName, spawned, failed,
1549
+ ...(group ? { completionGroup: { groupId: group.groupId, journalPath: group.journalPath } } : {}) } };
1308
1550
  },
1309
1551
  });
1310
1552
 
1311
1553
  return {
1312
1554
  createNestedReadAgentTools,
1555
+ nestedChildSnapshot,
1313
1556
  cancelQueuedAgent: (teamName, agentName) => {
1314
1557
  // Retain the entry for admission bookkeeping, but let active teardown own cancellation after launch commits.
1315
1558
  if (readQueue(teamName).some(queued => queued.member.name === agentName && queued.launchCommitted)) return false;
1316
- return removeQueuedReadSpawnsByName(teamName, agentName).length > 0;
1559
+ const pending = cancelCheckpointAdmission(teamName, agentName);
1560
+ const removed = removeQueuedReadSpawnsByName(teamName, agentName);
1561
+ return pending || removed.some(item => item.groupSettlement)
1562
+ ? Promise.all([pending, ...removed.map(item => item.groupSettlement)]).then(() => !!pending || removed.length > 0)
1563
+ : removed.length > 0;
1317
1564
  },
1318
1565
  };
1319
1566
  }