@sema-agent/core 5.55.0 → 5.57.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/CHANGELOG.md +140 -0
- package/dist/agents/send-message-tool.d.ts +11 -0
- package/dist/agents/send-message-tool.js +81 -13
- package/dist/agents/subagent.js +250 -89
- package/dist/agents/team.d.ts +10 -1
- package/dist/agents/team.js +1 -0
- package/dist/brain/anthropic.js +15 -5
- package/dist/brain/circuit-breaker.js +2 -1
- package/dist/brain/degrading.js +4 -1
- package/dist/brain/failover.js +16 -1
- package/dist/brain/open-responses.js +15 -5
- package/dist/brain/openai.js +16 -5
- package/dist/brain/request-params.d.ts +30 -27
- package/dist/brain/request-params.js +1 -7
- package/dist/brain/route-adjudicator.d.ts +190 -0
- package/dist/brain/route-adjudicator.js +189 -0
- package/dist/brain/route-conformance.d.ts +55 -0
- package/dist/brain/route-conformance.js +136 -0
- package/dist/brain/routing.js +8 -3
- package/dist/core/auto-compaction.d.ts +17 -4
- package/dist/core/auto-compaction.js +3 -0
- package/dist/core/context-edit.d.ts +55 -6
- package/dist/core/context-edit.js +12 -1
- package/dist/core/hooks.d.ts +293 -11
- package/dist/core/hooks.js +158 -11
- package/dist/core/human-input-projection.d.ts +20 -2
- package/dist/core/human-input-projection.js +9 -0
- package/dist/core/mcp.js +4 -4
- package/dist/core/memory-engine/engine.d.ts +15 -5
- package/dist/core/memory-engine/engine.js +3 -1
- package/dist/core/permission-rule-consent.d.ts +45 -0
- package/dist/core/permission-rule-consent.js +40 -11
- package/dist/core/permission-rule-model.d.ts +110 -75
- package/dist/core/permission-rule-model.js +61 -28
- package/dist/core/permission-rules.d.ts +23 -15
- package/dist/core/permission-rules.js +40 -31
- package/dist/core/runner/prepare-task.d.ts +8 -0
- package/dist/core/runner/prepare-task.js +66 -26
- package/dist/core/runner/runtask.d.ts +4 -1
- package/dist/core/runner/runtask.js +206 -21
- package/dist/core/runner/session-rule-policy.js +5 -5
- package/dist/core/scheduler.d.ts +5 -0
- package/dist/core/session-reconcile.d.ts +32 -0
- package/dist/core/session-reconcile.js +15 -0
- package/dist/core/side-query.d.ts +12 -5
- package/dist/core/task-notification.d.ts +34 -7
- package/dist/core/task-notification.js +11 -1
- package/dist/core/task-registry-agent.d.ts +20 -3
- package/dist/core/task-registry-agent.js +31 -2
- package/dist/core/tool-policy.d.ts +14 -9
- package/dist/core/tool-policy.js +27 -22
- package/dist/core/types.d.ts +69 -11
- package/dist/core/untrusted-text.js +8 -0
- package/dist/engine/compaction/compaction.d.ts +77 -7
- package/dist/engine/compaction/compaction.js +98 -9
- package/dist/engine/compaction/utils.d.ts +4 -0
- package/dist/engine/compaction/utils.js +6 -0
- package/dist/engine/harness/agent-harness.d.ts +84 -0
- package/dist/engine/harness/agent-harness.js +114 -13
- package/dist/engine/harness/messages.d.ts +4 -2
- package/dist/engine/harness/messages.js +7 -2
- package/dist/engine/harness/types.d.ts +16 -6
- package/dist/engine/llm/types.d.ts +65 -0
- package/dist/engine/loop/types.d.ts +7 -0
- package/dist/engine/session/import-validate.js +10 -0
- package/dist/engine/session/session.js +2 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/internal/llm.d.ts +1 -1
- package/dist/orchestration/run-spec.js +8 -1
- package/dist/prompts/default.d.ts +12 -6
- package/dist/prompts/default.js +2 -0
- package/dist/scenarios/scenario-registry.d.ts +5 -1
- package/dist/scenarios/scenario-registry.js +4 -2
- package/dist/tools/fs/index.js +8 -1
- package/dist/tools/scheduler-tools.js +28 -6
- package/dist/tools/web.d.ts +15 -0
- package/dist/tools/web.js +8 -2
- package/dist/tools/worktree.js +2 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +19 -1
package/dist/agents/subagent.js
CHANGED
|
@@ -1032,6 +1032,134 @@ export function delegationEntryLedgerFootprint(registry) {
|
|
|
1032
1032
|
handles += s.size;
|
|
1033
1033
|
return { keys: byKey.size, handles };
|
|
1034
1034
|
}
|
|
1035
|
+
const localDelegationInFlight = new WeakMap();
|
|
1036
|
+
function localDelegationInFlightCount(anchor, key) {
|
|
1037
|
+
return localDelegationInFlight.get(anchor)?.get(key) ?? 0;
|
|
1038
|
+
}
|
|
1039
|
+
function chargeLocalDelegationInFlight(anchor, key) {
|
|
1040
|
+
let byKey = localDelegationInFlight.get(anchor);
|
|
1041
|
+
if (byKey === undefined) {
|
|
1042
|
+
byKey = new Map();
|
|
1043
|
+
localDelegationInFlight.set(anchor, byKey);
|
|
1044
|
+
}
|
|
1045
|
+
byKey.set(key, (byKey.get(key) ?? 0) + 1);
|
|
1046
|
+
let released = false;
|
|
1047
|
+
return () => {
|
|
1048
|
+
if (released)
|
|
1049
|
+
return;
|
|
1050
|
+
released = true;
|
|
1051
|
+
const live = localDelegationInFlight.get(anchor);
|
|
1052
|
+
if (live === undefined)
|
|
1053
|
+
return;
|
|
1054
|
+
const n = (live.get(key) ?? 0) - 1;
|
|
1055
|
+
if (n > 0)
|
|
1056
|
+
live.set(key, n);
|
|
1057
|
+
else
|
|
1058
|
+
live.delete(key);
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
const spawnAbortedResult = (wt) => ({
|
|
1062
|
+
isError: true,
|
|
1063
|
+
content: `Sub-agent not started: the delegating call was aborted.${wt ? `\n${wt}` : ""}`,
|
|
1064
|
+
details: { error: "aborted" },
|
|
1065
|
+
});
|
|
1066
|
+
const noopRelease = () => undefined;
|
|
1067
|
+
const DELEGATION_POOL_DEFAULT_SCOPE = "default";
|
|
1068
|
+
async function untilAbort(p, signal) {
|
|
1069
|
+
if (signal === undefined)
|
|
1070
|
+
return await p;
|
|
1071
|
+
if (signal.aborted)
|
|
1072
|
+
throw new Error("delegation entry gate: aborted before the durable floor was read");
|
|
1073
|
+
let onAbort;
|
|
1074
|
+
try {
|
|
1075
|
+
return await Promise.race([
|
|
1076
|
+
p,
|
|
1077
|
+
new Promise((_resolve, reject) => {
|
|
1078
|
+
onAbort = () => reject(new Error("delegation entry gate: aborted while reading the durable floor"));
|
|
1079
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1080
|
+
}),
|
|
1081
|
+
]);
|
|
1082
|
+
}
|
|
1083
|
+
finally {
|
|
1084
|
+
if (onAbort !== undefined)
|
|
1085
|
+
signal.removeEventListener("abort", onAbort);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
async function openDelegationEntryGate(input) {
|
|
1089
|
+
const { poolAnchor, registry, store, root, caps, revival, signal } = input;
|
|
1090
|
+
if (root === undefined)
|
|
1091
|
+
return { key: undefined, admit: () => ({ admitted: true, release: noopRelease }) };
|
|
1092
|
+
const scope = input.scope ?? DELEGATION_POOL_DEFAULT_SCOPE;
|
|
1093
|
+
const key = JSON.stringify([scope, root]);
|
|
1094
|
+
let storedHandles = [];
|
|
1095
|
+
let storedEnumerationOk = false;
|
|
1096
|
+
if (store !== undefined && !revival) {
|
|
1097
|
+
try {
|
|
1098
|
+
storedHandles = (await untilAbort(store.listBySession(scope, root), signal)).map((r) => r.handle);
|
|
1099
|
+
storedEnumerationOk = true;
|
|
1100
|
+
}
|
|
1101
|
+
catch {
|
|
1102
|
+
storedHandles = [];
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
const admit = (kind) => {
|
|
1106
|
+
const activeFace = registry === undefined ? undefined : registry.activeDelegationHandles;
|
|
1107
|
+
const active = typeof activeFace === "function" ? activeFace.call(registry, scope, root) : [];
|
|
1108
|
+
const local = localDelegationInFlightCount(poolAnchor, key);
|
|
1109
|
+
const running = active.length + local;
|
|
1110
|
+
if (running >= caps.maxConcurrent) {
|
|
1111
|
+
return {
|
|
1112
|
+
admitted: false,
|
|
1113
|
+
refusal: {
|
|
1114
|
+
code: "delegation.concurrency_cap",
|
|
1115
|
+
text: `this session tree already has ${running} agents running — the concurrency cap (${caps.maxConcurrent}; RunnerDeps.delegationEntryCaps.maxConcurrent) refuses another. ${kind === "registered" ? "Wait for one to complete (you will be notified) or stop one, then relaunch." : "Wait for one of them to finish or stop one, then try again."}`,
|
|
1116
|
+
},
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
if (!revival) {
|
|
1120
|
+
const ledger = delegationEntryLedger(poolAnchor, key);
|
|
1121
|
+
let retained;
|
|
1122
|
+
if (store !== undefined && storedEnumerationOk) {
|
|
1123
|
+
const keep = new Set([...storedHandles, ...active]);
|
|
1124
|
+
if (kind === "registered") {
|
|
1125
|
+
for (const h of [...ledger])
|
|
1126
|
+
if (!keep.has(h))
|
|
1127
|
+
ledger.delete(h);
|
|
1128
|
+
retained = new Set([...storedHandles, ...ledger]).size;
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
const survivors = new Set(storedHandles);
|
|
1132
|
+
for (const h of ledger)
|
|
1133
|
+
if (keep.has(h))
|
|
1134
|
+
survivors.add(h);
|
|
1135
|
+
retained = survivors.size;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
else {
|
|
1139
|
+
retained = ledger.size;
|
|
1140
|
+
}
|
|
1141
|
+
const cumulative = retained + local;
|
|
1142
|
+
if (cumulative >= caps.maxCumulativePerSession) {
|
|
1143
|
+
return {
|
|
1144
|
+
admitted: false,
|
|
1145
|
+
refusal: {
|
|
1146
|
+
code: "delegation.session_cap",
|
|
1147
|
+
text: `this session tree has already launched ${cumulative} agents in its retained window — the cumulative cap (${caps.maxCumulativePerSession}; RunnerDeps.delegationEntryCaps.maxCumulativePerSession) refuses more. Continue an existing agent (SendMessage) instead of launching new ones.`,
|
|
1148
|
+
},
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
return { admitted: true, release: kind === "local" ? chargeLocalDelegationInFlight(poolAnchor, key) : noopRelease };
|
|
1153
|
+
};
|
|
1154
|
+
return { key, admit };
|
|
1155
|
+
}
|
|
1156
|
+
function chargeDelegationEntryHandle(registry, key, store, handle) {
|
|
1157
|
+
if (handle !== undefined)
|
|
1158
|
+
delegationEntryLedger(registry, key).add(handle);
|
|
1159
|
+
bindDelegationLedgerKeyStore(registry, key, store);
|
|
1160
|
+
armDelegationLedgerLifecycle(registry);
|
|
1161
|
+
sweepDelegationLedgerRound(registry, 3);
|
|
1162
|
+
}
|
|
1035
1163
|
export function normalizeSubagentType(value) {
|
|
1036
1164
|
return value.normalize("NFKC").toLowerCase().replace(/[\p{White_Space}\p{Pd}_]+/gu, "");
|
|
1037
1165
|
}
|
|
@@ -1314,7 +1442,6 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1314
1442
|
prepareArguments: (args) => foldGeneralPurposeAlias(args, generalPurposeShadowed),
|
|
1315
1443
|
execute: async (args, ctx) => {
|
|
1316
1444
|
const a = foldGeneralPurposeAlias(args, generalPurposeShadowed);
|
|
1317
|
-
const wantsBackground = opts.background !== undefined && a.run_in_background !== false;
|
|
1318
1445
|
const reviveClaim = ctx.reviveClaim;
|
|
1319
1446
|
if (reviveClaim !== undefined && opts.background === undefined) {
|
|
1320
1447
|
return {
|
|
@@ -1358,6 +1485,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1358
1485
|
}
|
|
1359
1486
|
const omitted = rawType === "" || (!generalPurposeShadowed && rawType === GENERAL_PURPOSE_SUBAGENT_TYPE);
|
|
1360
1487
|
const def = hasAgents && !wantsFork && !omitted ? agentMap.get(rawType) : undefined;
|
|
1488
|
+
const wantsBackground = opts.background !== undefined && (a.run_in_background !== false || def?.background === true);
|
|
1361
1489
|
const spawnAgentType = wantsFork ? FORK_SUBAGENT_TYPE : omitted ? GENERAL_PURPOSE_SUBAGENT_TYPE : rawType;
|
|
1362
1490
|
if (!def && !wantsFork && !omitted) {
|
|
1363
1491
|
return {
|
|
@@ -1789,6 +1917,21 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1789
1917
|
return { ...(recentSteps ? { recentSteps } : {}), ...(editedFiles ? { editedFiles } : {}) };
|
|
1790
1918
|
};
|
|
1791
1919
|
const treeScope = reviveClaim?.row.scope ?? ctx.principal ?? opts.background?.scope;
|
|
1920
|
+
const entryCapPoolAnchor = opts.background?.registry ?? opts;
|
|
1921
|
+
const entryCaps = ctx.delegationEntryCaps ?? { maxConcurrent: DELEGATION_MAX_CONCURRENT_DEFAULT, maxCumulativePerSession: DELEGATION_MAX_PER_SESSION_DEFAULT };
|
|
1922
|
+
const entryCapRoot = reviveClaim !== undefined
|
|
1923
|
+
? (reviveClaim.row.rootSessionId ?? reviveClaim.row.parentSessionId ?? (reviveClaim.row.sessionScoped ? reviveClaim.row.owner : undefined))
|
|
1924
|
+
: (ctx.rootSessionId ?? ctx.sessionId);
|
|
1925
|
+
const openEntryGate = () => openDelegationEntryGate({
|
|
1926
|
+
poolAnchor: entryCapPoolAnchor,
|
|
1927
|
+
registry: opts.background?.registry,
|
|
1928
|
+
store: opts.background?.agentStore,
|
|
1929
|
+
scope: treeScope,
|
|
1930
|
+
root: entryCapRoot,
|
|
1931
|
+
caps: entryCaps,
|
|
1932
|
+
revival: reviveClaim !== undefined,
|
|
1933
|
+
signal: ctx.signal,
|
|
1934
|
+
});
|
|
1792
1935
|
const provenanceRequest = ctx.delegationProvenanceForChildren?.();
|
|
1793
1936
|
const childProvenanceRef = provenanceRequest !== undefined ? { current: newDelegationProvenanceAggregate() } : undefined;
|
|
1794
1937
|
const childAttestation = (status) => childProvenanceRef !== undefined ? reduceDelegationAttestation(childProvenanceRef.current, { completed: status === "completed" }) : undefined;
|
|
@@ -2053,7 +2196,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2053
2196
|
if (ctx.signal?.aborted) {
|
|
2054
2197
|
await cancelObserver();
|
|
2055
2198
|
const wt = await finishWorktree();
|
|
2056
|
-
return
|
|
2199
|
+
return spawnAbortedResult(wt);
|
|
2057
2200
|
}
|
|
2058
2201
|
if (spawnVerdict.kind === "block") {
|
|
2059
2202
|
await cancelObserver();
|
|
@@ -2106,6 +2249,21 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2106
2249
|
const shortDesc = `fork: ${(typeof a.description === "string" && a.description.trim() ? a.description.trim() : prompt).slice(0, 180)}`;
|
|
2107
2250
|
const bgOwner = sessionScopedBg ? ctx.sessionId : ctx.taskId ?? bg.owner;
|
|
2108
2251
|
const bgScope = treeScope;
|
|
2252
|
+
const forkEntryGate = await openEntryGate();
|
|
2253
|
+
{
|
|
2254
|
+
const admission = forkEntryGate.admit("registered");
|
|
2255
|
+
if (!admission.admitted) {
|
|
2256
|
+
dropHostAbortListener();
|
|
2257
|
+
try {
|
|
2258
|
+
await (releaseForked ? releaseForked() : opts.runner.sessions.release?.(forkedId));
|
|
2259
|
+
}
|
|
2260
|
+
catch {
|
|
2261
|
+
}
|
|
2262
|
+
await cancelObserver();
|
|
2263
|
+
const wt = await finishWorktree();
|
|
2264
|
+
return errorResult(`Sub-agent not started in background: ${admission.refusal.text}${wt ? `\n${wt}` : ""}`, { error: admission.refusal.code, code: admission.refusal.code });
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2109
2267
|
const forkSettlementSeat = ctx.delegationSettlement?.();
|
|
2110
2268
|
const forkSettleId = forkSettlementSeat !== undefined ? `bg-${uuidv7()}` : undefined;
|
|
2111
2269
|
if (forkSettlementSeat !== undefined && forkSettleId !== undefined) {
|
|
@@ -2195,6 +2353,8 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2195
2353
|
}
|
|
2196
2354
|
return { isError: true, content: `Sub-agent not started in background: ${e instanceof Error ? e.message : String(e)}${wt ? `\n${wt}` : ""}`, details: { error: "register_failed" } };
|
|
2197
2355
|
}
|
|
2356
|
+
if (forkEntryGate.key !== undefined)
|
|
2357
|
+
chargeDelegationEntryHandle(bg.registry, forkEntryGate.key, bg.agentStore, taskId);
|
|
2198
2358
|
childInternals.peerSelfRef?.addAxis("h", taskId);
|
|
2199
2359
|
bg.registry.bindBackgroundAgentSession(taskId, forkedId);
|
|
2200
2360
|
const forkBgHangAt = Date.now();
|
|
@@ -2507,6 +2667,28 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2507
2667
|
const bgIgnoredNote = a.run_in_background === true
|
|
2508
2668
|
? `note: run_in_background was ignored for subagent_type "${FORK_SUBAGENT_TYPE}" — the fork ran synchronously (this delegation tool has no background surface configured).`
|
|
2509
2669
|
: undefined;
|
|
2670
|
+
const syncForkEntryGate = await openEntryGate();
|
|
2671
|
+
if (ctx.signal?.aborted) {
|
|
2672
|
+
try {
|
|
2673
|
+
await (releaseForked ? releaseForked() : opts.runner.sessions.release?.(forkedId));
|
|
2674
|
+
}
|
|
2675
|
+
catch {
|
|
2676
|
+
}
|
|
2677
|
+
await cancelObserver();
|
|
2678
|
+
const wt = await finishWorktree();
|
|
2679
|
+
return spawnAbortedResult(wt);
|
|
2680
|
+
}
|
|
2681
|
+
const syncForkAdmission = syncForkEntryGate.admit("local");
|
|
2682
|
+
if (!syncForkAdmission.admitted) {
|
|
2683
|
+
try {
|
|
2684
|
+
await (releaseForked ? releaseForked() : opts.runner.sessions.release?.(forkedId));
|
|
2685
|
+
}
|
|
2686
|
+
catch {
|
|
2687
|
+
}
|
|
2688
|
+
await cancelObserver();
|
|
2689
|
+
const wt = await finishWorktree();
|
|
2690
|
+
return errorResult(`Sub-agent not started: ${syncForkAdmission.refusal.text}${wt ? `\n${wt}` : ""}`, { error: syncForkAdmission.refusal.code, code: syncForkAdmission.refusal.code });
|
|
2691
|
+
}
|
|
2510
2692
|
let forkChild;
|
|
2511
2693
|
try {
|
|
2512
2694
|
forkChild = await opts.runner.runTask({ ...buildChildSpec(ctx.signal), sessionId: forkedId, requireExistingSession: true, objective: forkObjective }, forkInternals);
|
|
@@ -2527,6 +2709,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2527
2709
|
const wt = await finishWorktree();
|
|
2528
2710
|
return { isError: true, content: `Fork failed: the forked run threw (${e instanceof Error ? e.message : String(e)}).${wt ? `\n${wt}` : ""}`, details: { error: "fork run failed" } };
|
|
2529
2711
|
}
|
|
2712
|
+
finally {
|
|
2713
|
+
syncForkAdmission.release();
|
|
2714
|
+
}
|
|
2530
2715
|
const forkHandbackWarning = await reviewHandback({
|
|
2531
2716
|
review: ctx.autoModeReview,
|
|
2532
2717
|
toolCallId: ctx.toolCallId,
|
|
@@ -2601,51 +2786,15 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2601
2786
|
const shortDesc = reviveRow?.description ?? String(a.description ?? "sub-agent").slice(0, 200);
|
|
2602
2787
|
const bgOwner = reviveRow !== undefined ? reviveRow.owner : sessionScopedBg ? ctx.sessionId : ctx.taskId ?? bg.owner;
|
|
2603
2788
|
const bgScope = treeScope;
|
|
2604
|
-
const
|
|
2605
|
-
const
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
const capLedgerKey = capScope !== undefined && capRoot !== undefined ? JSON.stringify([capScope, capRoot]) : undefined;
|
|
2610
|
-
if (capScope !== undefined && capRoot !== undefined) {
|
|
2611
|
-
let storedHandles = [];
|
|
2612
|
-
let storedEnumerationOk = false;
|
|
2613
|
-
if (bg.agentStore !== undefined && reviveRow === undefined) {
|
|
2614
|
-
try {
|
|
2615
|
-
storedHandles = (await bg.agentStore.listBySession(capScope, capRoot)).map((r) => r.handle);
|
|
2616
|
-
storedEnumerationOk = true;
|
|
2617
|
-
}
|
|
2618
|
-
catch {
|
|
2619
|
-
storedHandles = [];
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
const capRefusal = async (code, text) => {
|
|
2789
|
+
const bgEntryGate = await openEntryGate();
|
|
2790
|
+
const capLedgerKey = bgEntryGate.key;
|
|
2791
|
+
{
|
|
2792
|
+
const admission = bgEntryGate.admit("registered");
|
|
2793
|
+
if (!admission.admitted) {
|
|
2623
2794
|
dropHostAbortListener();
|
|
2624
2795
|
await cancelObserver();
|
|
2625
2796
|
const wt = await finishWorktree();
|
|
2626
|
-
return errorResult(`Sub-agent not started in background: ${text}${wt ? `\n${wt}` : ""}`, { error: code, code });
|
|
2627
|
-
};
|
|
2628
|
-
const activeFace = bg.registry.activeDelegationHandles;
|
|
2629
|
-
const active = typeof activeFace === "function" ? activeFace.call(bg.registry, capScope, capRoot) : [];
|
|
2630
|
-
if (typeof activeFace === "function" && active.length >= entryCaps.maxConcurrent) {
|
|
2631
|
-
return await capRefusal("delegation.concurrency_cap", `this session tree already has ${active.length} background agents running — the concurrency cap (${entryCaps.maxConcurrent}; RunnerDeps.delegationEntryCaps.maxConcurrent) refuses another. Wait for one to complete (you will be notified) or stop one, then relaunch.`);
|
|
2632
|
-
}
|
|
2633
|
-
if (reviveRow === undefined) {
|
|
2634
|
-
const ledger = delegationEntryLedger(bg.registry, capLedgerKey);
|
|
2635
|
-
let cumulative;
|
|
2636
|
-
if (bg.agentStore !== undefined && storedEnumerationOk) {
|
|
2637
|
-
const keep = new Set([...storedHandles, ...active]);
|
|
2638
|
-
for (const h of [...ledger])
|
|
2639
|
-
if (!keep.has(h))
|
|
2640
|
-
ledger.delete(h);
|
|
2641
|
-
cumulative = new Set([...storedHandles, ...ledger]).size;
|
|
2642
|
-
}
|
|
2643
|
-
else {
|
|
2644
|
-
cumulative = ledger.size;
|
|
2645
|
-
}
|
|
2646
|
-
if (cumulative >= entryCaps.maxCumulativePerSession) {
|
|
2647
|
-
return await capRefusal("delegation.session_cap", `this session tree has already launched ${cumulative} background agents in its retained window — the cumulative cap (${entryCaps.maxCumulativePerSession}; RunnerDeps.delegationEntryCaps.maxCumulativePerSession) refuses more. Continue an existing agent (SendMessage) instead of launching new ones.`);
|
|
2648
|
-
}
|
|
2797
|
+
return errorResult(`Sub-agent not started in background: ${admission.refusal.text}${wt ? `\n${wt}` : ""}`, { error: admission.refusal.code, code: admission.refusal.code });
|
|
2649
2798
|
}
|
|
2650
2799
|
}
|
|
2651
2800
|
const settlementSeat = ctx.delegationSettlement?.();
|
|
@@ -2770,13 +2919,8 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2770
2919
|
}
|
|
2771
2920
|
return { isError: true, content: `Sub-agent not started in background: ${e instanceof Error ? e.message : String(e)}${wt ? `\n${wt}` : ""}`, details: { error: "register_failed" } };
|
|
2772
2921
|
}
|
|
2773
|
-
if (capLedgerKey !== undefined
|
|
2774
|
-
|
|
2775
|
-
if (capLedgerKey !== undefined) {
|
|
2776
|
-
bindDelegationLedgerKeyStore(bg.registry, capLedgerKey, bg.agentStore);
|
|
2777
|
-
armDelegationLedgerLifecycle(bg.registry);
|
|
2778
|
-
sweepDelegationLedgerRound(bg.registry, 3);
|
|
2779
|
-
}
|
|
2922
|
+
if (capLedgerKey !== undefined)
|
|
2923
|
+
chargeDelegationEntryHandle(bg.registry, capLedgerKey, bg.agentStore, reviveRow === undefined ? taskId : undefined);
|
|
2780
2924
|
childInternals.peerSelfRef?.addAxis("h", taskId);
|
|
2781
2925
|
if (agentName !== undefined) {
|
|
2782
2926
|
recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...(reviveRow !== undefined ? ((reviveRow.rootSessionId ?? reviveRow.parentSessionId) !== undefined ? { rootSessionId: reviveRow.rootSessionId ?? reviveRow.parentSessionId } : {}) : (ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: reviveRow?.spawnedAt ?? Date.now() }, (err) => opts.onObserverError?.(err, { site: "roster.recordSpawn" }));
|
|
@@ -3411,49 +3555,66 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
3411
3555
|
};
|
|
3412
3556
|
}
|
|
3413
3557
|
let child;
|
|
3558
|
+
const syncEntryGate = await openEntryGate();
|
|
3559
|
+
if (ctx.signal?.aborted) {
|
|
3560
|
+
await cancelObserver();
|
|
3561
|
+
const wt = await finishWorktree();
|
|
3562
|
+
return spawnAbortedResult(wt);
|
|
3563
|
+
}
|
|
3564
|
+
const syncAdmission = syncEntryGate.admit("local");
|
|
3565
|
+
if (!syncAdmission.admitted) {
|
|
3566
|
+
await cancelObserver();
|
|
3567
|
+
const wt = await finishWorktree();
|
|
3568
|
+
return errorResult(`Sub-agent not started: ${syncAdmission.refusal.text}${wt ? `\n${wt}` : ""}`, { error: syncAdmission.refusal.code, code: syncAdmission.refusal.code });
|
|
3569
|
+
}
|
|
3414
3570
|
const syncStartedAt = Date.now();
|
|
3415
3571
|
let retainEntry;
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3572
|
+
try {
|
|
3573
|
+
if (ctx.onSubagentSpawn || observerPairing) {
|
|
3574
|
+
retainEntry = ctx.onSubagentSpawn ? await tryRetainChild(ctx.subagentRetain) : undefined;
|
|
3575
|
+
if (retainEntry)
|
|
3576
|
+
stepRecorder.lockTo(retainEntry.childSessionId);
|
|
3577
|
+
const stream = opts.runner.runTaskStream({ ...buildChildSpec(ctx.signal), ...(retainEntry ? { sessionId: retainEntry.childSessionId } : {}) }, undefined, childInternals);
|
|
3578
|
+
observedSteerRef.steer = (text, o) => stream.steer(text, o);
|
|
3579
|
+
let settleResolve;
|
|
3580
|
+
const settled = new Promise((r) => { settleResolve = r; });
|
|
3581
|
+
if (ctx.onSubagentSpawn) {
|
|
3582
|
+
const resume = createSubagentResume({
|
|
3583
|
+
ledger: ctx.subagentRetain,
|
|
3584
|
+
parentToolCallId: ctx.toolCallId,
|
|
3585
|
+
runner: opts.runner,
|
|
3586
|
+
...(opts.background?.notify ? { notify: opts.background.notify } : {}),
|
|
3587
|
+
sink: ctx.onSubagentSpawn,
|
|
3588
|
+
...(opts.background ? { registry: opts.background.registry } : {}),
|
|
3589
|
+
...(opts.onObserverError !== undefined ? { onNotifyError: (f) => opts.onObserverError?.(f.error, { site: f.site }) } : {}),
|
|
3590
|
+
...(ctx.onQuestion !== undefined ? { currentOnQuestion: ctx.onQuestion } : {}),
|
|
3591
|
+
...(ctx.autoModeReview !== undefined ? { currentAutoModeReview: ctx.autoModeReview } : {}),
|
|
3592
|
+
});
|
|
3593
|
+
notifier.notify(() => ctx.onSubagentSpawn?.(createSteerHandle(stream, ctx.toolCallId, childAgentName, settled, {
|
|
3594
|
+
resume,
|
|
3595
|
+
...(retainEntry ? { childSessionId: retainEntry.childSessionId } : {}),
|
|
3596
|
+
})), "subagent.onSubagentSpawn");
|
|
3597
|
+
}
|
|
3598
|
+
try {
|
|
3599
|
+
for await (const ev of stream)
|
|
3600
|
+
observerTap?.record(ev);
|
|
3601
|
+
child = await stream.result();
|
|
3602
|
+
}
|
|
3603
|
+
catch (e) {
|
|
3604
|
+
await settleObserver("failed");
|
|
3605
|
+
throw e;
|
|
3606
|
+
}
|
|
3607
|
+
finally {
|
|
3608
|
+
settleResolve();
|
|
3609
|
+
ctx.subagentRetain?.markSettled(ctx.toolCallId);
|
|
3610
|
+
}
|
|
3449
3611
|
}
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
ctx.subagentRetain?.markSettled(ctx.toolCallId);
|
|
3612
|
+
else {
|
|
3613
|
+
child = await opts.runner.runTask(buildChildSpec(ctx.signal), childInternals);
|
|
3453
3614
|
}
|
|
3454
3615
|
}
|
|
3455
|
-
|
|
3456
|
-
|
|
3616
|
+
finally {
|
|
3617
|
+
syncAdmission.release();
|
|
3457
3618
|
}
|
|
3458
3619
|
await settleObserver(child.status);
|
|
3459
3620
|
const handbackWarning = await reviewHandback({
|
package/dist/agents/team.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Runner } from "../core/runner/runtask.js";
|
|
2
|
-
import type { McpServerSpec, ModelRef, ModelRole, TaskResult, ToolSpec } from "../core/types.js";
|
|
2
|
+
import type { McpServerSpec, ModelRef, ModelRole, TaskResult, TaskSpec, ToolSpec } from "../core/types.js";
|
|
3
3
|
export interface TeamMember {
|
|
4
4
|
/** The member's role / specialty, e.g. "安全评审" or "performance". */
|
|
5
5
|
role: string;
|
|
@@ -99,6 +99,15 @@ export interface TeamDiscussionOptions {
|
|
|
99
99
|
denyTools?: string[];
|
|
100
100
|
mcp?: McpServerSpec[];
|
|
101
101
|
};
|
|
102
|
+
/**
|
|
103
|
+
* Per-model auth — MIRRORS {@link TaskSpec.getApiKeyAndHeaders} (forwarded into every member /
|
|
104
|
+
* summary / synthesizer run, which resolve it against their own resolved models). Without this
|
|
105
|
+
* seat a team whose member/synthesizer models live on per-model-credential routes had NO way to
|
|
106
|
+
* carry the resolver: every nested run fell to the brain's construction-time credential, which the
|
|
107
|
+
* key↔URL pairing gate refuses off the deployment root — the teacher/verify delegation family
|
|
108
|
+
* carries the same seat for the same reason.
|
|
109
|
+
*/
|
|
110
|
+
getApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"];
|
|
102
111
|
/** Progress callback. */
|
|
103
112
|
onEvent?: (e: TeamEvent) => void;
|
|
104
113
|
/**
|
package/dist/agents/team.js
CHANGED
|
@@ -117,6 +117,7 @@ export async function runTeamDiscussion(opts) {
|
|
|
117
117
|
limits: memberLimits,
|
|
118
118
|
signal: opts.signal,
|
|
119
119
|
...(opts.principal !== undefined ? { principal: opts.principal } : {}),
|
|
120
|
+
...(opts.getApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: opts.getApiKeyAndHeaders } : {}),
|
|
120
121
|
}, { isDelegatedChild: true });
|
|
121
122
|
tokens += res.stats.tokens + (res.stats.nested?.tokens ?? 0);
|
|
122
123
|
turns += res.stats.turns + (res.stats.nested?.turns ?? 0);
|
package/dist/brain/anthropic.js
CHANGED
|
@@ -5,7 +5,8 @@ import { createRepetitionPoll, parseStreamedToolArgs } from "./stream-shared.js"
|
|
|
5
5
|
import { mintFallbackToolCallId } from "./tool-call-id.js";
|
|
6
6
|
import { emitBrainTelemetry, reportReasoningWireFacts } from "./status-sink.js";
|
|
7
7
|
import { errorResultMediaNote, IMAGE_OMITTED_NO_VISION, modelSupportsVision, sendableImages } from "./media-degrade.js";
|
|
8
|
-
import { ANTHROPIC_RESERVED, OUTPUT_CAP_KEYS, applyExtraBody, effectiveOutputCap, lockHeader, mergeHeaders,
|
|
8
|
+
import { ANTHROPIC_RESERVED, OUTPUT_CAP_KEYS, applyExtraBody, effectiveOutputCap, lockHeader, mergeHeaders, takeHeaderCasefold } from "./request-params.js";
|
|
9
|
+
import { adjudicateModelRoute, applyRouteCredentialHeaders, createBrainRouteJudge, resolveRouteCredential, routeRefusalText } from "./route-adjudicator.js";
|
|
9
10
|
import { MIN_THINKING_TOKENS, budgetCapSkipsThinking, declaredEffortLevels, reasoningBudgetShare, reasoningRequestCarried, resolveEffort } from "./reasoning.js";
|
|
10
11
|
import { runStreamingBrain } from "./stream-engine.js";
|
|
11
12
|
function thinkingBudget(maxTokens, share, fixed, hardCap = false) {
|
|
@@ -234,7 +235,17 @@ export function createAnthropicBrain(config = {}) {
|
|
|
234
235
|
httpLabel: "anthropic",
|
|
235
236
|
stallTimeouts: options?.stallTimeouts,
|
|
236
237
|
buildRequest: (overrides) => {
|
|
237
|
-
const
|
|
238
|
+
const routeCredential = resolveRouteCredential({
|
|
239
|
+
optionsApiKey: options?.apiKey,
|
|
240
|
+
optionsHeaders: options?.headers,
|
|
241
|
+
modelHeaders: model.headers,
|
|
242
|
+
configApiKey: config.apiKey,
|
|
243
|
+
configHeaders: config.headers,
|
|
244
|
+
});
|
|
245
|
+
const routeVerdict = adjudicateModelRoute(model, routeCredential, { baseUrl: config.baseUrl });
|
|
246
|
+
if (!routeVerdict.ok)
|
|
247
|
+
throw new BrainError("invalid_request", routeRefusalText(routeVerdict));
|
|
248
|
+
const apiKey = routeCredential.apiKey;
|
|
238
249
|
const root = (model.baseUrl || config.baseUrl || "https://api.anthropic.com").replace(/\/+$/, "");
|
|
239
250
|
const nonEmptyBlocks = context.systemBlocks?.filter((b) => b.text.length > 0);
|
|
240
251
|
const system = nonEmptyBlocks?.length
|
|
@@ -302,8 +313,7 @@ export function createAnthropicBrain(config = {}) {
|
|
|
302
313
|
betas.push("interleaved-thinking-2025-05-14");
|
|
303
314
|
}
|
|
304
315
|
const headers = mergeHeaders(model.headers, config.headers, options?.headers);
|
|
305
|
-
|
|
306
|
-
stripAuthHeaders(headers);
|
|
316
|
+
applyRouteCredentialHeaders(headers, routeCredential, { model: model.headers, options: options?.headers });
|
|
307
317
|
if (betas.length > 0) {
|
|
308
318
|
const existing = (takeHeaderCasefold(headers, "anthropic-beta") ?? "").split(",").map((b) => b.trim()).filter(Boolean);
|
|
309
319
|
for (const beta of betas) {
|
|
@@ -680,5 +690,5 @@ export function createAnthropicBrain(config = {}) {
|
|
|
680
690
|
},
|
|
681
691
|
});
|
|
682
692
|
};
|
|
683
|
-
return { stream };
|
|
693
|
+
return { stream, adjudicateRoute: createBrainRouteJudge(config) };
|
|
684
694
|
}
|
|
@@ -153,5 +153,6 @@ export function createCircuitBreakerBrain(inner, opts = {}) {
|
|
|
153
153
|
})();
|
|
154
154
|
return out;
|
|
155
155
|
};
|
|
156
|
-
|
|
156
|
+
const adjudicateRoute = (model, perModelAuth) => inner.adjudicateRoute?.(model, perModelAuth);
|
|
157
|
+
return { stream, adjudicateRoute };
|
|
157
158
|
}
|
package/dist/brain/degrading.js
CHANGED
|
@@ -144,6 +144,8 @@ export function createDegradingBrain(opts) {
|
|
|
144
144
|
break;
|
|
145
145
|
if (hop.model.id === failedModelId)
|
|
146
146
|
continue;
|
|
147
|
+
if (hop.brain.adjudicateRoute?.(hop.model)?.ok === false)
|
|
148
|
+
continue;
|
|
147
149
|
chainPath.push(hop.model.id);
|
|
148
150
|
degradeInfo = {
|
|
149
151
|
from: model.id,
|
|
@@ -182,5 +184,6 @@ export function createDegradingBrain(opts) {
|
|
|
182
184
|
})();
|
|
183
185
|
return out;
|
|
184
186
|
};
|
|
185
|
-
|
|
187
|
+
const adjudicateRoute = (model, perModelAuth) => opts.primary.adjudicateRoute?.(model, perModelAuth);
|
|
188
|
+
return { stream, adjudicateRoute };
|
|
186
189
|
}
|
package/dist/brain/failover.js
CHANGED
|
@@ -82,5 +82,20 @@ export function createFailoverBrain(brains) {
|
|
|
82
82
|
})();
|
|
83
83
|
return out;
|
|
84
84
|
};
|
|
85
|
-
|
|
85
|
+
const adjudicateRoute = (model, perModelAuth) => {
|
|
86
|
+
let firstRefusal;
|
|
87
|
+
let sawUnjudged = false;
|
|
88
|
+
for (const b of brains) {
|
|
89
|
+
const verdict = b.adjudicateRoute?.(model, perModelAuth);
|
|
90
|
+
if (verdict === undefined) {
|
|
91
|
+
sawUnjudged = true;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (verdict.ok)
|
|
95
|
+
return verdict;
|
|
96
|
+
firstRefusal ??= verdict;
|
|
97
|
+
}
|
|
98
|
+
return sawUnjudged ? undefined : firstRefusal;
|
|
99
|
+
};
|
|
100
|
+
return { stream, adjudicateRoute };
|
|
86
101
|
}
|
|
@@ -5,7 +5,8 @@ import { createRepetitionPoll, parseStreamedToolArgs } from "./stream-shared.js"
|
|
|
5
5
|
import { mintFallbackToolCallId } from "./tool-call-id.js";
|
|
6
6
|
import { emitBrainTelemetry } from "./status-sink.js";
|
|
7
7
|
import { errorResultMediaNote, IMAGE_OMITTED_NO_VISION, imagesOmittedNoVisionNote, modelSupportsVision, sendableImages } from "./media-degrade.js";
|
|
8
|
-
import { OUTPUT_CAP_KEYS, RESPONSES_RESERVED, applyExtraBody, effectiveOutputCap, lockHeader, mergeHeaders
|
|
8
|
+
import { OUTPUT_CAP_KEYS, RESPONSES_RESERVED, applyExtraBody, effectiveOutputCap, lockHeader, mergeHeaders } from "./request-params.js";
|
|
9
|
+
import { adjudicateModelRoute, applyRouteCredentialHeaders, createBrainRouteJudge, resolveRouteCredential, routeRefusalText } from "./route-adjudicator.js";
|
|
9
10
|
import { mintEffortWireValue, reasoningRequestCarried } from "./reasoning.js";
|
|
10
11
|
import { runStreamingBrain } from "./stream-engine.js";
|
|
11
12
|
const DEGENERATE_POLL_CHARS = 64;
|
|
@@ -228,7 +229,17 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
228
229
|
httpLabel: "responses",
|
|
229
230
|
stallTimeouts: options?.stallTimeouts,
|
|
230
231
|
buildRequest: (overrides) => {
|
|
231
|
-
const
|
|
232
|
+
const routeCredential = resolveRouteCredential({
|
|
233
|
+
optionsApiKey: options?.apiKey,
|
|
234
|
+
optionsHeaders: options?.headers,
|
|
235
|
+
modelHeaders: model.headers,
|
|
236
|
+
configApiKey: config.apiKey,
|
|
237
|
+
configHeaders: config.headers,
|
|
238
|
+
});
|
|
239
|
+
const routeVerdict = adjudicateModelRoute(model, routeCredential, { baseUrl: config.baseUrl });
|
|
240
|
+
if (!routeVerdict.ok)
|
|
241
|
+
throw new BrainError("invalid_request", routeRefusalText(routeVerdict));
|
|
242
|
+
const apiKey = routeCredential.apiKey;
|
|
232
243
|
const root = (model.baseUrl || config.baseUrl || "").replace(/\/+$/, "");
|
|
233
244
|
if (!root) {
|
|
234
245
|
throw new Error("createOpenResponsesBrain: no baseUrl configured (set config.baseUrl or model.baseUrl)");
|
|
@@ -265,8 +276,7 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
265
276
|
if (effort !== undefined)
|
|
266
277
|
body.reasoning = { effort };
|
|
267
278
|
const headers = mergeHeaders(model.headers, config.headers, options?.headers);
|
|
268
|
-
|
|
269
|
-
stripAuthHeaders(headers);
|
|
279
|
+
applyRouteCredentialHeaders(headers, routeCredential, { model: model.headers, options: options?.headers });
|
|
270
280
|
lockHeader(headers, "content-type", "application/json");
|
|
271
281
|
if (apiKey)
|
|
272
282
|
headers["authorization"] = `Bearer ${apiKey}`;
|
|
@@ -738,5 +748,5 @@ export function createOpenResponsesBrain(config = {}) {
|
|
|
738
748
|
},
|
|
739
749
|
});
|
|
740
750
|
};
|
|
741
|
-
return { stream };
|
|
751
|
+
return { stream, adjudicateRoute: createBrainRouteJudge(config) };
|
|
742
752
|
}
|