@oai404iao/pi-subagent 0.4.0-alpha.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -72
- package/config.example.json +1 -4
- package/config.schema.json +14 -28
- package/index.ts +2 -0
- package/package.json +9 -8
- package/src/agent-sync.ts +112 -40
- package/src/catalog.ts +14 -16
- package/src/config.ts +15 -52
- package/src/coordinator.ts +114 -422
- package/src/descriptor.ts +51 -81
- package/src/index.ts +57 -88
- package/src/render.ts +5 -15
- package/src/schemas.ts +8 -70
- package/src/task-path.ts +5 -47
- package/src/types.ts +14 -25
package/src/coordinator.ts
CHANGED
|
@@ -123,12 +123,12 @@ import {
|
|
|
123
123
|
type SubagentProviderName,
|
|
124
124
|
type SubagentRunResult,
|
|
125
125
|
type SubagentSettings,
|
|
126
|
+
type RuntimeMode,
|
|
126
127
|
type SubagentStopReason,
|
|
127
128
|
type TraceItem,
|
|
128
129
|
} from "./types.ts";
|
|
129
130
|
|
|
130
131
|
const REPORT_CUSTOM_TYPE = "pi-subagent/report";
|
|
131
|
-
const SETTLED_CUSTOM_TYPE = "pi-subagent/settled";
|
|
132
132
|
const AGENT_CUSTOM_TYPE = "pi-subagent/agent";
|
|
133
133
|
const LINEAGE_CUSTOM_TYPE = "pi-subagent/lineage";
|
|
134
134
|
const AGENT_ID_PATTERN =
|
|
@@ -153,7 +153,8 @@ const DELEGATION_SCOPE_PROMPT = [
|
|
|
153
153
|
const REPORT_PROMPT = [
|
|
154
154
|
"You have a `report` tool that sends a selected update to the agent that started you.",
|
|
155
155
|
"Call it with a self-contained answer before finishing, and earlier when a finding changes what the parent should do.",
|
|
156
|
-
"
|
|
156
|
+
"A report is recorded for the parent without waking it; it does not end this turn and does not prevent later follow-up messages.",
|
|
157
|
+
"Your final answer is delivered separately as a completion update.",
|
|
157
158
|
].join(" ");
|
|
158
159
|
|
|
159
160
|
export interface DelegationInput {
|
|
@@ -161,7 +162,6 @@ export interface DelegationInput {
|
|
|
161
162
|
task_name?: string;
|
|
162
163
|
description: string;
|
|
163
164
|
prompt: string;
|
|
164
|
-
run_in_background?: boolean;
|
|
165
165
|
context?: {
|
|
166
166
|
mode: ContextInheritance["mode"];
|
|
167
167
|
completed_turns?: number;
|
|
@@ -172,15 +172,13 @@ export type DelegationOutcome =
|
|
|
172
172
|
| { kind: "continuable"; details: DelegationDetails }
|
|
173
173
|
| { kind: "foreground"; details: DelegationDetails; result: SubagentRunResult };
|
|
174
174
|
|
|
175
|
-
export
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
pendingMessages: number;
|
|
183
|
-
};
|
|
175
|
+
export interface SendMessageOutcome {
|
|
176
|
+
kind: "mailbox";
|
|
177
|
+
agentId: string;
|
|
178
|
+
taskPath: string;
|
|
179
|
+
messageId: string;
|
|
180
|
+
pendingMessages: number;
|
|
181
|
+
}
|
|
184
182
|
|
|
185
183
|
export interface FollowupTaskOutcome {
|
|
186
184
|
agentId: string;
|
|
@@ -218,7 +216,6 @@ interface ParentRef {
|
|
|
218
216
|
customType: string,
|
|
219
217
|
content: string,
|
|
220
218
|
details: ParentMessageDetails,
|
|
221
|
-
delivery: "wakeup" | "quiet",
|
|
222
219
|
): Promise<void>;
|
|
223
220
|
}
|
|
224
221
|
|
|
@@ -240,7 +237,6 @@ interface Activation {
|
|
|
240
237
|
userMessageGates: Map<string, UserMessageGate>;
|
|
241
238
|
ownedChildren: Set<string>;
|
|
242
239
|
currentRun?: Promise<SubagentRunResult>;
|
|
243
|
-
currentMessageGate?: Promise<void>;
|
|
244
240
|
pendingSettlement?: SubagentRunResult;
|
|
245
241
|
unsubscribe?: () => void;
|
|
246
242
|
onUpdate?: (details: DelegationDetails) => void;
|
|
@@ -301,14 +297,7 @@ interface PendingPromptStart {
|
|
|
301
297
|
coldPrepared?: PreparedChildSession;
|
|
302
298
|
}
|
|
303
299
|
|
|
304
|
-
type SerializedSendMessageOutcome =
|
|
305
|
-
| {
|
|
306
|
-
kind: "legacy";
|
|
307
|
-
agentId: string;
|
|
308
|
-
taskPath: string;
|
|
309
|
-
pending?: PendingPromptStart;
|
|
310
|
-
}
|
|
311
|
-
| Extract<SendMessageOutcome, { kind: "mailbox-v2" }>;
|
|
300
|
+
type SerializedSendMessageOutcome = SendMessageOutcome;
|
|
312
301
|
|
|
313
302
|
interface PendingFollowupStart extends PendingPromptStart {
|
|
314
303
|
outcome: FollowupTaskOutcome;
|
|
@@ -490,12 +479,9 @@ function makeRuntimeSettings(descriptor: SubagentDescriptor): SubagentSettings {
|
|
|
490
479
|
return {
|
|
491
480
|
agentScope: descriptor.runtime.agentScope,
|
|
492
481
|
maxDepth: descriptor.runtime.maxDepth,
|
|
493
|
-
|
|
494
|
-
defaultBackground: descriptor.runtime.defaultBackground,
|
|
482
|
+
runtimeMode: descriptor.runtime.runtimeMode,
|
|
495
483
|
maxConcurrentBackgroundRuns: descriptor.runtime.maxConcurrentBackgroundRuns,
|
|
496
484
|
maxIdleRuntimes: descriptor.runtime.maxIdleRuntimes,
|
|
497
|
-
backgroundProtocol: descriptor.runtime.backgroundProtocol,
|
|
498
|
-
reportDelivery: descriptor.runtime.reportDelivery,
|
|
499
485
|
inheritExtensions: descriptor.runtime.inheritExtensions,
|
|
500
486
|
openAIIdentity: descriptor.runtime.openAIIdentity,
|
|
501
487
|
maxOutputBytes: descriptor.runtime.maxOutputBytes,
|
|
@@ -698,13 +684,13 @@ export class SubagentCoordinator {
|
|
|
698
684
|
model: ctx.model,
|
|
699
685
|
thinkingLevel: ctx.thinkingLevel ?? "off",
|
|
700
686
|
projectTrusted: ctx.isProjectTrusted(),
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
687
|
+
// Reports never start a parent turn. The entry becomes part of the
|
|
688
|
+
// parent session (and therefore its next model context) while the
|
|
689
|
+
// parent keeps working; durable completions are read with wait_agent.
|
|
690
|
+
deliver: async (customType, content, details) => {
|
|
691
|
+
const options = ctx.isIdle()
|
|
692
|
+
? { triggerTurn: false }
|
|
693
|
+
: { triggerTurn: false, deliverAs: "nextTurn" as const };
|
|
708
694
|
this.pi.sendMessage({ customType, content, display: true, details }, options);
|
|
709
695
|
},
|
|
710
696
|
};
|
|
@@ -746,32 +732,19 @@ export class SubagentCoordinator {
|
|
|
746
732
|
const resolvedProviderName: SubagentProviderName =
|
|
747
733
|
context.mode === "fresh" ? "spawn" : "fork";
|
|
748
734
|
const provider = this.providers.get(resolvedProviderName);
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
input.run_in_background === true
|
|
752
|
-
) {
|
|
753
|
-
throw new Error(
|
|
754
|
-
"run_in_background is disabled by pi-subagent foreground-only mode (enableRunInBackground: false)",
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
const runInBackground =
|
|
758
|
-
settings.enableRunInBackground
|
|
759
|
-
? providerName === "fork"
|
|
760
|
-
? (input.run_in_background ?? false)
|
|
761
|
-
: (input.run_in_background ?? settings.defaultBackground)
|
|
762
|
-
: false;
|
|
763
|
-
const mode: SubagentMode = runInBackground ? "continuable" : "one-shot";
|
|
735
|
+
const mode: SubagentMode =
|
|
736
|
+
settings.runtimeMode === "background" ? "continuable" : "one-shot";
|
|
764
737
|
if (mode === "continuable" && !provider.supportsContinuable) {
|
|
765
738
|
throw new Error(`subagent provider "${provider.name}" does not support continuable children`);
|
|
766
739
|
}
|
|
767
740
|
if (mode === "continuable" && !parent.sessionManager.getSessionFile()) {
|
|
768
741
|
throw new Error(
|
|
769
|
-
|
|
742
|
+
'continuable subagents require a persisted parent session; set runtimeMode to "foreground" to delegate from an ephemeral session',
|
|
770
743
|
);
|
|
771
744
|
}
|
|
772
745
|
if (mode === "continuable" && parent.activation?.descriptor.mode === "one-shot") {
|
|
773
746
|
throw new Error(
|
|
774
|
-
"a
|
|
747
|
+
"a foreground child cannot leave a continuable descendant behind; its runtime mode is fixed at creation",
|
|
775
748
|
);
|
|
776
749
|
}
|
|
777
750
|
|
|
@@ -840,12 +813,9 @@ export class SubagentCoordinator {
|
|
|
840
813
|
runtime: {
|
|
841
814
|
agentScope: settings.agentScope,
|
|
842
815
|
maxDepth: settings.maxDepth,
|
|
843
|
-
|
|
844
|
-
defaultBackground: settings.defaultBackground,
|
|
816
|
+
runtimeMode: settings.runtimeMode,
|
|
845
817
|
maxConcurrentBackgroundRuns: settings.maxConcurrentBackgroundRuns,
|
|
846
818
|
maxIdleRuntimes: settings.maxIdleRuntimes,
|
|
847
|
-
backgroundProtocol: settings.backgroundProtocol ?? "legacy",
|
|
848
|
-
reportDelivery: settings.reportDelivery,
|
|
849
819
|
inheritExtensions: settings.inheritExtensions,
|
|
850
820
|
openAIIdentity: openAIIdentityEnabled,
|
|
851
821
|
maxOutputBytes: settings.maxOutputBytes,
|
|
@@ -937,32 +907,9 @@ export class SubagentCoordinator {
|
|
|
937
907
|
): Promise<SendMessageOutcome> {
|
|
938
908
|
if (this.draining) throw new Error("pi-subagent is shutting down; message was not delivered");
|
|
939
909
|
const target = await this.resolveTarget(parent, childId);
|
|
940
|
-
|
|
910
|
+
return this.agentOperations.run(target.agentId, () =>
|
|
941
911
|
this.sendMessageSerialized(parent, target.agentId, message, signal),
|
|
942
912
|
);
|
|
943
|
-
if (delivery.kind === "mailbox-v2") return delivery;
|
|
944
|
-
const pending = delivery.pending;
|
|
945
|
-
if (!pending) return delivery;
|
|
946
|
-
try {
|
|
947
|
-
await pending.accepted;
|
|
948
|
-
} catch (error) {
|
|
949
|
-
if (pending.coldPrepared && !pending.activation.published) {
|
|
950
|
-
await this.agentOperations.run(target.agentId, async () => {
|
|
951
|
-
if (this.active.get(target.agentId) !== pending.activation) return;
|
|
952
|
-
pending.activation.suppressSettlement = true;
|
|
953
|
-
await this.rollbackActivation(
|
|
954
|
-
pending.activation,
|
|
955
|
-
pending.coldPrepared!,
|
|
956
|
-
);
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
throw error;
|
|
960
|
-
}
|
|
961
|
-
return {
|
|
962
|
-
kind: "legacy",
|
|
963
|
-
agentId: delivery.agentId,
|
|
964
|
-
taskPath: delivery.taskPath,
|
|
965
|
-
};
|
|
966
913
|
}
|
|
967
914
|
|
|
968
915
|
private async sendMessageSerialized(
|
|
@@ -975,139 +922,54 @@ export class SubagentCoordinator {
|
|
|
975
922
|
throw new Error("pi-subagent is shutting down; message was not delivered");
|
|
976
923
|
}
|
|
977
924
|
let activation = this.active.get(childId);
|
|
978
|
-
let coldPrepared: PreparedChildSession | undefined;
|
|
979
925
|
if (activation?.disposed) activation = undefined;
|
|
980
926
|
if (activation) {
|
|
981
927
|
this.touchActivation(activation);
|
|
982
928
|
this.assertContinuableDirectChild(parent, activation.descriptor);
|
|
983
|
-
if (
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
`subagent ${childId} became unavailable before its mailbox could be persisted`,
|
|
989
|
-
);
|
|
990
|
-
}
|
|
991
|
-
const enqueued = enqueueMailboxMessage(
|
|
992
|
-
activation.runtime.session.sessionManager,
|
|
993
|
-
{
|
|
994
|
-
senderAgentId: parent.agentId,
|
|
995
|
-
recipientAgentId: childId,
|
|
996
|
-
content: message,
|
|
997
|
-
},
|
|
929
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
930
|
+
await waitForPromise(activation.persistenceGate.promise, signal);
|
|
931
|
+
if (this.draining || activation.disposed) {
|
|
932
|
+
throw new Error(
|
|
933
|
+
`subagent ${childId} became unavailable before its mailbox could be persisted`,
|
|
998
934
|
);
|
|
999
|
-
return {
|
|
1000
|
-
kind: "mailbox-v2",
|
|
1001
|
-
agentId: childId,
|
|
1002
|
-
taskPath: descriptorTaskPath(activation.descriptor),
|
|
1003
|
-
messageId: enqueued.message.messageId,
|
|
1004
|
-
pendingMessages: enqueued.pendingMessages,
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
if (activation?.pendingSettlement && !activation.currentRun) {
|
|
1009
|
-
await this.finalizeContinuableLocked(activation);
|
|
1010
|
-
if (activation.disposed || this.active.get(childId) !== activation) {
|
|
1011
|
-
activation = undefined;
|
|
1012
935
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
if (!located) throw new Error(`unknown subagent: ${childId}; message was not delivered`);
|
|
1017
|
-
this.assertContinuableDirectChild(parent, located.descriptor);
|
|
1018
|
-
const manager = SessionManager.open(
|
|
1019
|
-
located.sessionFile,
|
|
1020
|
-
parent.sessionManager.getSessionDir(),
|
|
1021
|
-
parent.cwd,
|
|
1022
|
-
);
|
|
1023
|
-
if (located.descriptor.runtime.backgroundProtocol === "mailbox-v2") {
|
|
1024
|
-
if (signal?.aborted) throw abortReason(signal);
|
|
1025
|
-
const enqueued = enqueueMailboxMessage(manager, {
|
|
936
|
+
const enqueued = enqueueMailboxMessage(
|
|
937
|
+
activation.runtime.session.sessionManager,
|
|
938
|
+
{
|
|
1026
939
|
senderAgentId: parent.agentId,
|
|
1027
940
|
recipientAgentId: childId,
|
|
1028
941
|
content: message,
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
kind: "mailbox-v2",
|
|
1032
|
-
agentId: childId,
|
|
1033
|
-
taskPath: descriptorTaskPath(located.descriptor),
|
|
1034
|
-
messageId: enqueued.message.messageId,
|
|
1035
|
-
pendingMessages: enqueued.pendingMessages,
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
|
-
coldPrepared = {
|
|
1039
|
-
sessionManager: manager,
|
|
1040
|
-
seedMessageCount: manager.buildSessionContext().messages.length,
|
|
1041
|
-
rollback: () => Promise.resolve(),
|
|
1042
|
-
};
|
|
1043
|
-
activation = await this.createActivation({
|
|
1044
|
-
parent,
|
|
1045
|
-
descriptor: located.descriptor,
|
|
1046
|
-
prepared: coldPrepared,
|
|
1047
|
-
isNew: false,
|
|
1048
|
-
});
|
|
1049
|
-
this.acquireParentOwnership(activation, parent);
|
|
1050
|
-
} else {
|
|
1051
|
-
this.assertContinuableDirectChild(parent, activation.descriptor);
|
|
1052
|
-
}
|
|
1053
|
-
const resolvedPath = descriptorTaskPath(activation.descriptor);
|
|
1054
|
-
|
|
1055
|
-
const session = activation.runtime.session;
|
|
1056
|
-
if (activation.currentRun || session.isStreaming) {
|
|
1057
|
-
if (signal?.aborted) throw signal.reason ?? new Error("message delivery aborted");
|
|
1058
|
-
if (activation.controlState.turn.state === "queued") {
|
|
1059
|
-
if (!activation.currentMessageGate) {
|
|
1060
|
-
throw new Error(
|
|
1061
|
-
`subagent ${activation.agentId} is queued but has no message gate`,
|
|
1062
|
-
);
|
|
1063
|
-
}
|
|
1064
|
-
return {
|
|
1065
|
-
kind: "legacy",
|
|
1066
|
-
agentId: childId,
|
|
1067
|
-
taskPath: resolvedPath,
|
|
1068
|
-
pending: {
|
|
1069
|
-
activation,
|
|
1070
|
-
accepted: waitForPromise(
|
|
1071
|
-
activation.currentMessageGate,
|
|
1072
|
-
signal,
|
|
1073
|
-
).then(() => {
|
|
1074
|
-
if (signal?.aborted) throw abortReason(signal);
|
|
1075
|
-
return session.followUp(message);
|
|
1076
|
-
}),
|
|
1077
|
-
},
|
|
1078
|
-
};
|
|
1079
|
-
}
|
|
1080
|
-
await session.followUp(message);
|
|
1081
|
-
return {
|
|
1082
|
-
kind: "legacy",
|
|
1083
|
-
agentId: childId,
|
|
1084
|
-
taskPath: resolvedPath,
|
|
1085
|
-
};
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
try {
|
|
1089
|
-
this.acquireParentOwnership(activation, parent);
|
|
1090
|
-
const started = this.startPrompt(activation, message, signal, {
|
|
1091
|
-
detachAtAcceptance: true,
|
|
1092
|
-
waitForCapacity: !parent.activation?.holdsBackgroundSlot,
|
|
1093
|
-
});
|
|
942
|
+
},
|
|
943
|
+
);
|
|
1094
944
|
return {
|
|
1095
|
-
kind: "
|
|
945
|
+
kind: "mailbox",
|
|
1096
946
|
agentId: childId,
|
|
1097
|
-
taskPath:
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
accepted: started.accepted,
|
|
1101
|
-
...(coldPrepared ? { coldPrepared } : {}),
|
|
1102
|
-
},
|
|
947
|
+
taskPath: descriptorTaskPath(activation.descriptor),
|
|
948
|
+
messageId: enqueued.message.messageId,
|
|
949
|
+
pendingMessages: enqueued.pendingMessages,
|
|
1103
950
|
};
|
|
1104
|
-
} catch (error) {
|
|
1105
|
-
if (coldPrepared && !activation.published) {
|
|
1106
|
-
activation.suppressSettlement = true;
|
|
1107
|
-
await this.rollbackActivation(activation, coldPrepared);
|
|
1108
|
-
}
|
|
1109
|
-
throw error;
|
|
1110
951
|
}
|
|
952
|
+
const located = await this.findPersistedChild(parent, childId);
|
|
953
|
+
if (!located) throw new Error(`unknown subagent: ${childId}; message was not delivered`);
|
|
954
|
+
this.assertContinuableDirectChild(parent, located.descriptor);
|
|
955
|
+
const manager = SessionManager.open(
|
|
956
|
+
located.sessionFile,
|
|
957
|
+
parent.sessionManager.getSessionDir(),
|
|
958
|
+
parent.cwd,
|
|
959
|
+
);
|
|
960
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
961
|
+
const enqueued = enqueueMailboxMessage(manager, {
|
|
962
|
+
senderAgentId: parent.agentId,
|
|
963
|
+
recipientAgentId: childId,
|
|
964
|
+
content: message,
|
|
965
|
+
});
|
|
966
|
+
return {
|
|
967
|
+
kind: "mailbox",
|
|
968
|
+
agentId: childId,
|
|
969
|
+
taskPath: descriptorTaskPath(located.descriptor),
|
|
970
|
+
messageId: enqueued.message.messageId,
|
|
971
|
+
pendingMessages: enqueued.pendingMessages,
|
|
972
|
+
};
|
|
1111
973
|
}
|
|
1112
974
|
|
|
1113
975
|
async followupTask(
|
|
@@ -1188,12 +1050,6 @@ export class SubagentCoordinator {
|
|
|
1188
1050
|
parent.cwd,
|
|
1189
1051
|
);
|
|
1190
1052
|
}
|
|
1191
|
-
if (descriptor.runtime.backgroundProtocol !== "mailbox-v2") {
|
|
1192
|
-
throw new Error(
|
|
1193
|
-
`subagent ${childId} uses the legacy background protocol; use send_message to start its next turn`,
|
|
1194
|
-
);
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
1053
|
const owner = mailboxOwner(descriptor);
|
|
1198
1054
|
const batch = readMailbox(manager.getEntries(), owner).pending;
|
|
1199
1055
|
if (batch.length === 0) {
|
|
@@ -1716,18 +1572,13 @@ export class SubagentCoordinator {
|
|
|
1716
1572
|
const content = `Background subagent ${readablePath} (${child.agentId}) reported:\n\n${truncated.text}${
|
|
1717
1573
|
truncated.truncated ? `\n\n[Report truncated; ${truncated.omittedBytes} bytes omitted.]` : ""
|
|
1718
1574
|
}`;
|
|
1719
|
-
await child.parent.deliver(
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
label: child.descriptor.label,
|
|
1727
|
-
...(truncated.truncated ? { truncated: true } : {}),
|
|
1728
|
-
},
|
|
1729
|
-
child.descriptor.runtime.reportDelivery,
|
|
1730
|
-
);
|
|
1575
|
+
await child.parent.deliver(REPORT_CUSTOM_TYPE, content, {
|
|
1576
|
+
kind: "report",
|
|
1577
|
+
childAgentId: child.agentId,
|
|
1578
|
+
taskPath: readablePath,
|
|
1579
|
+
label: child.descriptor.label,
|
|
1580
|
+
...(truncated.truncated ? { truncated: true } : {}),
|
|
1581
|
+
});
|
|
1731
1582
|
}
|
|
1732
1583
|
|
|
1733
1584
|
private runAdmittedOperation<T>(operation: () => Promise<T>): Promise<T> {
|
|
@@ -1812,14 +1663,12 @@ export class SubagentCoordinator {
|
|
|
1812
1663
|
|
|
1813
1664
|
createChildToolDefinitions(
|
|
1814
1665
|
getActivation: () => Activation,
|
|
1815
|
-
|
|
1816
|
-
defaultBackground = true,
|
|
1666
|
+
runtimeMode: RuntimeMode = "background",
|
|
1817
1667
|
agentDiscovery?: AgentDiscoveryResult,
|
|
1818
|
-
backgroundProtocol: NonNullable<SubagentSettings["backgroundProtocol"]> = "legacy",
|
|
1819
1668
|
): ToolDefinition[] {
|
|
1820
1669
|
const agentNames = agentDiscovery?.agents.map((agent) => agent.name);
|
|
1821
1670
|
const assertBackgroundControlEnabled = (toolName: string): void => {
|
|
1822
|
-
if (
|
|
1671
|
+
if (runtimeMode === "foreground") {
|
|
1823
1672
|
throw new Error(`tool "${toolName}" is unavailable in foreground-only mode`);
|
|
1824
1673
|
}
|
|
1825
1674
|
};
|
|
@@ -1836,12 +1685,10 @@ export class SubagentCoordinator {
|
|
|
1836
1685
|
label: "Subagent",
|
|
1837
1686
|
description:
|
|
1838
1687
|
"Delegate a standalone task to a named child path, with optional completed-turn context inheritance. " +
|
|
1839
|
-
(
|
|
1688
|
+
(runtimeMode === "foreground"
|
|
1840
1689
|
? "This foreground-only instance always waits for the result."
|
|
1841
|
-
:
|
|
1842
|
-
|
|
1843
|
-
: "It waits for the result by default; background mode returns a readable path plus durable id."),
|
|
1844
|
-
parameters: delegationParameters(enableRunInBackground, agentNames),
|
|
1690
|
+
: "It returns a readable path plus durable id; use send_message, followup_task, and wait_agent to continue it."),
|
|
1691
|
+
parameters: delegationParameters(agentNames),
|
|
1845
1692
|
execute: async (_id, params, signal, onUpdate) => {
|
|
1846
1693
|
const activation = getActivation();
|
|
1847
1694
|
const outcome = await this.delegate(
|
|
@@ -1861,11 +1708,11 @@ export class SubagentCoordinator {
|
|
|
1861
1708
|
name: "subagent_fork",
|
|
1862
1709
|
label: "Subagent Fork",
|
|
1863
1710
|
description:
|
|
1864
|
-
"Delegate a task to a child seeded with all completed turns from this conversation.
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
),
|
|
1711
|
+
"Delegate a task to a child seeded with all completed turns from this conversation. " +
|
|
1712
|
+
(runtimeMode === "foreground"
|
|
1713
|
+
? "This foreground-only instance always waits for the result."
|
|
1714
|
+
: "It returns a readable path plus durable id and can be continued through its mailbox."),
|
|
1715
|
+
parameters: forkDelegationParameters(agentNames),
|
|
1869
1716
|
execute: async (_id, params, signal, onUpdate) => {
|
|
1870
1717
|
const activation = getActivation();
|
|
1871
1718
|
const outcome = await this.delegate(
|
|
@@ -1885,9 +1732,7 @@ export class SubagentCoordinator {
|
|
|
1885
1732
|
name: "send_message",
|
|
1886
1733
|
label: "Send Message",
|
|
1887
1734
|
description:
|
|
1888
|
-
|
|
1889
|
-
? "Durably append a message to a direct mailbox-v2 child's FIFO mailbox without starting or resuming it."
|
|
1890
|
-
: "Queue a message as a direct continuable child's next FIFO turn. This returns acceptance, not the child's answer.",
|
|
1735
|
+
"Durably append a message to a direct continuable child's FIFO mailbox without starting or resuming it. Use followup_task to start the queued batch. This returns acceptance, not the child's answer.",
|
|
1891
1736
|
parameters: SendMessageParameters,
|
|
1892
1737
|
execute: async (_id, params, signal) => {
|
|
1893
1738
|
assertBackgroundControlEnabled("send_message");
|
|
@@ -1903,7 +1748,7 @@ export class SubagentCoordinator {
|
|
|
1903
1748
|
{
|
|
1904
1749
|
type: "text",
|
|
1905
1750
|
text:
|
|
1906
|
-
delivery.kind === "mailbox
|
|
1751
|
+
delivery.kind === "mailbox"
|
|
1907
1752
|
? `message ${delivery.messageId} durably enqueued for ${delivery.taskPath}; ${delivery.pendingMessages} pending`
|
|
1908
1753
|
: `message queued as the next turn for ${delivery.taskPath}`,
|
|
1909
1754
|
},
|
|
@@ -1913,7 +1758,7 @@ export class SubagentCoordinator {
|
|
|
1913
1758
|
action: "send",
|
|
1914
1759
|
agentId: delivery.agentId,
|
|
1915
1760
|
taskPath: delivery.taskPath,
|
|
1916
|
-
...(delivery.kind === "mailbox
|
|
1761
|
+
...(delivery.kind === "mailbox"
|
|
1917
1762
|
? {
|
|
1918
1763
|
messageId: delivery.messageId,
|
|
1919
1764
|
pendingMessages: delivery.pendingMessages,
|
|
@@ -1928,7 +1773,7 @@ export class SubagentCoordinator {
|
|
|
1928
1773
|
name: "followup_task",
|
|
1929
1774
|
label: "Follow-up Task",
|
|
1930
1775
|
description:
|
|
1931
|
-
"Start exactly one scheduled turn for a direct
|
|
1776
|
+
"Start exactly one scheduled turn for a direct child, atomically claiming its current pending FIFO mailbox batch.",
|
|
1932
1777
|
parameters: FollowupTaskParameters,
|
|
1933
1778
|
execute: async (_id, params, signal) => {
|
|
1934
1779
|
assertBackgroundControlEnabled("followup_task");
|
|
@@ -1961,7 +1806,7 @@ export class SubagentCoordinator {
|
|
|
1961
1806
|
name: "wait_agent",
|
|
1962
1807
|
label: "Wait Agent",
|
|
1963
1808
|
description:
|
|
1964
|
-
"Wait event-driven for unread completion updates from direct
|
|
1809
|
+
"Wait event-driven for unread completion updates from direct children. This does not start a child or occupy a background scheduler slot.",
|
|
1965
1810
|
parameters: WaitAgentParameters,
|
|
1966
1811
|
execute: async (id, params, signal) => {
|
|
1967
1812
|
assertBackgroundControlEnabled("wait_agent");
|
|
@@ -2099,10 +1944,7 @@ export class SubagentCoordinator {
|
|
|
2099
1944
|
scope === "descendants"
|
|
2100
1945
|
? ` parent=${entry.parentTaskPath} depth=${entry.depth}`
|
|
2101
1946
|
: "";
|
|
2102
|
-
const mailbox =
|
|
2103
|
-
entry.descriptor.runtime.backgroundProtocol === "mailbox-v2"
|
|
2104
|
-
? ` pending=${entry.pendingMessages} updates=${entry.unreadUpdates}`
|
|
2105
|
-
: "";
|
|
1947
|
+
const mailbox = ` pending=${entry.pendingMessages} updates=${entry.unreadUpdates}`;
|
|
2106
1948
|
return `${entry.taskPath} [${entry.status}]${mailbox}${location} — ${entry.descriptor.label} (${entry.descriptor.agent.name}) id=${entry.agentId}`;
|
|
2107
1949
|
})
|
|
2108
1950
|
.join("\n");
|
|
@@ -2180,10 +2022,8 @@ export class SubagentCoordinator {
|
|
|
2180
2022
|
if (!activation) throw new Error("subagent activation is not published yet");
|
|
2181
2023
|
return activation;
|
|
2182
2024
|
},
|
|
2183
|
-
descriptor.runtime.
|
|
2184
|
-
descriptor.runtime.defaultBackground,
|
|
2025
|
+
descriptor.runtime.runtimeMode,
|
|
2185
2026
|
agentDiscovery,
|
|
2186
|
-
descriptor.runtime.backgroundProtocol,
|
|
2187
2027
|
);
|
|
2188
2028
|
const model =
|
|
2189
2029
|
options.parent.modelRuntime.getModel(descriptor.model.provider, descriptor.model.id) ??
|
|
@@ -2299,17 +2139,8 @@ export class SubagentCoordinator {
|
|
|
2299
2139
|
return false;
|
|
2300
2140
|
}
|
|
2301
2141
|
if (
|
|
2302
|
-
|
|
2303
|
-
BACKGROUND_CONTROL_TOOLS.has(tool)
|
|
2304
|
-
) {
|
|
2305
|
-
return false;
|
|
2306
|
-
}
|
|
2307
|
-
if (
|
|
2308
|
-
descriptor.runtime.backgroundProtocol !== "mailbox-v2"
|
|
2309
|
-
&& (
|
|
2310
|
-
tool === "followup_task"
|
|
2311
|
-
|| tool === "wait_agent"
|
|
2312
|
-
)
|
|
2142
|
+
descriptor.runtime.runtimeMode === "foreground"
|
|
2143
|
+
&& BACKGROUND_CONTROL_TOOLS.has(tool)
|
|
2313
2144
|
) {
|
|
2314
2145
|
return false;
|
|
2315
2146
|
}
|
|
@@ -2428,13 +2259,6 @@ export class SubagentCoordinator {
|
|
|
2428
2259
|
resolveAccepted = resolvePromise;
|
|
2429
2260
|
rejectAccepted = rejectPromise;
|
|
2430
2261
|
});
|
|
2431
|
-
activation.currentMessageGate = accepted;
|
|
2432
|
-
const clearCurrentMessageGate = () => {
|
|
2433
|
-
if (activation.currentMessageGate === accepted) {
|
|
2434
|
-
activation.currentMessageGate = undefined;
|
|
2435
|
-
}
|
|
2436
|
-
};
|
|
2437
|
-
void accepted.then(clearCurrentMessageGate, clearCurrentMessageGate);
|
|
2438
2262
|
const turnAbortController = new AbortController();
|
|
2439
2263
|
activation.turnAbortController = turnAbortController;
|
|
2440
2264
|
const abort = () => {
|
|
@@ -2570,86 +2394,6 @@ export class SubagentCoordinator {
|
|
|
2570
2394
|
return { turnId, accepted, result: lifecycle };
|
|
2571
2395
|
}
|
|
2572
2396
|
|
|
2573
|
-
private startInternalMessage(
|
|
2574
|
-
activation: Activation,
|
|
2575
|
-
customType: string,
|
|
2576
|
-
content: string,
|
|
2577
|
-
details: ParentMessageDetails,
|
|
2578
|
-
): void {
|
|
2579
|
-
if (activation.currentRun || activation.disposed) return;
|
|
2580
|
-
activation.pendingSettlement = undefined;
|
|
2581
|
-
this.resetTurnCapture(activation);
|
|
2582
|
-
const turnId = uuidv7();
|
|
2583
|
-
queueAgentTurn(activation.controlState, turnId);
|
|
2584
|
-
this.emitUpdate(activation);
|
|
2585
|
-
const turnAbortController = new AbortController();
|
|
2586
|
-
activation.turnAbortController = turnAbortController;
|
|
2587
|
-
let resolveMessageGate!: () => void;
|
|
2588
|
-
let rejectMessageGate!: (error: Error) => void;
|
|
2589
|
-
let messageGateSettled = false;
|
|
2590
|
-
const messageGate = new Promise<void>((resolvePromise, rejectPromise) => {
|
|
2591
|
-
resolveMessageGate = resolvePromise;
|
|
2592
|
-
rejectMessageGate = rejectPromise;
|
|
2593
|
-
});
|
|
2594
|
-
activation.currentMessageGate = messageGate;
|
|
2595
|
-
void messageGate.catch(() => {});
|
|
2596
|
-
const core = (async (): Promise<SubagentRunResult> => {
|
|
2597
|
-
let permit: BackgroundRunPermit | undefined;
|
|
2598
|
-
try {
|
|
2599
|
-
permit = await this.acquireBackgroundRun(
|
|
2600
|
-
activation,
|
|
2601
|
-
turnAbortController.signal,
|
|
2602
|
-
true,
|
|
2603
|
-
);
|
|
2604
|
-
startAgentTurn(activation.controlState, turnId);
|
|
2605
|
-
activation.startedTurnIds.add(turnId);
|
|
2606
|
-
this.emitTurnStart(activation, turnId);
|
|
2607
|
-
this.emitUpdate(activation);
|
|
2608
|
-
messageGateSettled = true;
|
|
2609
|
-
resolveMessageGate();
|
|
2610
|
-
await activation.runtime.session.sendCustomMessage(
|
|
2611
|
-
{ customType, content, display: true, details },
|
|
2612
|
-
{ triggerTurn: true, deliverAs: "followUp" },
|
|
2613
|
-
);
|
|
2614
|
-
return this.collectResult(activation, turnId, "completed");
|
|
2615
|
-
} catch (error) {
|
|
2616
|
-
activation.lastError = errorText(error);
|
|
2617
|
-
if (!messageGateSettled) {
|
|
2618
|
-
messageGateSettled = true;
|
|
2619
|
-
rejectMessageGate(
|
|
2620
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
2621
|
-
);
|
|
2622
|
-
}
|
|
2623
|
-
const result = this.collectResult(
|
|
2624
|
-
activation,
|
|
2625
|
-
turnId,
|
|
2626
|
-
turnAbortController.signal.aborted ? "aborted" : "error",
|
|
2627
|
-
);
|
|
2628
|
-
if (!result.output) result.output = activation.lastError;
|
|
2629
|
-
return result;
|
|
2630
|
-
} finally {
|
|
2631
|
-
if (activation.turnAbortController === turnAbortController) {
|
|
2632
|
-
activation.turnAbortController = undefined;
|
|
2633
|
-
}
|
|
2634
|
-
if (activation.currentMessageGate === messageGate) {
|
|
2635
|
-
activation.currentMessageGate = undefined;
|
|
2636
|
-
}
|
|
2637
|
-
if (permit) {
|
|
2638
|
-
activation.holdsBackgroundSlot = false;
|
|
2639
|
-
permit.release();
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
})();
|
|
2643
|
-
let lifecycle!: Promise<SubagentRunResult>;
|
|
2644
|
-
lifecycle = core.then(async (result) => {
|
|
2645
|
-
if (activation.currentRun === lifecycle) activation.currentRun = undefined;
|
|
2646
|
-
await this.runFinished(activation, result);
|
|
2647
|
-
return result;
|
|
2648
|
-
});
|
|
2649
|
-
activation.currentRun = lifecycle;
|
|
2650
|
-
void lifecycle.catch(() => {});
|
|
2651
|
-
}
|
|
2652
|
-
|
|
2653
2397
|
private async runFinished(activation: Activation, result: SubagentRunResult): Promise<void> {
|
|
2654
2398
|
activation.pendingMailboxClaims.delete(result.turnId);
|
|
2655
2399
|
activation.persistenceGate.reject(
|
|
@@ -2671,7 +2415,6 @@ export class SubagentCoordinator {
|
|
|
2671
2415
|
&& !activation.silentSettlementTurnIds.has(result.turnId)
|
|
2672
2416
|
&& activation.published
|
|
2673
2417
|
&& activation.descriptor.mode === "continuable"
|
|
2674
|
-
&& activation.descriptor.runtime.backgroundProtocol === "mailbox-v2"
|
|
2675
2418
|
) {
|
|
2676
2419
|
try {
|
|
2677
2420
|
appendCompletionUpdate(activation.parent.sessionManager, {
|
|
@@ -2743,18 +2486,6 @@ export class SubagentCoordinator {
|
|
|
2743
2486
|
const finalizePromise = (async (): Promise<boolean> => {
|
|
2744
2487
|
let retained = false;
|
|
2745
2488
|
try {
|
|
2746
|
-
const silentSettlement =
|
|
2747
|
-
activation.silentSettlementTurnIds.has(result.turnId);
|
|
2748
|
-
if (
|
|
2749
|
-
activation.descriptor.runtime.backgroundProtocol !== "mailbox-v2"
|
|
2750
|
-
&& !silentSettlement
|
|
2751
|
-
&& !activation.suppressSettlement
|
|
2752
|
-
&& !this.draining
|
|
2753
|
-
) {
|
|
2754
|
-
await this.deliverSettlement(activation, result).catch((error) => {
|
|
2755
|
-
activation.lastError = errorText(error);
|
|
2756
|
-
});
|
|
2757
|
-
}
|
|
2758
2489
|
this.emitEnd(activation, result);
|
|
2759
2490
|
activation.silentSettlementTurnIds.delete(result.turnId);
|
|
2760
2491
|
activation.pendingSettlement = undefined;
|
|
@@ -2786,30 +2517,6 @@ export class SubagentCoordinator {
|
|
|
2786
2517
|
}
|
|
2787
2518
|
}
|
|
2788
2519
|
|
|
2789
|
-
private async deliverSettlement(activation: Activation, result: SubagentRunResult): Promise<void> {
|
|
2790
|
-
const readablePath = descriptorTaskPath(activation.descriptor);
|
|
2791
|
-
const truncated = truncateUtf8(result.output, activation.descriptor.runtime.maxOutputBytes);
|
|
2792
|
-
const closing = truncated.text.trim()
|
|
2793
|
-
? `Its closing message:\n\n${truncated.text}`
|
|
2794
|
-
: "It left no closing message.";
|
|
2795
|
-
const content = `Background subagent ${readablePath} (${activation.agentId}) ${stopReasonHeadline(result.stopReason)} and will do no further work unless you send it more.\n\n${closing}${
|
|
2796
|
-
truncated.truncated ? `\n\n[Closing message truncated; ${truncated.omittedBytes} bytes omitted.]` : ""
|
|
2797
|
-
}`;
|
|
2798
|
-
await activation.parent.deliver(
|
|
2799
|
-
SETTLED_CUSTOM_TYPE,
|
|
2800
|
-
content,
|
|
2801
|
-
{
|
|
2802
|
-
kind: "settled",
|
|
2803
|
-
childAgentId: activation.agentId,
|
|
2804
|
-
taskPath: readablePath,
|
|
2805
|
-
label: activation.descriptor.label,
|
|
2806
|
-
stopReason: result.stopReason,
|
|
2807
|
-
...(truncated.truncated ? { truncated: true } : {}),
|
|
2808
|
-
},
|
|
2809
|
-
"wakeup",
|
|
2810
|
-
);
|
|
2811
|
-
}
|
|
2812
|
-
|
|
2813
2520
|
private parentForActivation(activation: Activation): ParentRef {
|
|
2814
2521
|
return {
|
|
2815
2522
|
agentId: activation.agentId,
|
|
@@ -2822,26 +2529,15 @@ export class SubagentCoordinator {
|
|
|
2822
2529
|
thinkingLevel: activation.runtime.session.thinkingLevel,
|
|
2823
2530
|
projectTrusted: activation.parent.projectTrusted,
|
|
2824
2531
|
activation,
|
|
2825
|
-
deliver: async (customType, content, details
|
|
2532
|
+
deliver: async (customType, content, details) => {
|
|
2826
2533
|
if (activation.disposed) throw new Error(`parent subagent ${activation.agentId} is no longer resident`);
|
|
2827
2534
|
const session = activation.runtime.session;
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
);
|
|
2835
|
-
return;
|
|
2836
|
-
}
|
|
2837
|
-
if (activation.currentRun || session.isStreaming) {
|
|
2838
|
-
await session.sendCustomMessage(
|
|
2839
|
-
{ customType, content, display: true, details },
|
|
2840
|
-
{ triggerTurn: true, deliverAs: "followUp" },
|
|
2841
|
-
);
|
|
2842
|
-
return;
|
|
2843
|
-
}
|
|
2844
|
-
this.startInternalMessage(activation, customType, content, details);
|
|
2535
|
+
await session.sendCustomMessage(
|
|
2536
|
+
{ customType, content, display: true, details },
|
|
2537
|
+
session.isStreaming
|
|
2538
|
+
? { triggerTurn: false, deliverAs: "nextTurn" }
|
|
2539
|
+
: { triggerTurn: false },
|
|
2540
|
+
);
|
|
2845
2541
|
},
|
|
2846
2542
|
};
|
|
2847
2543
|
}
|
|
@@ -3379,7 +3075,7 @@ export class SubagentCoordinator {
|
|
|
3379
3075
|
if (parentPath === undefined || parentDepth === undefined) {
|
|
3380
3076
|
continue;
|
|
3381
3077
|
}
|
|
3382
|
-
|
|
3078
|
+
{
|
|
3383
3079
|
let expectedPath: string;
|
|
3384
3080
|
try {
|
|
3385
3081
|
expectedPath = taskPath(
|
|
@@ -3418,7 +3114,6 @@ export class SubagentCoordinator {
|
|
|
3418
3114
|
);
|
|
3419
3115
|
const problems = new Map<string, string>();
|
|
3420
3116
|
for (const record of records) {
|
|
3421
|
-
if (record.descriptor.version !== 3) continue;
|
|
3422
3117
|
const parent = byId.get(record.descriptor.parentAgentId);
|
|
3423
3118
|
if (parent) {
|
|
3424
3119
|
let expectedPath: string | undefined;
|
|
@@ -3559,28 +3254,26 @@ export class SubagentCoordinator {
|
|
|
3559
3254
|
for (const activation of this.active.values()) {
|
|
3560
3255
|
if (activation.descriptor.cwd !== parent.cwd) continue;
|
|
3561
3256
|
let pendingMessages = 0;
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
continue;
|
|
3583
|
-
}
|
|
3257
|
+
const mailbox = foldOwnedMailbox(
|
|
3258
|
+
activation.runtime.session.sessionManager.getEntries(),
|
|
3259
|
+
mailboxOwner(activation.descriptor),
|
|
3260
|
+
);
|
|
3261
|
+
if (mailbox.kind === "valid") {
|
|
3262
|
+
pendingMessages = mailbox.snapshot.pending.length;
|
|
3263
|
+
} else {
|
|
3264
|
+
activeDiagnostics.push({
|
|
3265
|
+
kind: "diagnostic",
|
|
3266
|
+
piSessionId: activation.runtime.session.sessionId,
|
|
3267
|
+
reason: "corrupt",
|
|
3268
|
+
...(activation.runtime.session.sessionFile
|
|
3269
|
+
? { sessionFile: activation.runtime.session.sessionFile }
|
|
3270
|
+
: {}),
|
|
3271
|
+
...(activation.descriptor.parentSessionFile
|
|
3272
|
+
? { parentSessionFile: activation.descriptor.parentSessionFile }
|
|
3273
|
+
: {}),
|
|
3274
|
+
message: `corrupt subagent mailbox: ${mailbox.message}`,
|
|
3275
|
+
});
|
|
3276
|
+
continue;
|
|
3584
3277
|
}
|
|
3585
3278
|
const completions = foldCompletionMailbox(
|
|
3586
3279
|
activation.runtime.session.sessionManager.getEntries(),
|
|
@@ -3658,7 +3351,6 @@ export class SubagentCoordinator {
|
|
|
3658
3351
|
|
|
3659
3352
|
export {
|
|
3660
3353
|
REPORT_CUSTOM_TYPE,
|
|
3661
|
-
SETTLED_CUSTOM_TYPE,
|
|
3662
3354
|
AGENT_CUSTOM_TYPE,
|
|
3663
3355
|
LINEAGE_CUSTOM_TYPE,
|
|
3664
3356
|
};
|