@ian-pascoe/pi-minimal-subagents 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -12
- package/package.json +1 -1
- package/src/minimal-subagents-capabilities.ts +1 -12
- package/src/minimal-subagents-config.ts +5 -16
- package/src/minimal-subagents-coordinator.ts +32 -151
- package/src/minimal-subagents-delivery-ledger.ts +13 -21
- package/src/minimal-subagents-extension.ts +0 -9
- package/src/minimal-subagents-registry.ts +1 -24
- package/src/minimal-subagents-render-contract.ts +12 -32
- package/src/minimal-subagents-rendering.ts +42 -126
- package/src/minimal-subagents-session-wire.ts +6 -0
- package/src/minimal-subagents-sessions.ts +25 -39
- package/src/minimal-subagents-tools.ts +5 -26
- package/src/minimal-subagents-types.ts +9 -16
package/README.md
CHANGED
|
@@ -141,22 +141,24 @@ the complete read-only discovery bundle.
|
|
|
141
141
|
`agent_message` reports whether a message was delivered through an active
|
|
142
142
|
parent wait, queued for the recipient, or failed. `subagent_wait` can return an
|
|
143
143
|
intermediate Wait Event containing a Coordination Message before the child turn
|
|
144
|
-
settles
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
settles. That event claims only its message, so later unconsumed messages and the
|
|
145
|
+
terminal result retain automatic fallback. If the turn has already settled, one
|
|
146
|
+
wait returns its terminal result with queued messages in `messages`. Pass
|
|
147
|
+
optional `turn_id` to address an older retained turn exactly. Without it, waits
|
|
148
|
+
select the oldest observable claimed or pending turn before the active/latest
|
|
149
|
+
turn. A caller may have only one outstanding wait for the same source turn; a
|
|
150
|
+
concurrent duplicate is rejected instead of competing for one Wait Event.
|
|
149
151
|
|
|
150
152
|
The persisted Delivery Ledger records Coordination Messages, terminal results,
|
|
151
153
|
globally increasing sequence, and wait ownership before delivery. Existing
|
|
152
154
|
items retain their sequence; gaps from skipped malformed records are valid.
|
|
153
|
-
Claims can name only active, latest, or retained turns.
|
|
154
|
-
|
|
155
|
-
reloads, forks, and newer turns. Automatic fallback retains its ordered
|
|
156
|
-
reservation
|
|
157
|
-
|
|
158
|
-
Evidence settles and compacts ledger items, preventing duplicate delivery
|
|
159
|
-
unbounded checkpoint growth. The pure Delivery Ledger state machine retains at
|
|
155
|
+
Claims can name only active, latest, or retained turns. Wait-returned messages
|
|
156
|
+
retain individual delivery evidence; terminal wait ownership remains durable
|
|
157
|
+
across reloads, forks, and newer turns. Automatic fallback retains its ordered
|
|
158
|
+
queue reservation and sends every unclaimed Coordination Message and terminal
|
|
159
|
+
result as a Pi steer, including while the recipient is active. Destination-session
|
|
160
|
+
Delivery Evidence settles and compacts ledger items, preventing duplicate delivery
|
|
161
|
+
and unbounded checkpoint growth. The pure Delivery Ledger state machine retains at
|
|
160
162
|
most 20 pending wait-only terminal results per source agent; Coordination
|
|
161
163
|
Messages are not removed by that terminal-retention limit. Delivered messages
|
|
162
164
|
include stable delivery, source-agent, and source-turn identities in persisted
|
package/package.json
CHANGED
|
@@ -36,18 +36,7 @@ export function buildEligibleModelIds(input: {
|
|
|
36
36
|
const source = scopeConfigured
|
|
37
37
|
? input.scopedModels.map((entry) => entry.model)
|
|
38
38
|
: input.availableModels;
|
|
39
|
-
|
|
40
|
-
const result: string[] = [];
|
|
41
|
-
|
|
42
|
-
for (const model of source) {
|
|
43
|
-
const canonicalId = `${model.provider}/${model.id}`;
|
|
44
|
-
if (!seen.has(canonicalId)) {
|
|
45
|
-
seen.add(canonicalId);
|
|
46
|
-
result.push(canonicalId);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return result;
|
|
39
|
+
return [...new Set(source.map(({ provider, id }) => `${provider}/${id}`))];
|
|
51
40
|
}
|
|
52
41
|
|
|
53
42
|
/** Supplies inherited tools, the ancestor ceiling, and runtime availability for exact tool resolution. */
|
|
@@ -51,8 +51,8 @@ interface MinimalSubagentsSettingsDocument {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
interface MinimalSubagentsConfigInput {
|
|
54
|
-
globalSettings:
|
|
55
|
-
projectSettings:
|
|
54
|
+
globalSettings: MinimalSubagentsSettingsDocumentInput;
|
|
55
|
+
projectSettings: MinimalSubagentsSettingsDocumentInput;
|
|
56
56
|
eligibleModelIds: readonly string[];
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -126,19 +126,8 @@ function parseModelRolesWireValue(value: JsonValue): ModelRolesWireValue {
|
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
function parsePiSettingsDocument(
|
|
130
|
-
settings: MinimalSubagentsSettingsDocumentInput,
|
|
131
|
-
): MinimalSubagentsSettingsDocument {
|
|
132
|
-
if (!Value.Check(SettingsDocumentSchema, settings)) return {};
|
|
133
|
-
const parsed: MinimalSubagentsSettingsDocument = {};
|
|
134
|
-
if (settings.minimalSubagents !== undefined) {
|
|
135
|
-
parsed.minimalSubagents = settings.minimalSubagents;
|
|
136
|
-
}
|
|
137
|
-
return parsed;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
129
|
function readMinimalSubagentsSettings(
|
|
141
|
-
settings:
|
|
130
|
+
settings: MinimalSubagentsSettingsDocumentInput,
|
|
142
131
|
scope: SettingsScope,
|
|
143
132
|
warnings: string[],
|
|
144
133
|
): ParsedMinimalSubagentsSettings {
|
|
@@ -359,8 +348,8 @@ export function resolveMinimalSubagentsSettings(
|
|
|
359
348
|
eligibleModelIds: readonly string[],
|
|
360
349
|
): ResolvedMinimalSubagentsConfig {
|
|
361
350
|
return resolveMinimalSubagentsConfig({
|
|
362
|
-
globalSettings:
|
|
363
|
-
projectSettings:
|
|
351
|
+
globalSettings: settings.getGlobalSettings(),
|
|
352
|
+
projectSettings: settings.getProjectSettings(),
|
|
364
353
|
eligibleModelIds,
|
|
365
354
|
});
|
|
366
355
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
3
2
|
import { assembleImportedContext, contextContainsImages } from "./minimal-subagents-context.js";
|
|
4
3
|
import {
|
|
5
4
|
canAgentContractSpawn,
|
|
@@ -54,6 +53,7 @@ import type {
|
|
|
54
53
|
StatusResult,
|
|
55
54
|
TurnId,
|
|
56
55
|
TurnResult,
|
|
56
|
+
WaitMessageResult,
|
|
57
57
|
WaitResult,
|
|
58
58
|
} from "./minimal-subagents-types.js";
|
|
59
59
|
|
|
@@ -86,11 +86,6 @@ interface PendingParentMessage {
|
|
|
86
86
|
cancelGrace?: () => void;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
interface CancelableWait {
|
|
90
|
-
promise: Promise<void>;
|
|
91
|
-
cancel: () => void;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
89
|
function agentDeliveryKey(agentId: string, turnId: string): string {
|
|
95
90
|
return `${agentId}\u0000${turnId}`;
|
|
96
91
|
}
|
|
@@ -110,8 +105,9 @@ function terminalTurnResult(
|
|
|
110
105
|
};
|
|
111
106
|
}
|
|
112
107
|
|
|
113
|
-
function terminalWaitResult(result: TurnResult): WaitResult {
|
|
114
|
-
|
|
108
|
+
function terminalWaitResult(result: TurnResult, messages: WaitMessageResult[] = []): WaitResult {
|
|
109
|
+
const terminal = { event: "turn" as const, ...structuredClone(result) };
|
|
110
|
+
return messages.length === 0 ? terminal : { ...terminal, messages: structuredClone(messages) };
|
|
115
111
|
}
|
|
116
112
|
|
|
117
113
|
/** One root-owned coordinator for persistent nested Pi child sessions. */
|
|
@@ -119,18 +115,15 @@ export class MinimalSubagentsCoordinator {
|
|
|
119
115
|
private readonly agents = new Map<string, PersistedAgent>();
|
|
120
116
|
private readonly runtimes = new Map<string, ChildAgentRuntime>();
|
|
121
117
|
private readonly runtimeInitializations = new Map<string, Promise<ChildAgentRuntime>>();
|
|
122
|
-
private readonly importedMessages = new Map<string, AgentMessage[]>();
|
|
123
118
|
private readonly tombstones = new Set<string>();
|
|
124
119
|
private readonly pendingAgentIds = new Set<string>();
|
|
125
120
|
private deliveryLedger: DeliveryLedger = createDeliveryLedger();
|
|
126
121
|
private readonly waiters = new Map<string, Set<TurnWaiter>>();
|
|
127
122
|
private readonly pendingParentMessages = new Map<string, PendingParentMessage[]>();
|
|
128
123
|
private readonly recipientQueues = new Map<string, Promise<unknown>>();
|
|
129
|
-
private readonly recipientIdleWaiters = new Map<string, Set<() => void>>();
|
|
130
124
|
private readonly automaticDeliveryKeys = new Set<string>();
|
|
131
125
|
private readonly automaticCoordinationDeliveryIds = new Set<string>();
|
|
132
126
|
private readonly waitHandedDeliveryIds = new Set<string>();
|
|
133
|
-
private readonly automaticDeliveryClaimWaiters = new Map<string, Set<() => void>>();
|
|
134
127
|
private readonly backgroundOperations = new Set<Promise<void>>();
|
|
135
128
|
private acceptingOperations = true;
|
|
136
129
|
private lifecycleEpoch = 0;
|
|
@@ -252,7 +245,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
252
245
|
agent.session_id = identity.sessionId;
|
|
253
246
|
agent.session_leaf_id = identity.sessionLeafId;
|
|
254
247
|
this.agents.set(agentId, agent);
|
|
255
|
-
this.importedMessages.set(agentId, imported.messages);
|
|
256
248
|
this.dependencies.registry.append(
|
|
257
249
|
createRegistryEvent(this.dependencies.registry.rootSessionId, "agent-created", { agent }),
|
|
258
250
|
);
|
|
@@ -366,15 +358,16 @@ export class MinimalSubagentsCoordinator {
|
|
|
366
358
|
new Error(`Minimal subagents duplicate wait: ${callerId} is already waiting for ${turnId}`),
|
|
367
359
|
);
|
|
368
360
|
}
|
|
369
|
-
const pendingMessage = this.claimPendingParentMessage(callerId, agentId, turnId);
|
|
370
|
-
if (pendingMessage) return Promise.resolve(pendingMessage);
|
|
371
361
|
const retainedResult =
|
|
372
362
|
findTerminalDelivery(this.deliveryLedger, agentId, turnId)?.result ??
|
|
373
363
|
(agent.latest_result?.turn_id === turnId ? agent.latest_result : undefined);
|
|
374
364
|
if (retainedResult) {
|
|
365
|
+
const messages = this.drainPendingParentMessages(callerId, agentId, turnId);
|
|
375
366
|
this.claimTerminalDelivery(callerId, retainedResult);
|
|
376
|
-
return Promise.resolve(terminalWaitResult(retainedResult));
|
|
367
|
+
return Promise.resolve(terminalWaitResult(retainedResult, messages));
|
|
377
368
|
}
|
|
369
|
+
const pendingMessage = this.claimPendingParentMessage(callerId, agentId, turnId);
|
|
370
|
+
if (pendingMessage) return Promise.resolve(pendingMessage);
|
|
378
371
|
if (agent.active_turn_id !== turnId) {
|
|
379
372
|
return Promise.reject(
|
|
380
373
|
new Error(`Minimal subagents wait: turn ${turnId} is no longer retained for ${agentId}`),
|
|
@@ -505,7 +498,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
505
498
|
result.trashed_session_files.push(agent.session_file);
|
|
506
499
|
}
|
|
507
500
|
this.agents.delete(agent.agent_id);
|
|
508
|
-
this.importedMessages.delete(agent.agent_id);
|
|
509
501
|
this.pruneDeliveryStateForDeletedAgent(agent.agent_id);
|
|
510
502
|
this.pruneRecentMessageProjectionsForDeletedAgent(agent.agent_id);
|
|
511
503
|
this.tombstones.add(agent.agent_id);
|
|
@@ -520,10 +512,7 @@ export class MinimalSubagentsCoordinator {
|
|
|
520
512
|
failedAncestors.add(agent.agent_id);
|
|
521
513
|
if (runtime && agent.session_file) {
|
|
522
514
|
try {
|
|
523
|
-
this.runtimes.set(
|
|
524
|
-
agent.agent_id,
|
|
525
|
-
await this.dependencies.sessions.restoreRuntime(agent),
|
|
526
|
-
);
|
|
515
|
+
this.runtimes.set(agent.agent_id, await this.dependencies.sessions.openRuntime(agent));
|
|
527
516
|
} catch (restoreError) {
|
|
528
517
|
agent.availability = "unavailable";
|
|
529
518
|
agent.unavailable_reason = `Deletion recovery failed: ${
|
|
@@ -550,7 +539,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
550
539
|
this.agents.clear();
|
|
551
540
|
this.deliveryLedger = createDeliveryLedger();
|
|
552
541
|
this.pendingParentMessages.clear();
|
|
553
|
-
this.releaseAllRecipientIdleWaiters();
|
|
554
542
|
this.recipientQueues.clear();
|
|
555
543
|
this.backgroundOperations.clear();
|
|
556
544
|
await Promise.allSettled(
|
|
@@ -559,19 +547,16 @@ export class MinimalSubagentsCoordinator {
|
|
|
559
547
|
for (const runtime of abandonedRuntimes) runtime.dispose();
|
|
560
548
|
this.runtimes.clear();
|
|
561
549
|
this.runtimeInitializations.clear();
|
|
562
|
-
this.importedMessages.clear();
|
|
563
550
|
this.pendingAgentIds.clear();
|
|
564
551
|
this.tombstones.clear();
|
|
565
552
|
this.deliveryLedger = createDeliveryLedger();
|
|
566
553
|
this.waiters.clear();
|
|
567
554
|
this.pendingParentMessages.clear();
|
|
568
555
|
this.waitHandedDeliveryIds.clear();
|
|
569
|
-
this.releaseAllRecipientIdleWaiters();
|
|
570
556
|
this.recipientQueues.clear();
|
|
571
557
|
this.backgroundOperations.clear();
|
|
572
558
|
this.automaticDeliveryKeys.clear();
|
|
573
559
|
this.automaticCoordinationDeliveryIds.clear();
|
|
574
|
-
this.automaticDeliveryClaimWaiters.clear();
|
|
575
560
|
this.deliveryLedger = createDeliveryLedger({
|
|
576
561
|
deliveries: snapshot.deliveries,
|
|
577
562
|
coordination_deliveries: snapshot.coordination_deliveries,
|
|
@@ -623,7 +608,7 @@ export class MinimalSubagentsCoordinator {
|
|
|
623
608
|
});
|
|
624
609
|
continue;
|
|
625
610
|
}
|
|
626
|
-
const runtime = await this.dependencies.sessions.
|
|
611
|
+
const runtime = await this.dependencies.sessions.openRuntime(agent);
|
|
627
612
|
if (restoreEpoch !== this.lifecycleEpoch) {
|
|
628
613
|
runtime.dispose();
|
|
629
614
|
return;
|
|
@@ -728,13 +713,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
728
713
|
await Promise.allSettled(scheduled);
|
|
729
714
|
}
|
|
730
715
|
|
|
731
|
-
/** Release ordered automatic deliveries after one recipient conversation becomes idle. */
|
|
732
|
-
markRecipientIdle(agentId: string): void {
|
|
733
|
-
const waiters = this.recipientIdleWaiters.get(agentId);
|
|
734
|
-
this.recipientIdleWaiters.delete(agentId);
|
|
735
|
-
for (const resolve of waiters ?? []) resolve();
|
|
736
|
-
}
|
|
737
|
-
|
|
738
716
|
/** Clone complete child leaves for root fork ownership without ever sharing source session paths. */
|
|
739
717
|
async prepareFork(sourceRootSessionFile: string): Promise<ForkSnapshot> {
|
|
740
718
|
const activeRootChildren = this.childrenOf("root");
|
|
@@ -793,7 +771,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
793
771
|
shutdown(): Promise<void> {
|
|
794
772
|
if (this.shutdownPromise) return this.shutdownPromise;
|
|
795
773
|
this.acceptingOperations = false;
|
|
796
|
-
this.releaseAllRecipientIdleWaiters();
|
|
797
774
|
this.shutdownPromise = this.finishShutdown();
|
|
798
775
|
return this.shutdownPromise;
|
|
799
776
|
}
|
|
@@ -902,19 +879,14 @@ export class MinimalSubagentsCoordinator {
|
|
|
902
879
|
new Error(agent.clone_error ?? `No persistent session exists for ${agent.agent_id}`),
|
|
903
880
|
);
|
|
904
881
|
}
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
importedMessages
|
|
908
|
-
? this.dependencies.sessions.createRuntime({ agent, importedMessages })
|
|
909
|
-
: this.dependencies.sessions.restoreRuntime(agent)
|
|
910
|
-
)
|
|
882
|
+
const initialization = this.dependencies.sessions
|
|
883
|
+
.openRuntime(agent)
|
|
911
884
|
.then((runtime) => {
|
|
912
885
|
if (this.agents.get(agent.agent_id) !== agent) {
|
|
913
886
|
runtime.dispose();
|
|
914
887
|
throw new Error(`Minimal subagents runtime replaced while opening ${agent.agent_id}`);
|
|
915
888
|
}
|
|
916
889
|
this.runtimes.set(agent.agent_id, runtime);
|
|
917
|
-
this.importedMessages.delete(agent.agent_id);
|
|
918
890
|
return runtime;
|
|
919
891
|
})
|
|
920
892
|
.finally(() => {
|
|
@@ -1012,7 +984,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
1012
984
|
});
|
|
1013
985
|
}
|
|
1014
986
|
if (result.status !== "completed") this.removeSettledEmptyTurnClaim(agent.agent_id, turnId);
|
|
1015
|
-
this.markRecipientIdle(agent.agent_id);
|
|
1016
987
|
}
|
|
1017
988
|
|
|
1018
989
|
private async deliverAutomaticResult(
|
|
@@ -1037,15 +1008,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
1037
1008
|
this.settleDelivery(delivery);
|
|
1038
1009
|
return;
|
|
1039
1010
|
}
|
|
1040
|
-
while (!this.isRecipientIdle(delivery.destination_agent_id)) {
|
|
1041
|
-
const idleWait = this.createRecipientIdleWait(delivery.destination_agent_id);
|
|
1042
|
-
const claimWait = this.createAutomaticDeliveryClaimWait(deliveryKey);
|
|
1043
|
-
await Promise.race([idleWait.promise, claimWait.promise]);
|
|
1044
|
-
idleWait.cancel();
|
|
1045
|
-
claimWait.cancel();
|
|
1046
|
-
if (!this.acceptingOperations || this.shouldStopAutomaticTerminalDelivery(delivery))
|
|
1047
|
-
return;
|
|
1048
|
-
}
|
|
1049
1011
|
if (!this.acceptingOperations || this.shouldStopAutomaticTerminalDelivery(delivery)) return;
|
|
1050
1012
|
if (this.hasDeliveryEvidence(delivery)) {
|
|
1051
1013
|
this.settleDelivery(delivery);
|
|
@@ -1064,11 +1026,8 @@ export class MinimalSubagentsCoordinator {
|
|
|
1064
1026
|
usage: result.usage,
|
|
1065
1027
|
},
|
|
1066
1028
|
};
|
|
1067
|
-
await this.deliverToRecipient(
|
|
1068
|
-
delivery
|
|
1069
|
-
message,
|
|
1070
|
-
() => this.isTerminalDeliveryCurrent(delivery),
|
|
1071
|
-
true,
|
|
1029
|
+
await this.deliverToRecipient(delivery.destination_agent_id, message, () =>
|
|
1030
|
+
this.isTerminalDeliveryCurrent(delivery),
|
|
1072
1031
|
);
|
|
1073
1032
|
});
|
|
1074
1033
|
} catch (error) {
|
|
@@ -1276,7 +1235,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
1276
1235
|
targetId: string,
|
|
1277
1236
|
message: CoordinatorMessage,
|
|
1278
1237
|
isCurrentDelivery: () => boolean = () => true,
|
|
1279
|
-
requireIdleRecipient = false,
|
|
1280
1238
|
): Promise<void> {
|
|
1281
1239
|
if (!isCurrentDelivery()) {
|
|
1282
1240
|
throw new Error("Minimal subagents delivery abandoned after session branch change");
|
|
@@ -1294,9 +1252,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
1294
1252
|
throw new Error("Minimal subagents delivery abandoned after session branch change");
|
|
1295
1253
|
}
|
|
1296
1254
|
const visibleMessage = addCoordinatorMessageEnvelope(message);
|
|
1297
|
-
if (requireIdleRecipient && (target.active_turn_id || runtime.isRunning)) {
|
|
1298
|
-
throw new Error(`Minimal subagents automatic delivery recipient became active: ${targetId}`);
|
|
1299
|
-
}
|
|
1300
1255
|
if (target.active_turn_id || runtime.isRunning) {
|
|
1301
1256
|
await runtime.queueCoordinatorMessage(visibleMessage);
|
|
1302
1257
|
return;
|
|
@@ -1380,8 +1335,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
1380
1335
|
private applyDeliveryLedgerTransition(transition: DeliveryLedgerTransition): void {
|
|
1381
1336
|
this.deliveryLedger = transition.ledger;
|
|
1382
1337
|
for (const delivery of transition.prunedTerminalDeliveries) {
|
|
1383
|
-
const key = agentDeliveryKey(delivery.source_agent_id, delivery.source_turn_id);
|
|
1384
|
-
this.releaseAutomaticDeliveryClaimWaiters(key);
|
|
1385
1338
|
this.dependencies.registry.append(
|
|
1386
1339
|
createRegistryEvent(this.dependencies.registry.rootSessionId, "delivery-pruned", {
|
|
1387
1340
|
source_agent_id: delivery.source_agent_id,
|
|
@@ -1517,16 +1470,8 @@ export class MinimalSubagentsCoordinator {
|
|
|
1517
1470
|
}
|
|
1518
1471
|
|
|
1519
1472
|
private pruneDeliveryStateForDeletedAgent(agentId: string): void {
|
|
1520
|
-
const previousTerminalDeliveries = this.deliveryLedger.terminalDeliveries;
|
|
1521
1473
|
const previousCoordinationDeliveries = this.deliveryLedger.coordinationDeliveries;
|
|
1522
1474
|
this.deliveryLedger = pruneDeliveryLedgerAgents(this.deliveryLedger, [agentId]).ledger;
|
|
1523
|
-
for (const delivery of previousTerminalDeliveries) {
|
|
1524
|
-
if (!this.isTerminalDeliveryCurrent(delivery)) {
|
|
1525
|
-
this.releaseAutomaticDeliveryClaimWaiters(
|
|
1526
|
-
agentDeliveryKey(delivery.source_agent_id, delivery.source_turn_id),
|
|
1527
|
-
);
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
1475
|
for (const delivery of previousCoordinationDeliveries) {
|
|
1531
1476
|
if (!this.isCoordinationDeliveryCurrent(delivery)) {
|
|
1532
1477
|
this.waitHandedDeliveryIds.delete(delivery.delivery_id);
|
|
@@ -1686,7 +1631,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
1686
1631
|
(candidate) => candidate.callerId === destinationAgentId,
|
|
1687
1632
|
);
|
|
1688
1633
|
if (!waiter) return false;
|
|
1689
|
-
this.claimDeliveryTurn(message.details.source_agent_id, message.details.source_turn_id);
|
|
1690
1634
|
this.deliveryLedger = setCoordinationDeliveryPath(
|
|
1691
1635
|
this.deliveryLedger,
|
|
1692
1636
|
delivery.delivery_id,
|
|
@@ -1711,7 +1655,7 @@ export class MinimalSubagentsCoordinator {
|
|
|
1711
1655
|
callerId: string,
|
|
1712
1656
|
sourceAgentId: string,
|
|
1713
1657
|
sourceTurnId: string,
|
|
1714
|
-
):
|
|
1658
|
+
): WaitMessageResult | undefined {
|
|
1715
1659
|
const key = agentDeliveryKey(sourceAgentId, sourceTurnId);
|
|
1716
1660
|
const pendingMessages = this.pendingParentMessages.get(key);
|
|
1717
1661
|
const index = pendingMessages?.findIndex(
|
|
@@ -1728,7 +1672,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
1728
1672
|
)
|
|
1729
1673
|
.sort((left, right) => left.sequence - right.sequence)[0];
|
|
1730
1674
|
if (!retained) return undefined;
|
|
1731
|
-
this.claimDeliveryTurn(sourceAgentId, sourceTurnId);
|
|
1732
1675
|
this.deliveryLedger = setCoordinationDeliveryPath(
|
|
1733
1676
|
this.deliveryLedger,
|
|
1734
1677
|
retained.delivery_id,
|
|
@@ -1748,7 +1691,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
1748
1691
|
}
|
|
1749
1692
|
const pending = pendingMessages[index];
|
|
1750
1693
|
if (!pending) return undefined;
|
|
1751
|
-
this.claimDeliveryTurn(sourceAgentId, sourceTurnId);
|
|
1752
1694
|
const delivery = findCoordinationDelivery(this.deliveryLedger, pending.deliveryId);
|
|
1753
1695
|
if (delivery) {
|
|
1754
1696
|
this.deliveryLedger = setCoordinationDeliveryPath(
|
|
@@ -1760,10 +1702,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
1760
1702
|
if (currentDelivery) this.persistCoordinationDeliveryState(currentDelivery);
|
|
1761
1703
|
this.waitHandedDeliveryIds.add(delivery.delivery_id);
|
|
1762
1704
|
}
|
|
1763
|
-
const sourceResult = this.agents.get(sourceAgentId)?.latest_result;
|
|
1764
|
-
if (sourceResult?.turn_id === sourceTurnId) {
|
|
1765
|
-
this.setTerminalDeliveryPathToWait(callerId, sourceResult);
|
|
1766
|
-
}
|
|
1767
1705
|
pending.claimed = true;
|
|
1768
1706
|
pending.cancelGrace?.();
|
|
1769
1707
|
pending.releaseClaim();
|
|
@@ -1779,6 +1717,20 @@ export class MinimalSubagentsCoordinator {
|
|
|
1779
1717
|
};
|
|
1780
1718
|
}
|
|
1781
1719
|
|
|
1720
|
+
private drainPendingParentMessages(
|
|
1721
|
+
callerId: string,
|
|
1722
|
+
sourceAgentId: string,
|
|
1723
|
+
sourceTurnId: string,
|
|
1724
|
+
): WaitMessageResult[] {
|
|
1725
|
+
const messages: WaitMessageResult[] = [];
|
|
1726
|
+
let message = this.claimPendingParentMessage(callerId, sourceAgentId, sourceTurnId);
|
|
1727
|
+
while (message) {
|
|
1728
|
+
messages.push(message);
|
|
1729
|
+
message = this.claimPendingParentMessage(callerId, sourceAgentId, sourceTurnId);
|
|
1730
|
+
}
|
|
1731
|
+
return messages;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1782
1734
|
private queuePendingParentMessage(
|
|
1783
1735
|
targetId: string,
|
|
1784
1736
|
message: CoordinatorMessage,
|
|
@@ -1791,10 +1743,7 @@ export class MinimalSubagentsCoordinator {
|
|
|
1791
1743
|
message.details.source_agent_id,
|
|
1792
1744
|
message.details.source_turn_id,
|
|
1793
1745
|
);
|
|
1794
|
-
|
|
1795
|
-
const claimPromise = new Promise<void>((resolve) => {
|
|
1796
|
-
releaseClaim = resolve;
|
|
1797
|
-
});
|
|
1746
|
+
const { promise: claimPromise, resolve: releaseClaim } = Promise.withResolvers<void>();
|
|
1798
1747
|
const pending: PendingParentMessage = {
|
|
1799
1748
|
deliveryId: delivery.delivery_id,
|
|
1800
1749
|
message,
|
|
@@ -1837,20 +1786,11 @@ export class MinimalSubagentsCoordinator {
|
|
|
1837
1786
|
pending.cancelGrace?.();
|
|
1838
1787
|
pending.cancelGrace = undefined;
|
|
1839
1788
|
if (!this.acceptingOperations || pending.claimed || turnClaimed()) return;
|
|
1840
|
-
while (!this.isRecipientIdle(targetId)) {
|
|
1841
|
-
const idleWait = this.createRecipientIdleWait(targetId);
|
|
1842
|
-
await Promise.race([pending.claimPromise, idleWait.promise]);
|
|
1843
|
-
idleWait.cancel();
|
|
1844
|
-
if (!this.acceptingOperations || pending.claimed || turnClaimed()) return;
|
|
1845
|
-
}
|
|
1846
1789
|
if (!this.acceptingOperations || pending.claimed || turnClaimed()) return;
|
|
1847
1790
|
this.removePendingParentMessage(key, pending);
|
|
1848
1791
|
this.waitHandedDeliveryIds.add(delivery.delivery_id);
|
|
1849
|
-
await this.deliverToRecipient(
|
|
1850
|
-
|
|
1851
|
-
message,
|
|
1852
|
-
() => this.isCoordinationDeliveryCurrent(delivery),
|
|
1853
|
-
true,
|
|
1792
|
+
await this.deliverToRecipient(targetId, message, () =>
|
|
1793
|
+
this.isCoordinationDeliveryCurrent(delivery),
|
|
1854
1794
|
);
|
|
1855
1795
|
});
|
|
1856
1796
|
void operation.catch((cause) => {
|
|
@@ -1890,14 +1830,12 @@ export class MinimalSubagentsCoordinator {
|
|
|
1890
1830
|
}
|
|
1891
1831
|
|
|
1892
1832
|
private setTerminalDeliveryPathToWait(callerId: string, result: TurnResult): void {
|
|
1893
|
-
const key = agentDeliveryKey(result.agent_id, result.turn_id);
|
|
1894
1833
|
const delivery = findTerminalDelivery(this.deliveryLedger, result.agent_id, result.turn_id);
|
|
1895
1834
|
if (!delivery || delivery.destination_agent_id !== callerId || delivery.path === "wait") return;
|
|
1896
1835
|
this.applyDeliveryLedgerTransition(
|
|
1897
1836
|
setTerminalDeliveryPath(this.deliveryLedger, result.agent_id, result.turn_id, "wait"),
|
|
1898
1837
|
);
|
|
1899
1838
|
const retained = findTerminalDelivery(this.deliveryLedger, result.agent_id, result.turn_id);
|
|
1900
|
-
this.releaseAutomaticDeliveryClaimWaiters(key);
|
|
1901
1839
|
if (!retained) return;
|
|
1902
1840
|
this.dependencies.registry.append(
|
|
1903
1841
|
createRegistryEvent(this.dependencies.registry.rootSessionId, "delivery-pending", {
|
|
@@ -1906,63 +1844,6 @@ export class MinimalSubagentsCoordinator {
|
|
|
1906
1844
|
);
|
|
1907
1845
|
}
|
|
1908
1846
|
|
|
1909
|
-
private isRecipientIdle(agentId: string): boolean {
|
|
1910
|
-
if (agentId === "root") return this.dependencies.root.isIdle();
|
|
1911
|
-
const agent = this.agents.get(agentId);
|
|
1912
|
-
if (!agent || agent.active_turn_id) return false;
|
|
1913
|
-
return !this.runtimes.get(agentId)?.isRunning;
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
private createRecipientIdleWait(agentId: string): CancelableWait {
|
|
1917
|
-
let release!: () => void;
|
|
1918
|
-
const promise = new Promise<void>((resolve) => {
|
|
1919
|
-
release = resolve;
|
|
1920
|
-
});
|
|
1921
|
-
const waiters = this.recipientIdleWaiters.get(agentId) ?? new Set();
|
|
1922
|
-
waiters.add(release);
|
|
1923
|
-
this.recipientIdleWaiters.set(agentId, waiters);
|
|
1924
|
-
return {
|
|
1925
|
-
promise,
|
|
1926
|
-
cancel: () => {
|
|
1927
|
-
const currentWaiters = this.recipientIdleWaiters.get(agentId);
|
|
1928
|
-
currentWaiters?.delete(release);
|
|
1929
|
-
if (currentWaiters?.size === 0) this.recipientIdleWaiters.delete(agentId);
|
|
1930
|
-
},
|
|
1931
|
-
};
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
private createAutomaticDeliveryClaimWait(deliveryKey: string): CancelableWait {
|
|
1935
|
-
let release!: () => void;
|
|
1936
|
-
const promise = new Promise<void>((resolve) => {
|
|
1937
|
-
release = resolve;
|
|
1938
|
-
});
|
|
1939
|
-
const waiters = this.automaticDeliveryClaimWaiters.get(deliveryKey) ?? new Set();
|
|
1940
|
-
waiters.add(release);
|
|
1941
|
-
this.automaticDeliveryClaimWaiters.set(deliveryKey, waiters);
|
|
1942
|
-
return {
|
|
1943
|
-
promise,
|
|
1944
|
-
cancel: () => {
|
|
1945
|
-
const currentWaiters = this.automaticDeliveryClaimWaiters.get(deliveryKey);
|
|
1946
|
-
currentWaiters?.delete(release);
|
|
1947
|
-
if (currentWaiters?.size === 0) this.automaticDeliveryClaimWaiters.delete(deliveryKey);
|
|
1948
|
-
},
|
|
1949
|
-
};
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
private releaseAutomaticDeliveryClaimWaiters(deliveryKey: string): void {
|
|
1953
|
-
const waiters = this.automaticDeliveryClaimWaiters.get(deliveryKey);
|
|
1954
|
-
this.automaticDeliveryClaimWaiters.delete(deliveryKey);
|
|
1955
|
-
for (const resolve of waiters ?? []) resolve();
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
private releaseAllRecipientIdleWaiters(): void {
|
|
1959
|
-
const allWaiters = [...this.recipientIdleWaiters.values()];
|
|
1960
|
-
this.recipientIdleWaiters.clear();
|
|
1961
|
-
for (const waiters of allWaiters) {
|
|
1962
|
-
for (const resolve of waiters) resolve();
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
1847
|
private async cancelDuringShutdown(agentId: string): Promise<void> {
|
|
1967
1848
|
const target = this.agents.get(agentId);
|
|
1968
1849
|
if (!target) return;
|
|
@@ -58,23 +58,13 @@ function deliveryTurnKey(sourceAgentId: string, sourceTurnId: string): string {
|
|
|
58
58
|
return `${sourceAgentId}\u0000${sourceTurnId}`;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
function cloneTerminalDelivery(delivery: PersistedDelivery): PersistedDelivery {
|
|
62
|
-
return structuredClone(delivery);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function cloneCoordinationDelivery(
|
|
66
|
-
delivery: PersistedCoordinationDelivery,
|
|
67
|
-
): PersistedCoordinationDelivery {
|
|
68
|
-
return structuredClone(delivery);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
61
|
function createTransition(
|
|
72
62
|
ledger: DeliveryLedger,
|
|
73
63
|
prunedTerminalDeliveries: readonly PersistedDelivery[] = [],
|
|
74
64
|
): DeliveryLedgerTransition {
|
|
75
65
|
return {
|
|
76
66
|
ledger,
|
|
77
|
-
prunedTerminalDeliveries: prunedTerminalDeliveries.map(
|
|
67
|
+
prunedTerminalDeliveries: prunedTerminalDeliveries.map((delivery) => structuredClone(delivery)),
|
|
78
68
|
};
|
|
79
69
|
}
|
|
80
70
|
|
|
@@ -134,7 +124,7 @@ export function createDeliveryLedger(
|
|
|
134
124
|
): DeliveryLedger {
|
|
135
125
|
const coordinationDeliveries = (snapshot.coordination_deliveries ?? [])
|
|
136
126
|
.filter((delivery) => !delivery.settled)
|
|
137
|
-
.map(
|
|
127
|
+
.map((delivery) => structuredClone(delivery));
|
|
138
128
|
let nextSequence = Math.max(
|
|
139
129
|
1,
|
|
140
130
|
snapshot.next_delivery_sequence ?? 1,
|
|
@@ -146,7 +136,7 @@ export function createDeliveryLedger(
|
|
|
146
136
|
const terminalDeliveries = (snapshot.deliveries ?? [])
|
|
147
137
|
.filter((delivery) => !delivery.settled)
|
|
148
138
|
.map((delivery) => {
|
|
149
|
-
const restored =
|
|
139
|
+
const restored = structuredClone(delivery);
|
|
150
140
|
if (restored.sequence === undefined) restored.sequence = nextSequence++;
|
|
151
141
|
return restored;
|
|
152
142
|
});
|
|
@@ -161,8 +151,10 @@ export function createDeliveryLedger(
|
|
|
161
151
|
/** Serialize pending Delivery Ledger state without exposing mutable internal references. */
|
|
162
152
|
export function deliveryLedgerSnapshot(ledger: DeliveryLedger): DeliveryLedgerSnapshot {
|
|
163
153
|
return {
|
|
164
|
-
deliveries: ledger.terminalDeliveries.map(
|
|
165
|
-
coordination_deliveries: ledger.coordinationDeliveries.map(
|
|
154
|
+
deliveries: ledger.terminalDeliveries.map((delivery) => structuredClone(delivery)),
|
|
155
|
+
coordination_deliveries: ledger.coordinationDeliveries.map((delivery) =>
|
|
156
|
+
structuredClone(delivery),
|
|
157
|
+
),
|
|
166
158
|
wait_claimed_turns: [...ledger.waitClaimedTurns],
|
|
167
159
|
next_delivery_sequence: ledger.nextSequence,
|
|
168
160
|
};
|
|
@@ -194,7 +186,7 @@ export function addTerminalDelivery(
|
|
|
194
186
|
],
|
|
195
187
|
nextSequence: ledger.nextSequence + 1,
|
|
196
188
|
});
|
|
197
|
-
return { ...transition, delivery:
|
|
189
|
+
return { ...transition, delivery: structuredClone(delivery) };
|
|
198
190
|
}
|
|
199
191
|
|
|
200
192
|
/** Replay or update one already-sequenced terminal delivery. */
|
|
@@ -202,7 +194,7 @@ export function upsertTerminalDelivery(
|
|
|
202
194
|
ledger: DeliveryLedger,
|
|
203
195
|
delivery: PersistedDelivery,
|
|
204
196
|
): DeliveryLedgerTransition {
|
|
205
|
-
const restored =
|
|
197
|
+
const restored = structuredClone(delivery);
|
|
206
198
|
const existing = ledger.terminalDeliveries.find(
|
|
207
199
|
(current) =>
|
|
208
200
|
deliveryTurnKey(current.source_agent_id, current.source_turn_id) ===
|
|
@@ -251,7 +243,7 @@ export function addCoordinationDelivery(
|
|
|
251
243
|
],
|
|
252
244
|
nextSequence: ledger.nextSequence + 1,
|
|
253
245
|
},
|
|
254
|
-
delivery:
|
|
246
|
+
delivery: structuredClone(delivery),
|
|
255
247
|
prunedTerminalDeliveries: [],
|
|
256
248
|
};
|
|
257
249
|
}
|
|
@@ -267,7 +259,7 @@ export function upsertCoordinationDelivery(
|
|
|
267
259
|
...ledger.coordinationDeliveries.filter(
|
|
268
260
|
(current) => current.delivery_id !== delivery.delivery_id,
|
|
269
261
|
),
|
|
270
|
-
|
|
262
|
+
structuredClone(delivery),
|
|
271
263
|
],
|
|
272
264
|
nextSequence: Math.max(ledger.nextSequence, delivery.sequence + 1),
|
|
273
265
|
});
|
|
@@ -505,7 +497,7 @@ export function findTerminalDelivery(
|
|
|
505
497
|
const delivery = ledger.terminalDeliveries.find(
|
|
506
498
|
(candidate) => deliveryTurnKey(candidate.source_agent_id, candidate.source_turn_id) === key,
|
|
507
499
|
);
|
|
508
|
-
return delivery ?
|
|
500
|
+
return delivery ? structuredClone(delivery) : undefined;
|
|
509
501
|
}
|
|
510
502
|
|
|
511
503
|
/** Return one pending Coordination Message by stable delivery identity. */
|
|
@@ -516,7 +508,7 @@ export function findCoordinationDelivery(
|
|
|
516
508
|
const delivery = ledger.coordinationDeliveries.find(
|
|
517
509
|
(candidate) => candidate.delivery_id === deliveryId,
|
|
518
510
|
);
|
|
519
|
-
return delivery ?
|
|
511
|
+
return delivery ? structuredClone(delivery) : undefined;
|
|
520
512
|
}
|
|
521
513
|
|
|
522
514
|
/** Report whether one source turn currently has a durable wait claim. */
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
getAgentDir,
|
|
6
6
|
SessionManager,
|
|
7
7
|
SettingsManager,
|
|
8
|
-
type AgentSettledEvent,
|
|
9
8
|
type ExtensionAPI,
|
|
10
9
|
type ExtensionContext,
|
|
11
10
|
type ExtensionFactory,
|
|
@@ -107,7 +106,6 @@ function createRootConversationEndpoint(
|
|
|
107
106
|
sourceTurnId,
|
|
108
107
|
deliveryId,
|
|
109
108
|
),
|
|
110
|
-
isIdle: () => context.isIdle(),
|
|
111
109
|
};
|
|
112
110
|
}
|
|
113
111
|
|
|
@@ -331,7 +329,6 @@ export class MinimalSubagentsLifecycleController {
|
|
|
331
329
|
this.pi.on("session_before_fork", (event, context) => this.prepareSessionFork(event, context));
|
|
332
330
|
this.pi.on("session_tree", (event, context) => this.restoreSessionTree(event, context));
|
|
333
331
|
this.pi.on("message_end", (event, context) => this.reconcileMessageDelivery(event, context));
|
|
334
|
-
this.pi.on("agent_settled", (event, context) => this.releaseSettledRecipient(event, context));
|
|
335
332
|
this.pi.on("session_shutdown", (event, context) => this.shutdownSession(event, context));
|
|
336
333
|
}
|
|
337
334
|
|
|
@@ -554,12 +551,6 @@ export class MinimalSubagentsLifecycleController {
|
|
|
554
551
|
}
|
|
555
552
|
}
|
|
556
553
|
|
|
557
|
-
private releaseSettledRecipient(_event: AgentSettledEvent, context: ExtensionContext): void {
|
|
558
|
-
if (!this.coordinator) return;
|
|
559
|
-
if (context.isIdle()) this.coordinator.markRecipientIdle("root");
|
|
560
|
-
this.uiController?.refresh();
|
|
561
|
-
}
|
|
562
|
-
|
|
563
554
|
private async shutdownSession(
|
|
564
555
|
event: SessionShutdownEvent,
|
|
565
556
|
context: ExtensionContext,
|
|
@@ -740,12 +740,6 @@ function validateRegistrySnapshot(
|
|
|
740
740
|
}
|
|
741
741
|
const identityError = validateDeliveryIdentity(delivery, version);
|
|
742
742
|
if (identityError) return invalidRegistrySnapshot("invalid-delivery-identity", identityError);
|
|
743
|
-
if (!isOwnedTurnId(delivery.source_agent_id, delivery.source_turn_id)) {
|
|
744
|
-
return invalidRegistrySnapshot(
|
|
745
|
-
"invalid-delivery-identity",
|
|
746
|
-
"terminal source_turn_id must belong to source_agent_id",
|
|
747
|
-
);
|
|
748
|
-
}
|
|
749
743
|
const source = agentsById.get(delivery.source_agent_id);
|
|
750
744
|
if (!source) {
|
|
751
745
|
return invalidRegistrySnapshot(
|
|
@@ -763,17 +757,6 @@ function validateRegistrySnapshot(
|
|
|
763
757
|
for (const delivery of coordinationDeliveries) {
|
|
764
758
|
const identityError = validateCoordinationIdentity(delivery);
|
|
765
759
|
if (identityError) return invalidRegistrySnapshot("invalid-delivery-identity", identityError);
|
|
766
|
-
if (
|
|
767
|
-
!isOwnedTurnId(
|
|
768
|
-
delivery.message.details.source_agent_id,
|
|
769
|
-
delivery.message.details.source_turn_id,
|
|
770
|
-
)
|
|
771
|
-
) {
|
|
772
|
-
return invalidRegistrySnapshot(
|
|
773
|
-
"invalid-delivery-identity",
|
|
774
|
-
"Coordination Message source_turn_id must belong to source_agent_id",
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
760
|
if (
|
|
778
761
|
!areAdjacentAgents(
|
|
779
762
|
delivery.message.details.source_agent_id,
|
|
@@ -1228,13 +1211,7 @@ export function replayRegistryEntries(
|
|
|
1228
1211
|
}
|
|
1229
1212
|
});
|
|
1230
1213
|
|
|
1231
|
-
|
|
1232
|
-
for (let index = parsedEvents.length - 1; index >= 0; index--) {
|
|
1233
|
-
if (parsedEvents[index]?.event.event === "checkpoint") {
|
|
1234
|
-
checkpointIndex = index;
|
|
1235
|
-
break;
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1214
|
+
const checkpointIndex = parsedEvents.findLastIndex(({ event }) => event.event === "checkpoint");
|
|
1238
1215
|
const checkpointEvent = checkpointIndex >= 0 ? parsedEvents[checkpointIndex]?.event : undefined;
|
|
1239
1216
|
const checkpoint = checkpointEvent?.event === "checkpoint" ? checkpointEvent.snapshot : undefined;
|
|
1240
1217
|
const agents = new Map(
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
MessageRenderer,
|
|
4
4
|
ToolDefinition,
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { Type, type Static
|
|
6
|
+
import { Type, type Static } from "typebox";
|
|
7
7
|
import { Value } from "typebox/value";
|
|
8
8
|
|
|
9
9
|
/** Names of the six coordinator tools with custom transcript renderers. */
|
|
@@ -107,16 +107,6 @@ const ManagementCallArgumentsSchema = Type.Object({
|
|
|
107
107
|
recursive: Type.Optional(Type.Boolean()),
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
/** Tool-call contracts keep each coordinator name correlated with its own arguments. */
|
|
111
|
-
export const coordinatorToolCallSchemas = {
|
|
112
|
-
subagent: SpawnCallArgumentsSchema,
|
|
113
|
-
agent_message: MessageCallArgumentsSchema,
|
|
114
|
-
subagent_wait: WaitCallArgumentsSchema,
|
|
115
|
-
subagent_status: StatusCallArgumentsSchema,
|
|
116
|
-
subagent_cancel: ManagementCallArgumentsSchema,
|
|
117
|
-
subagent_delete: ManagementCallArgumentsSchema,
|
|
118
|
-
} as const satisfies { readonly [Name in CoordinatorToolName]: TSchema };
|
|
119
|
-
|
|
120
110
|
const SpawnDetailsSchema = Type.Object({
|
|
121
111
|
agent_id: Type.String(),
|
|
122
112
|
turn_id: Type.String(),
|
|
@@ -142,6 +132,7 @@ const WaitMessageDetailsSchema = Type.Object({
|
|
|
142
132
|
agent_id: Type.String(),
|
|
143
133
|
turn_id: Type.String(),
|
|
144
134
|
message_id: Type.String(),
|
|
135
|
+
delivery_id: Type.Optional(Type.String()),
|
|
145
136
|
message: Type.String(),
|
|
146
137
|
elapsed_ms: Type.Optional(Type.Number()),
|
|
147
138
|
usage: Type.Optional(RenderUsageSchema),
|
|
@@ -155,6 +146,7 @@ const WaitTurnDetailsSchema = Type.Object({
|
|
|
155
146
|
error: Type.Optional(Type.String()),
|
|
156
147
|
elapsed_ms: Type.Optional(Type.Number()),
|
|
157
148
|
usage: Type.Optional(RenderUsageSchema),
|
|
149
|
+
messages: Type.Optional(Type.Array(WaitMessageDetailsSchema)),
|
|
158
150
|
});
|
|
159
151
|
const StatusDetailsSchema = Type.Union([
|
|
160
152
|
Type.Object({
|
|
@@ -183,15 +175,11 @@ const DeleteDetailsSchema = Type.Object({
|
|
|
183
175
|
),
|
|
184
176
|
});
|
|
185
177
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
subagent_status: StatusDetailsSchema,
|
|
192
|
-
subagent_cancel: CancelDetailsSchema,
|
|
193
|
-
subagent_delete: DeleteDetailsSchema,
|
|
194
|
-
} as const satisfies { readonly [Name in CoordinatorToolName]: TSchema };
|
|
178
|
+
const MessageRenderDetailsSchema = Type.Union([
|
|
179
|
+
CurrentMessageDetailsSchema,
|
|
180
|
+
LegacyMessageDetailsSchema,
|
|
181
|
+
]);
|
|
182
|
+
const WaitRenderDetailsSchema = Type.Union([WaitMessageDetailsSchema, WaitTurnDetailsSchema]);
|
|
195
183
|
|
|
196
184
|
export type SpawnCallArguments = Static<typeof SpawnCallArgumentsSchema>;
|
|
197
185
|
export type MessageCallArguments = Static<typeof MessageCallArgumentsSchema>;
|
|
@@ -199,12 +187,8 @@ export type WaitCallArguments = Static<typeof WaitCallArgumentsSchema>;
|
|
|
199
187
|
export type StatusCallArguments = Static<typeof StatusCallArgumentsSchema>;
|
|
200
188
|
export type ManagementCallArguments = Static<typeof ManagementCallArgumentsSchema>;
|
|
201
189
|
export type SpawnRenderDetails = Static<typeof SpawnDetailsSchema>;
|
|
202
|
-
export type MessageRenderDetails = Static<
|
|
203
|
-
|
|
204
|
-
>;
|
|
205
|
-
export type WaitRenderDetails = Static<
|
|
206
|
-
typeof WaitMessageDetailsSchema | typeof WaitTurnDetailsSchema
|
|
207
|
-
>;
|
|
190
|
+
export type MessageRenderDetails = Static<typeof MessageRenderDetailsSchema>;
|
|
191
|
+
export type WaitRenderDetails = Static<typeof WaitRenderDetailsSchema>;
|
|
208
192
|
export type StatusRenderDetails = Static<typeof StatusDetailsSchema>;
|
|
209
193
|
export type CancelRenderDetails = Static<typeof CancelDetailsSchema>;
|
|
210
194
|
export type DeleteRenderDetails = Static<typeof DeleteDetailsSchema>;
|
|
@@ -263,13 +247,9 @@ export function parseCoordinatorToolResult(
|
|
|
263
247
|
case "subagent":
|
|
264
248
|
return Value.Check(SpawnDetailsSchema, details) ? { toolName, details } : undefined;
|
|
265
249
|
case "agent_message":
|
|
266
|
-
return Value.Check(
|
|
267
|
-
? { toolName, details }
|
|
268
|
-
: undefined;
|
|
250
|
+
return Value.Check(MessageRenderDetailsSchema, details) ? { toolName, details } : undefined;
|
|
269
251
|
case "subagent_wait":
|
|
270
|
-
return Value.Check(
|
|
271
|
-
? { toolName, details }
|
|
272
|
-
: undefined;
|
|
252
|
+
return Value.Check(WaitRenderDetailsSchema, details) ? { toolName, details } : undefined;
|
|
273
253
|
case "subagent_status":
|
|
274
254
|
return Value.Check(StatusDetailsSchema, details) ? { toolName, details } : undefined;
|
|
275
255
|
case "subagent_cancel":
|
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
type RenderStatusAgent,
|
|
35
35
|
type SpawnCallArguments,
|
|
36
36
|
type SpawnRenderDetails,
|
|
37
|
-
type StatusCallArguments,
|
|
38
37
|
type StatusRenderDetails,
|
|
39
38
|
type WaitCallArguments,
|
|
40
39
|
type WaitRenderDetails,
|
|
@@ -259,59 +258,6 @@ function renderManagementToolCall(
|
|
|
259
258
|
);
|
|
260
259
|
}
|
|
261
260
|
|
|
262
|
-
type CoordinatorToolCallRenderers = {
|
|
263
|
-
readonly subagent: (args: SpawnCallArguments, theme: MinimalSubagentsRenderTheme) => Component;
|
|
264
|
-
readonly agent_message: (
|
|
265
|
-
args: MessageCallArguments,
|
|
266
|
-
theme: MinimalSubagentsRenderTheme,
|
|
267
|
-
) => Component;
|
|
268
|
-
readonly subagent_wait: (
|
|
269
|
-
args: WaitCallArguments,
|
|
270
|
-
theme: MinimalSubagentsRenderTheme,
|
|
271
|
-
) => Component;
|
|
272
|
-
readonly subagent_status: (
|
|
273
|
-
args: StatusCallArguments,
|
|
274
|
-
theme: MinimalSubagentsRenderTheme,
|
|
275
|
-
) => Component;
|
|
276
|
-
readonly subagent_cancel: (
|
|
277
|
-
args: ManagementCallArguments,
|
|
278
|
-
theme: MinimalSubagentsRenderTheme,
|
|
279
|
-
) => Component;
|
|
280
|
-
readonly subagent_delete: (
|
|
281
|
-
args: ManagementCallArguments,
|
|
282
|
-
theme: MinimalSubagentsRenderTheme,
|
|
283
|
-
) => Component;
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const COORDINATOR_TOOL_CALL_RENDERERS = {
|
|
287
|
-
subagent: (args, theme) =>
|
|
288
|
-
new Text(
|
|
289
|
-
`${coordinatorToolCallTitle(theme, "Subagent")} ${theme.fg("accent", args.agent_id ?? "generated")}${coordinatorToolCallPreview(theme, args.task)}`,
|
|
290
|
-
0,
|
|
291
|
-
0,
|
|
292
|
-
),
|
|
293
|
-
agent_message: (args, theme) =>
|
|
294
|
-
new Text(
|
|
295
|
-
`${coordinatorToolCallTitle(theme, "Message")} ${theme.fg("accent", args.agent_id ?? "parent")}${coordinatorToolCallPreview(theme, args.message)}`,
|
|
296
|
-
0,
|
|
297
|
-
0,
|
|
298
|
-
),
|
|
299
|
-
subagent_wait: (args, theme) =>
|
|
300
|
-
new Text(
|
|
301
|
-
`${coordinatorToolCallTitle(theme, "Wait")} ${theme.fg("accent", args.agent_id ?? "agent")}`,
|
|
302
|
-
0,
|
|
303
|
-
0,
|
|
304
|
-
),
|
|
305
|
-
subagent_status: (args, theme) =>
|
|
306
|
-
new Text(
|
|
307
|
-
`${coordinatorToolCallTitle(theme, "Status")} ${theme.fg("accent", args.agent_id ?? "children")}`,
|
|
308
|
-
0,
|
|
309
|
-
0,
|
|
310
|
-
),
|
|
311
|
-
subagent_cancel: (args, theme) => renderManagementToolCall("Cancel", args, theme),
|
|
312
|
-
subagent_delete: (args, theme) => renderManagementToolCall("Delete", args, theme),
|
|
313
|
-
} satisfies CoordinatorToolCallRenderers;
|
|
314
|
-
|
|
315
261
|
function renderSpawnResult(
|
|
316
262
|
details: SpawnRenderDetails,
|
|
317
263
|
options: ToolRenderResultOptions,
|
|
@@ -395,9 +341,12 @@ function renderWaitResult(
|
|
|
395
341
|
: details.status;
|
|
396
342
|
const duration = formatSubagentDuration(details.elapsed_ms);
|
|
397
343
|
const tokens = formatSubagentTokenCount(details.usage?.totalTokens);
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
344
|
+
const drainedMessageCount = details.event === "message" ? 0 : (details.messages?.length ?? 0);
|
|
345
|
+
const metrics = [
|
|
346
|
+
duration,
|
|
347
|
+
tokens ? `${tokens} tokens` : undefined,
|
|
348
|
+
drainedMessageCount > 0 ? `${drainedMessageCount} messages` : undefined,
|
|
349
|
+
].filter((metric): metric is string => metric !== undefined);
|
|
401
350
|
const summary = renderSubagentSummary(theme, status, agentId, metrics);
|
|
402
351
|
if (options.isPartial || !options.expanded) {
|
|
403
352
|
return new Text(`${summary}${options.isPartial ? "" : collapsedExpansionHint(theme)}`, 0, 0);
|
|
@@ -410,6 +359,14 @@ function renderWaitResult(
|
|
|
410
359
|
container.addChild(renderLabelValue(theme, "Message ID", details.message_id));
|
|
411
360
|
return container;
|
|
412
361
|
}
|
|
362
|
+
if (details.messages && details.messages.length > 0) {
|
|
363
|
+
appendTextSection(
|
|
364
|
+
container,
|
|
365
|
+
theme,
|
|
366
|
+
"Messages",
|
|
367
|
+
details.messages.map((message) => message.message).join("\n\n"),
|
|
368
|
+
);
|
|
369
|
+
}
|
|
413
370
|
const output = details.output ?? "";
|
|
414
371
|
if (status === "completed") {
|
|
415
372
|
if (output.length > 0) {
|
|
@@ -660,51 +617,6 @@ function renderDeleteResult(
|
|
|
660
617
|
return container;
|
|
661
618
|
}
|
|
662
619
|
|
|
663
|
-
type CoordinatorToolResultRenderers = {
|
|
664
|
-
readonly subagent: (
|
|
665
|
-
details: SpawnRenderDetails,
|
|
666
|
-
options: ToolRenderResultOptions,
|
|
667
|
-
theme: MinimalSubagentsRenderTheme,
|
|
668
|
-
args: SpawnCallArguments,
|
|
669
|
-
) => Component;
|
|
670
|
-
readonly agent_message: (
|
|
671
|
-
details: MessageRenderDetails,
|
|
672
|
-
options: ToolRenderResultOptions,
|
|
673
|
-
theme: MinimalSubagentsRenderTheme,
|
|
674
|
-
args: MessageCallArguments,
|
|
675
|
-
) => Component;
|
|
676
|
-
readonly subagent_wait: (
|
|
677
|
-
details: WaitRenderDetails,
|
|
678
|
-
options: ToolRenderResultOptions,
|
|
679
|
-
theme: MinimalSubagentsRenderTheme,
|
|
680
|
-
args: WaitCallArguments,
|
|
681
|
-
) => Component;
|
|
682
|
-
readonly subagent_status: (
|
|
683
|
-
details: StatusRenderDetails,
|
|
684
|
-
options: ToolRenderResultOptions,
|
|
685
|
-
theme: MinimalSubagentsRenderTheme,
|
|
686
|
-
) => Component;
|
|
687
|
-
readonly subagent_cancel: (
|
|
688
|
-
details: CancelRenderDetails,
|
|
689
|
-
options: ToolRenderResultOptions,
|
|
690
|
-
theme: MinimalSubagentsRenderTheme,
|
|
691
|
-
) => Component;
|
|
692
|
-
readonly subagent_delete: (
|
|
693
|
-
details: DeleteRenderDetails,
|
|
694
|
-
options: ToolRenderResultOptions,
|
|
695
|
-
theme: MinimalSubagentsRenderTheme,
|
|
696
|
-
) => Component;
|
|
697
|
-
};
|
|
698
|
-
|
|
699
|
-
const COORDINATOR_TOOL_RESULT_RENDERERS = {
|
|
700
|
-
subagent: renderSpawnResult,
|
|
701
|
-
agent_message: renderMessageResult,
|
|
702
|
-
subagent_wait: renderWaitResult,
|
|
703
|
-
subagent_status: renderStatusResult,
|
|
704
|
-
subagent_cancel: renderCancelResult,
|
|
705
|
-
subagent_delete: renderDeleteResult,
|
|
706
|
-
} satisfies CoordinatorToolResultRenderers;
|
|
707
|
-
|
|
708
620
|
/** Render one of the six coordinator tool calls with a shared native Pi grammar. */
|
|
709
621
|
export function renderCoordinatorToolCall(
|
|
710
622
|
toolName: CoordinatorToolName,
|
|
@@ -715,17 +627,33 @@ export function renderCoordinatorToolCall(
|
|
|
715
627
|
if (parsed === undefined) return new Text(coordinatorToolCallTitle(theme, toolName), 0, 0);
|
|
716
628
|
switch (parsed.toolName) {
|
|
717
629
|
case "subagent":
|
|
718
|
-
return
|
|
630
|
+
return new Text(
|
|
631
|
+
`${coordinatorToolCallTitle(theme, "Subagent")} ${theme.fg("accent", parsed.args.agent_id ?? "generated")}${coordinatorToolCallPreview(theme, parsed.args.task)}`,
|
|
632
|
+
0,
|
|
633
|
+
0,
|
|
634
|
+
);
|
|
719
635
|
case "agent_message":
|
|
720
|
-
return
|
|
636
|
+
return new Text(
|
|
637
|
+
`${coordinatorToolCallTitle(theme, "Message")} ${theme.fg("accent", parsed.args.agent_id ?? "parent")}${coordinatorToolCallPreview(theme, parsed.args.message)}`,
|
|
638
|
+
0,
|
|
639
|
+
0,
|
|
640
|
+
);
|
|
721
641
|
case "subagent_wait":
|
|
722
|
-
return
|
|
642
|
+
return new Text(
|
|
643
|
+
`${coordinatorToolCallTitle(theme, "Wait")} ${theme.fg("accent", parsed.args.agent_id ?? "agent")}`,
|
|
644
|
+
0,
|
|
645
|
+
0,
|
|
646
|
+
);
|
|
723
647
|
case "subagent_status":
|
|
724
|
-
return
|
|
648
|
+
return new Text(
|
|
649
|
+
`${coordinatorToolCallTitle(theme, "Status")} ${theme.fg("accent", parsed.args.agent_id ?? "children")}`,
|
|
650
|
+
0,
|
|
651
|
+
0,
|
|
652
|
+
);
|
|
725
653
|
case "subagent_cancel":
|
|
726
|
-
return
|
|
654
|
+
return renderManagementToolCall("Cancel", parsed.args, theme);
|
|
727
655
|
case "subagent_delete":
|
|
728
|
-
return
|
|
656
|
+
return renderManagementToolCall("Delete", parsed.args, theme);
|
|
729
657
|
}
|
|
730
658
|
}
|
|
731
659
|
|
|
@@ -743,44 +671,32 @@ export function renderCoordinatorToolResult(
|
|
|
743
671
|
const parsedCall = parseCoordinatorToolCall(toolName, args);
|
|
744
672
|
switch (parsedResult.toolName) {
|
|
745
673
|
case "subagent":
|
|
746
|
-
return
|
|
674
|
+
return renderSpawnResult(
|
|
747
675
|
parsedResult.details,
|
|
748
676
|
options,
|
|
749
677
|
theme,
|
|
750
678
|
parsedCall?.toolName === "subagent" ? parsedCall.args : {},
|
|
751
679
|
);
|
|
752
680
|
case "agent_message":
|
|
753
|
-
return
|
|
681
|
+
return renderMessageResult(
|
|
754
682
|
parsedResult.details,
|
|
755
683
|
options,
|
|
756
684
|
theme,
|
|
757
685
|
parsedCall?.toolName === "agent_message" ? parsedCall.args : {},
|
|
758
686
|
);
|
|
759
687
|
case "subagent_wait":
|
|
760
|
-
return
|
|
688
|
+
return renderWaitResult(
|
|
761
689
|
parsedResult.details,
|
|
762
690
|
options,
|
|
763
691
|
theme,
|
|
764
692
|
parsedCall?.toolName === "subagent_wait" ? parsedCall.args : {},
|
|
765
693
|
);
|
|
766
694
|
case "subagent_status":
|
|
767
|
-
return
|
|
768
|
-
parsedResult.details,
|
|
769
|
-
options,
|
|
770
|
-
theme,
|
|
771
|
-
);
|
|
695
|
+
return renderStatusResult(parsedResult.details, options, theme);
|
|
772
696
|
case "subagent_cancel":
|
|
773
|
-
return
|
|
774
|
-
parsedResult.details,
|
|
775
|
-
options,
|
|
776
|
-
theme,
|
|
777
|
-
);
|
|
697
|
+
return renderCancelResult(parsedResult.details, options, theme);
|
|
778
698
|
case "subagent_delete":
|
|
779
|
-
return
|
|
780
|
-
parsedResult.details,
|
|
781
|
-
options,
|
|
782
|
-
theme,
|
|
783
|
-
);
|
|
699
|
+
return renderDeleteResult(parsedResult.details, options, theme);
|
|
784
700
|
}
|
|
785
701
|
}
|
|
786
702
|
|
|
@@ -28,6 +28,11 @@ export const ForkOwnershipRecordSchema = Type.Object({
|
|
|
28
28
|
direct_parent_id: Type.String(),
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
const DeliveryEvidenceMessageSchema = Type.Object({
|
|
32
|
+
delivery_id: Type.Optional(Type.String()),
|
|
33
|
+
message_id: Type.Optional(Type.String()),
|
|
34
|
+
});
|
|
35
|
+
|
|
31
36
|
/** Parses durable custom-message and wait-tool Delivery Evidence details. */
|
|
32
37
|
export const DeliveryEvidenceDetailsSchema = Type.Object({
|
|
33
38
|
event: Type.Optional(Type.String()),
|
|
@@ -35,6 +40,7 @@ export const DeliveryEvidenceDetailsSchema = Type.Object({
|
|
|
35
40
|
source_turn_id: Type.String(),
|
|
36
41
|
delivery_id: Type.Optional(Type.String()),
|
|
37
42
|
message_id: Type.Optional(Type.String()),
|
|
43
|
+
messages: Type.Optional(Type.Array(DeliveryEvidenceMessageSchema)),
|
|
38
44
|
});
|
|
39
45
|
|
|
40
46
|
export type ChildSessionIdentityRecord = Static<typeof ChildSessionIdentityRecordSchema>;
|
|
@@ -53,7 +53,6 @@ import type {
|
|
|
53
53
|
PersistedAgent,
|
|
54
54
|
PersistedSessionIdentity,
|
|
55
55
|
ProjectContextMode,
|
|
56
|
-
RuntimeCreationRequest,
|
|
57
56
|
RuntimeProfile,
|
|
58
57
|
RuntimeTurnOutcome,
|
|
59
58
|
} from "./minimal-subagents-types.js";
|
|
@@ -203,12 +202,14 @@ function findLatestChildSessionRecord<TRecordSchema extends TSchema>(
|
|
|
203
202
|
customType: string,
|
|
204
203
|
schema: TRecordSchema,
|
|
205
204
|
): Static<TRecordSchema> | undefined {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
205
|
+
return entries.findLast(
|
|
206
|
+
(
|
|
207
|
+
entry,
|
|
208
|
+
): entry is Extract<SessionEntry, { type: "custom" }> & {
|
|
209
|
+
data: Static<TRecordSchema>;
|
|
210
|
+
} =>
|
|
211
|
+
entry.type === "custom" && entry.customType === customType && Value.Check(schema, entry.data),
|
|
212
|
+
)?.data;
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
function findLatestForkGeneration(
|
|
@@ -247,18 +248,17 @@ function findCurrentForkOwnership(
|
|
|
247
248
|
entries: ReturnType<SessionManager["getBranch"]>,
|
|
248
249
|
cloneSessionId: string,
|
|
249
250
|
): ForkOwnershipRecord | undefined {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
251
|
+
return entries.findLast(
|
|
252
|
+
(
|
|
253
|
+
entry,
|
|
254
|
+
): entry is Extract<SessionEntry, { type: "custom" }> & {
|
|
255
|
+
data: ForkOwnershipRecord;
|
|
256
|
+
} =>
|
|
257
|
+
entry.type === "custom" &&
|
|
258
|
+
entry.customType === FORK_OWNERSHIP_ENTRY_TYPE &&
|
|
255
259
|
Value.Check(ForkOwnershipRecordSchema, entry.data) &&
|
|
256
|
-
entry.data.clone_session_id === cloneSessionId
|
|
257
|
-
|
|
258
|
-
return entry.data;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return undefined;
|
|
260
|
+
entry.data.clone_session_id === cloneSessionId,
|
|
261
|
+
)?.data;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
function verifyForkCloneProvenance(
|
|
@@ -411,7 +411,11 @@ export function findDeliveryEvidence(
|
|
|
411
411
|
return (
|
|
412
412
|
details.source_agent_id === sourceAgentId &&
|
|
413
413
|
details.source_turn_id === sourceTurnId &&
|
|
414
|
-
(details.delivery_id === deliveryId ||
|
|
414
|
+
(details.delivery_id === deliveryId ||
|
|
415
|
+
details.message_id === deliveryId ||
|
|
416
|
+
details.messages?.some(
|
|
417
|
+
(message) => message.delivery_id === deliveryId || message.message_id === deliveryId,
|
|
418
|
+
))
|
|
415
419
|
);
|
|
416
420
|
}
|
|
417
421
|
if (waitToolResult && details.event === "message") return false;
|
|
@@ -527,17 +531,6 @@ class PiChildAgentRuntime implements ChildAgentRuntime {
|
|
|
527
531
|
});
|
|
528
532
|
}
|
|
529
533
|
|
|
530
|
-
get sessionFile(): string {
|
|
531
|
-
const sessionFile = this.session.sessionFile;
|
|
532
|
-
if (!sessionFile)
|
|
533
|
-
throw new Error("Minimal subagents child runtime lost its persistent session file");
|
|
534
|
-
return sessionFile;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
get sessionId(): string {
|
|
538
|
-
return this.session.sessionId;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
534
|
get sessionLeafId(): string | undefined {
|
|
542
535
|
return this.session.sessionManager.getLeafId() ?? undefined;
|
|
543
536
|
}
|
|
@@ -720,14 +713,6 @@ export class PiAgentSessionFactory implements AgentSessionFactory {
|
|
|
720
713
|
});
|
|
721
714
|
}
|
|
722
715
|
|
|
723
|
-
createRuntime(request: RuntimeCreationRequest): Promise<ChildAgentRuntime> {
|
|
724
|
-
return this.openRuntime(request.agent);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
restoreRuntime(agent: PersistedAgent): Promise<ChildAgentRuntime> {
|
|
728
|
-
return this.openRuntime(agent);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
716
|
resolveLaunchMissingDependencies(agent: PersistedAgent): Promise<string[]> {
|
|
732
717
|
return this.findMissingDependencies(agent, false);
|
|
733
718
|
}
|
|
@@ -967,7 +952,8 @@ export class PiAgentSessionFactory implements AgentSessionFactory {
|
|
|
967
952
|
return discovery;
|
|
968
953
|
}
|
|
969
954
|
|
|
970
|
-
|
|
955
|
+
/** Open one verified persisted Child Agent runtime for launch or restoration. */
|
|
956
|
+
async openRuntime(agent: PersistedAgent): Promise<ChildAgentRuntime> {
|
|
971
957
|
if (!agent.session_file)
|
|
972
958
|
throw new Error(`Minimal subagents restore: ${agent.agent_id} has no session file`);
|
|
973
959
|
const model = this.modelById.get(agent.launch_contract.model);
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
type ToolDefinition,
|
|
10
10
|
type ToolRenderResultOptions,
|
|
11
11
|
} from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import type { Static } from "typebox";
|
|
13
12
|
import type { MinimalSubagentsCoordinator } from "./minimal-subagents-coordinator.js";
|
|
14
13
|
import type { MinimalSubagentsModelRole } from "./minimal-subagents-config.js";
|
|
15
14
|
import {
|
|
@@ -53,27 +52,6 @@ export interface CoordinatorToolDefinitionOptions {
|
|
|
53
52
|
onAttention?: (message: string) => void;
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
/** Arguments consumed by the wait tool's narrow coordinator execution seam. */
|
|
57
|
-
export type CoordinatorWaitToolParameters = Static<
|
|
58
|
-
ReturnType<typeof createCoordinatorToolSchemas>["subagent_wait"]
|
|
59
|
-
>;
|
|
60
|
-
|
|
61
|
-
/** Forward one typed wait-tool request without requiring an unrelated Pi execution context. */
|
|
62
|
-
export function executeCoordinatorWaitTool(
|
|
63
|
-
coordinator: Pick<CoordinatorToolOperations, "wait">,
|
|
64
|
-
callerId: string,
|
|
65
|
-
parameters: CoordinatorWaitToolParameters,
|
|
66
|
-
signal: AbortSignal | undefined,
|
|
67
|
-
): Promise<WaitResult> {
|
|
68
|
-
return coordinator.wait(
|
|
69
|
-
callerId,
|
|
70
|
-
parameters.agent_id,
|
|
71
|
-
parameters.timeout_ms,
|
|
72
|
-
signal,
|
|
73
|
-
parameters.turn_id,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
55
|
function buildModelRolePromptGuidelines(
|
|
78
56
|
modelRoles: readonly MinimalSubagentsModelRole[],
|
|
79
57
|
): string[] | undefined {
|
|
@@ -224,7 +202,7 @@ export function createCoordinatorToolDefinitions(
|
|
|
224
202
|
name: "subagent_wait",
|
|
225
203
|
label: "Subagent Wait",
|
|
226
204
|
description:
|
|
227
|
-
"Wait for one direct child's oldest observable turn, or select an exact retained turn_id.
|
|
205
|
+
"Wait for one direct child's oldest observable turn, or select an exact retained turn_id. An active child may first return event=message; later unconsumed items still fall back automatically. An already settled turn returns event=turn once with queued messages in messages. Timeout never cancels the child.",
|
|
228
206
|
promptSnippet: "Wait for one direct child's exact turn",
|
|
229
207
|
parameters: options.schemas.subagent_wait,
|
|
230
208
|
async execute(_toolCallId, parameters, signal, onUpdate) {
|
|
@@ -243,11 +221,12 @@ export function createCoordinatorToolDefinitions(
|
|
|
243
221
|
waitingInterval.unref?.();
|
|
244
222
|
try {
|
|
245
223
|
return await runCoordinatorToolActivity(options, async () => {
|
|
246
|
-
const result = await
|
|
247
|
-
options.coordinator,
|
|
224
|
+
const result = await options.coordinator.wait(
|
|
248
225
|
options.callerId,
|
|
249
|
-
parameters,
|
|
226
|
+
parameters.agent_id,
|
|
227
|
+
parameters.timeout_ms,
|
|
250
228
|
signal,
|
|
229
|
+
parameters.turn_id,
|
|
251
230
|
);
|
|
252
231
|
return {
|
|
253
232
|
...structuredToolResult(result),
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { AgentMessage, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import type { Usage } from "@earendil-works/pi-ai";
|
|
3
3
|
|
|
4
|
-
/** Canonical path-like identity for one persistent subagent. */
|
|
5
|
-
export type AgentId = string & { readonly __agentId: unique symbol };
|
|
6
|
-
|
|
7
4
|
/** Stable identity for one prompt and its complete assistant/tool loop. */
|
|
8
5
|
export type TurnId = string & { readonly __turnId: unique symbol };
|
|
9
6
|
|
|
@@ -78,8 +75,14 @@ export interface WaitMessageResult {
|
|
|
78
75
|
message: string;
|
|
79
76
|
}
|
|
80
77
|
|
|
78
|
+
/** Reports one terminal child turn and any earlier messages drained by the same wait. */
|
|
79
|
+
export interface WaitTurnResult extends TurnResult {
|
|
80
|
+
event: "turn";
|
|
81
|
+
messages?: WaitMessageResult[];
|
|
82
|
+
}
|
|
83
|
+
|
|
81
84
|
/** Reports one terminal child turn returned by subagent_wait. */
|
|
82
|
-
export type WaitResult = WaitMessageResult |
|
|
85
|
+
export type WaitResult = WaitMessageResult | WaitTurnResult;
|
|
83
86
|
|
|
84
87
|
/** Provides bounded hierarchy, usage, and best-known Runtime Profile data for one persistent agent. */
|
|
85
88
|
export interface AgentSummary extends RuntimeProfile {
|
|
@@ -190,8 +193,6 @@ export interface CoordinatorMessage {
|
|
|
190
193
|
|
|
191
194
|
/** Process-local adapter around one SDK-created Pi child session. */
|
|
192
195
|
export interface ChildAgentRuntime {
|
|
193
|
-
readonly sessionFile: string;
|
|
194
|
-
readonly sessionId: string;
|
|
195
196
|
readonly sessionLeafId: string | undefined;
|
|
196
197
|
readonly isRunning: boolean;
|
|
197
198
|
runPrompt(
|
|
@@ -219,17 +220,11 @@ export interface PersistedSessionIdentity {
|
|
|
219
220
|
sessionLeafId?: string;
|
|
220
221
|
}
|
|
221
222
|
|
|
222
|
-
/** Combines a persisted agent record with first-launch imported context. */
|
|
223
|
-
export interface RuntimeCreationRequest {
|
|
224
|
-
agent: PersistedAgent;
|
|
225
|
-
importedMessages: AgentMessage[];
|
|
226
|
-
}
|
|
227
|
-
|
|
228
223
|
/** Pi-specific session operations injected into the pure coordinator. */
|
|
229
224
|
export interface AgentSessionFactory {
|
|
230
225
|
createIdentity(agent: PersistedAgent, importedMessages: AgentMessage[]): PersistedSessionIdentity;
|
|
231
|
-
|
|
232
|
-
|
|
226
|
+
/** Open one verified persisted Child Agent runtime for launch or restoration. */
|
|
227
|
+
openRuntime(agent: PersistedAgent): Promise<ChildAgentRuntime>;
|
|
233
228
|
resolveLaunchMissingDependencies(agent: PersistedAgent): Promise<string[]>;
|
|
234
229
|
resolveRestorationMissingDependencies(agent: PersistedAgent): Promise<string[]>;
|
|
235
230
|
resolveThinkingLevel(modelId: string, requested: ThinkingLevel): ThinkingLevel;
|
|
@@ -254,8 +249,6 @@ export interface RootConversationEndpoint {
|
|
|
254
249
|
/** Queue one typed coordinator message into the root conversation. */
|
|
255
250
|
queueCoordinatorMessage(message: CoordinatorMessage): Promise<void>;
|
|
256
251
|
hasDeliveryEvidence(sourceAgentId: string, sourceTurnId: string, deliveryId?: string): boolean;
|
|
257
|
-
/** Report whether automatic delivery can start a new root turn without racing an active wait. */
|
|
258
|
-
isIdle(): boolean;
|
|
259
252
|
}
|
|
260
253
|
|
|
261
254
|
/** Stores root-owned agent identity, launch contract, availability, and latest activity. */
|