dsh-llm-fallbacks 0.4.4 → 0.5.1

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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import z from "@deepseek-ai/schemastery";
3
- import { LlmAdapter, LlmError, createUserMessage } from "@deepseek-ai/dsh-llm";
3
+ import { LlmAdapter, LlmError, boundContextSummary, createUserMessage } from "@deepseek-ai/dsh-llm";
4
4
  import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
5
5
  //#region .build/host/selectors.js
6
6
  var SelectorError = class extends Error {
@@ -668,11 +668,15 @@ function annotateCandidates(candidates, surviving, options) {
668
668
  }
669
669
  //#endregion
670
670
  //#region .build/host/roles.js
671
- function resolveRole(agent, rules, roleIds, warn = console.warn) {
671
+ function resolveRole(agent, rules, roleIds, warn = console.warn, servedRoute) {
672
672
  if ((agent.session?.header?.origin ?? "root") !== "subagent") return INHERIT_ROLE_ID;
673
+ const recordedRoute = {
674
+ provider: agent.options?.provider,
675
+ model: agent.options?.model
676
+ };
673
677
  for (const rule of rules) {
674
- if (rule.provider && rule.provider !== agent.options?.provider) continue;
675
- if (rule.model && rule.model !== agent.options?.model) continue;
678
+ const matchesPair = (pair) => (!rule.provider || rule.provider === pair.provider) && (!rule.model || rule.model === pair.model);
679
+ if (!matchesPair(recordedRoute) && !(servedRoute !== void 0 && matchesPair(servedRoute))) continue;
676
680
  const target = rule.role.trim();
677
681
  if (target === "inherit") return INHERIT_ROLE_ID;
678
682
  const declared = roleIds.get(target);
@@ -692,7 +696,7 @@ async function resolveRoleAtDispatch(agent, rules, roleIds, opts) {
692
696
  const declared = roleIds.get(preset);
693
697
  if (declared !== void 0) return declared;
694
698
  }
695
- const role = resolveRole(agent, rules, roleIds, opts.warn);
699
+ const role = resolveRole(agent, rules, roleIds, opts.warn, opts.servedRoute);
696
700
  if (role !== "inherit") return role;
697
701
  if (opts.automatchEnabled && opts.automatch !== void 0) {
698
702
  const picked = await opts.automatch(agent);
@@ -800,6 +804,549 @@ function effectivePolicy(event, settings) {
800
804
  };
801
805
  }
802
806
  //#endregion
807
+ //#region .build/host/role-notice.js
808
+ const ROLE_NOTICE_PLUGIN = "dsh-llm-fallbacks";
809
+ const ROLE_NOTICE_SOURCE_KIND = "plugin";
810
+ const ROLE_NOTICE_PERSONA_SKIPPED_SUFFIX = " (persona not applied)";
811
+ function markNoticeEmitted(emitted, childSessionId) {
812
+ if (!emitted.has(childSessionId) && emitted.size >= 256) {
813
+ const oldest = emitted.values().next().value;
814
+ if (oldest !== void 0) emitted.delete(oldest);
815
+ }
816
+ emitted.add(childSessionId);
817
+ }
818
+ function buildRoleNotice(role, personaNotApplied) {
819
+ return createUserMessage({
820
+ content: [{
821
+ type: "text",
822
+ text: personaNotApplied ? `[role: ${role}]${ROLE_NOTICE_PERSONA_SKIPPED_SUFFIX}` : `[role: ${role}]`
823
+ }],
824
+ source: {
825
+ kind: ROLE_NOTICE_SOURCE_KIND,
826
+ plugin: ROLE_NOTICE_PLUGIN,
827
+ form: "notice",
828
+ summary: boundContextSummary(`role: ${role}`)
829
+ }
830
+ });
831
+ }
832
+ function errorMessage$2(error) {
833
+ return error instanceof Error ? error.message : String(error);
834
+ }
835
+ function installRoleNotice(ctx, options) {
836
+ const buildNotice = options.buildNotice ?? buildRoleNotice;
837
+ const debug = (message) => {
838
+ try {
839
+ options.debug?.(message);
840
+ } catch {}
841
+ };
842
+ return ctx.on("agent/pre-step", async ({ agent, signal }, next) => {
843
+ const decision = await next();
844
+ try {
845
+ if (decision.kind === "reject" || signal.aborted) return decision;
846
+ if (decision.messages.length === 0) return decision;
847
+ if (agent.session?.header?.origin !== "subagent") return decision;
848
+ if (options.emitted.has(agent.id)) return decision;
849
+ const record = options.records.get(agent.id);
850
+ if (record === void 0 || !record.firstNoticePending) return decision;
851
+ const notice = buildNotice(record.role, record.personaNotApplied === true);
852
+ markNoticeEmitted(options.emitted, agent.id);
853
+ record.firstNoticePending = false;
854
+ return {
855
+ ...decision,
856
+ messages: [...decision.messages, notice]
857
+ };
858
+ } catch (error) {
859
+ debug(`llm-fallbacks: role notice for agent "${agent.id}" skipped (contained, the step is unchanged): ${errorMessage$2(error)}`);
860
+ return decision;
861
+ }
862
+ }, { prepend: true });
863
+ }
864
+ //#endregion
865
+ //#region .build/host/role-projection-key.js
866
+ const ROLE_PROJECTION_KEY = "fallbacksSubagentRole";
867
+ const ROLE_STATE_SCHEMA = { parse(value) {
868
+ if (value === null) return null;
869
+ if (typeof value === "object") {
870
+ const { role, inheritedEventCount } = value;
871
+ if (isInheritedEventCount(inheritedEventCount)) {
872
+ if (role === void 0) return { inheritedEventCount };
873
+ if (typeof role === "string" && role.trim() !== "") return {
874
+ inheritedEventCount,
875
+ role
876
+ };
877
+ }
878
+ }
879
+ throw new TypeError(`role projection: expected null or a non-blank role id with its fork boundary, got ${describeValue(value)}`);
880
+ } };
881
+ function isInheritedEventCount(value) {
882
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
883
+ }
884
+ const ROLE_WIRE_SCHEMA = { parse(value) {
885
+ if (value === null) return null;
886
+ if (typeof value === "string" && value.trim() !== "") return value;
887
+ throw new TypeError(`role projection: expected null or a non-blank role id on the wire, got ${describeValue(value)}`);
888
+ } };
889
+ function describeValue(value) {
890
+ if (value === void 0) return "undefined";
891
+ try {
892
+ return JSON.stringify(value) ?? String(value);
893
+ } catch {
894
+ return String(value);
895
+ }
896
+ }
897
+ const ROLE_NOTICE_TEXT_RE = new RegExp(`^\\[role: ([\\s\\S]+)\\](?:${ROLE_NOTICE_PERSONA_SKIPPED_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})?$`);
898
+ function roleFromNoticeText(text) {
899
+ const role = ROLE_NOTICE_TEXT_RE.exec(text)?.[1];
900
+ return role === void 0 || role.trim() === "" ? void 0 : role;
901
+ }
902
+ function roleFromContent(content) {
903
+ if (!Array.isArray(content)) return void 0;
904
+ for (const block of content) {
905
+ if (typeof block !== "object" || block === null) continue;
906
+ const { type, text } = block;
907
+ if (type !== "text" || typeof text !== "string") continue;
908
+ const role = roleFromNoticeText(text);
909
+ if (role !== void 0) return role;
910
+ }
911
+ }
912
+ function roleFromEvent(event) {
913
+ if (typeof event !== "object" || event === null) return void 0;
914
+ const { type, data } = event;
915
+ if (type !== "user/message") return void 0;
916
+ if (typeof data !== "object" || data === null) return void 0;
917
+ const { content, source } = data;
918
+ if (typeof source !== "object" || source === null) return void 0;
919
+ const { kind, plugin } = source;
920
+ if (kind !== "plugin" || plugin !== "dsh-llm-fallbacks") return void 0;
921
+ return roleFromContent(content);
922
+ }
923
+ const roleProjectionUnit = {
924
+ key: ROLE_PROJECTION_KEY,
925
+ stateSchema: ROLE_STATE_SCHEMA,
926
+ stateVersion: 2,
927
+ init: (_header, inheritedEventCount) => ({ inheritedEventCount }),
928
+ apply: (state, event) => {
929
+ if (typeof event !== "object" || event === null) return state;
930
+ const inheritedEventCount = state?.inheritedEventCount ?? 0;
931
+ const { seq } = event;
932
+ if (typeof seq === "number" && seq < inheritedEventCount) return state;
933
+ const role = roleFromEvent(event);
934
+ if (role === void 0 || role === state?.role) return state;
935
+ return {
936
+ inheritedEventCount,
937
+ role
938
+ };
939
+ },
940
+ wire: {
941
+ viewSchema: ROLE_WIRE_SCHEMA,
942
+ view: (state) => state === null ? null : state.role ?? null
943
+ }
944
+ };
945
+ function errorMessage$1(error) {
946
+ return error instanceof Error ? error.message : String(error);
947
+ }
948
+ function installRoleProjection(ctx, options = {}) {
949
+ const debug = (message) => {
950
+ try {
951
+ options.debug?.(message);
952
+ } catch {}
953
+ };
954
+ let child;
955
+ try {
956
+ child = ctx.inject(["sessionProjections"], (projectionCtx) => {
957
+ try {
958
+ const { sessionProjections: registry } = projectionCtx;
959
+ if (registry === void 0 || typeof registry.register !== "function") {
960
+ debug("llm-fallbacks: role projection not registered — the resolved sessionProjections service is not a registry (the badge shows no role on this composition)");
961
+ return () => {};
962
+ }
963
+ return registry.register(roleProjectionUnit);
964
+ } catch (error) {
965
+ debug(`llm-fallbacks: role projection not registered (contained): ${errorMessage$1(error)}`);
966
+ return () => {};
967
+ }
968
+ });
969
+ } catch (error) {
970
+ debug(`llm-fallbacks: role projection install skipped (contained): ${errorMessage$1(error)}`);
971
+ }
972
+ return () => {
973
+ try {
974
+ child?.dispose();
975
+ } catch {}
976
+ };
977
+ }
978
+ //#endregion
979
+ //#region .build/host/subagents-seam.js
980
+ const SUBAGENT_SEAM_EVENT = "internal/get";
981
+ const SUBAGENT_SEAM_SERVICE = "subagents";
982
+ const SUBAGENT_CATALOG_EVENT = "subagent/catalog";
983
+ const ASSIGNMENT_BODY_START_RE = /^(?:#{1,6}[ \t]+Task\b|-{3,}[ \t]*$|#[ \t])/m;
984
+ const EXECUTE_AS_BOLD_LINE_RE = /^[ \t]*(?:[-*][ \t]+)?\*\*\s*Execute as\s*\*\*\s*:\s*(.*)$/;
985
+ const EXECUTE_AS_PLAIN_LINE_RE = /^[ \t]*(?:[-*][ \t]+)?Execute as\s*:\s*(.*)$/;
986
+ const ASSIGNMENT_HEADER_OPEN_RE = /^(?:#{1,6}[ \t]|-{3,}[ \t]*\r?$|[ \t]*(?:[-*][ \t]+)?\*\*|[ \t]*(?:[-*][ \t]+)?\*{0,2}[A-Za-z][A-Za-z /_-]{0,29}\*{0,2}[ \t]*:[ \t]*\S)/;
987
+ function assignmentHeaderRegion(prompt) {
988
+ const marker = prompt.match(ASSIGNMENT_BODY_START_RE);
989
+ return marker !== null && marker.index !== void 0 ? prompt.slice(0, marker.index) : leadingHeaderBlock(prompt);
990
+ }
991
+ function leadingHeaderBlock(prompt) {
992
+ let end = 0;
993
+ let offset = 0;
994
+ let atParagraphStart = true;
995
+ for (const line of prompt.split("\n")) {
996
+ offset += line.length + 1;
997
+ if (line.trim() === "") {
998
+ atParagraphStart = true;
999
+ continue;
1000
+ }
1001
+ if (atParagraphStart && !ASSIGNMENT_HEADER_OPEN_RE.test(line.replace(/\r$/, ""))) break;
1002
+ atParagraphStart = false;
1003
+ end = offset;
1004
+ }
1005
+ return end === 0 ? "" : prompt.slice(0, end);
1006
+ }
1007
+ function declaredExecuteAsValues(prompt) {
1008
+ const values = [];
1009
+ for (const line of assignmentHeaderRegion(prompt).split(/\r?\n/)) {
1010
+ const match = line.match(EXECUTE_AS_BOLD_LINE_RE) ?? line.match(EXECUTE_AS_PLAIN_LINE_RE);
1011
+ if (match !== null && match[1] !== void 0) values.push(match[1].trim());
1012
+ }
1013
+ return values;
1014
+ }
1015
+ function canonicalizeRoleReference(raw) {
1016
+ const trimmed = raw.trim();
1017
+ const id = (trimmed.startsWith("@") ? trimmed.slice(1) : trimmed).trim();
1018
+ if (id === "" || id === "inherit") return void 0;
1019
+ return id;
1020
+ }
1021
+ function lookupDeclaredRole(id, roleIds) {
1022
+ const direct = roleIds.get(id);
1023
+ if (direct !== void 0) return direct;
1024
+ const lower = id.toLowerCase();
1025
+ for (const [trimmedId, declared] of roleIds) if (trimmedId.toLowerCase() === lower) return declared;
1026
+ }
1027
+ function resolveDeclaredRoleFromAssignment(prompt, roleIds) {
1028
+ const declared = new Set();
1029
+ for (const raw of declaredExecuteAsValues(prompt)) {
1030
+ const id = canonicalizeRoleReference(raw);
1031
+ if (id === void 0) continue;
1032
+ const resolved = lookupDeclaredRole(id, roleIds);
1033
+ if (resolved !== void 0) declared.add(resolved);
1034
+ }
1035
+ if (declared.size !== 1) return void 0;
1036
+ return declared.values().next().value;
1037
+ }
1038
+ function promptTextOf(blocks) {
1039
+ if (!Array.isArray(blocks)) return "";
1040
+ return blocks.filter((block) => block?.type === "text").map((block) => block.text ?? "").join("\n");
1041
+ }
1042
+ function personaForRole(roles, roleId) {
1043
+ for (const role of roles) {
1044
+ if (role.id !== roleId) continue;
1045
+ const persona = role.persona?.trim() ?? "";
1046
+ return persona === "" ? void 0 : persona;
1047
+ }
1048
+ }
1049
+ function childSessionIdOf(result) {
1050
+ if (typeof result !== "object" || result === null) return void 0;
1051
+ const value = result;
1052
+ for (const key of [
1053
+ "subagentId",
1054
+ "runId",
1055
+ "childId",
1056
+ "id"
1057
+ ]) {
1058
+ const candidate = value[key];
1059
+ if (typeof candidate === "string" && candidate !== "") return candidate;
1060
+ }
1061
+ }
1062
+ function parentSessionIdOf(request) {
1063
+ const id = request?.parent?.session?.id;
1064
+ return typeof id === "string" && id !== "" ? id : void 0;
1065
+ }
1066
+ function correlationKey(parentSessionId, label) {
1067
+ return `${parentSessionId}\u0000${label}`;
1068
+ }
1069
+ function errorMessage(error) {
1070
+ return error instanceof Error ? error.message : String(error);
1071
+ }
1072
+ const subagentSeamStores = new WeakMap();
1073
+ const subagentSeamStates = new WeakMap();
1074
+ function seamStateFor(root) {
1075
+ const existing = subagentSeamStates.get(root);
1076
+ if (existing !== void 0) return existing;
1077
+ const created = {
1078
+ records: new Map(),
1079
+ noticeEmitted: new Set()
1080
+ };
1081
+ subagentSeamStates.set(root, created);
1082
+ return created;
1083
+ }
1084
+ const subagentSeamRoots = new WeakSet();
1085
+ function installSubagentSeam(ctx, options) {
1086
+ const root = ctx.root;
1087
+ const state = seamStateFor(root);
1088
+ const records = state.records;
1089
+ const noticeEmitted = state.noticeEmitted;
1090
+ const ownsWrapper = !subagentSeamRoots.has(root);
1091
+ if (ownsWrapper) subagentSeamRoots.add(root);
1092
+ const pending = new Map();
1093
+ const wrappers = new WeakMap();
1094
+ const disposers = [];
1095
+ let unreadableServiceLogged = false;
1096
+ let noRoleLogged = false;
1097
+ const debug = (message) => {
1098
+ try {
1099
+ options.debug?.(message);
1100
+ } catch {}
1101
+ };
1102
+ const reportDegrade = (error) => {
1103
+ debug(`llm-fallbacks: subagent role seam degraded to the native path (start unaffected): ${errorMessage(error)}`);
1104
+ };
1105
+ const recordChild = (childSessionId, role, at, personaNotApplied) => {
1106
+ const existing = records.get(childSessionId);
1107
+ records.set(childSessionId, {
1108
+ role,
1109
+ at,
1110
+ firstNoticePending: existing?.firstNoticePending ?? true,
1111
+ ...personaNotApplied || existing?.personaNotApplied === true ? { personaNotApplied: true } : {}
1112
+ });
1113
+ };
1114
+ const beginCorrelation = (request, role, personaNotApplied) => {
1115
+ const parentSessionId = parentSessionIdOf(request);
1116
+ const label = typeof request?.label === "string" ? request.label : void 0;
1117
+ if (parentSessionId === void 0 || label === void 0 || label === "") return void 0;
1118
+ const key = correlationKey(parentSessionId, label);
1119
+ if (!pending.has(key) && pending.size >= 64) {
1120
+ const oldest = pending.keys().next().value;
1121
+ if (oldest !== void 0) pending.delete(oldest);
1122
+ }
1123
+ pending.delete(key);
1124
+ pending.set(key, {
1125
+ role,
1126
+ at: Date.now(),
1127
+ personaNotApplied
1128
+ });
1129
+ return key;
1130
+ };
1131
+ const endCorrelation = (key) => {
1132
+ if (key !== void 0) pending.delete(key);
1133
+ };
1134
+ const resolveRoleAtSeam = (request) => {
1135
+ try {
1136
+ const prompt = promptTextOf(request?.prompt);
1137
+ if (prompt.trim() === "") return void 0;
1138
+ const role = resolveDeclaredRoleFromAssignment(prompt, options.roleIds());
1139
+ if (role === void 0 && !noRoleLogged) {
1140
+ noRoleLogged = true;
1141
+ debug(`llm-fallbacks: subagent role seam — no role resolved from the Assignment prompt (absent or undeclared '**Execute as**: <id>'); the dispatch is unchanged`);
1142
+ }
1143
+ return role;
1144
+ } catch (error) {
1145
+ reportDegrade(error);
1146
+ return;
1147
+ }
1148
+ };
1149
+ const observeStartResult = (result, onChildId, onNoChildId, onRejected) => {
1150
+ const onValue = (value) => {
1151
+ try {
1152
+ const childSessionId = childSessionIdOf(value);
1153
+ if (childSessionId === void 0) onNoChildId();
1154
+ else onChildId(childSessionId);
1155
+ } catch (error) {
1156
+ reportDegrade(error);
1157
+ }
1158
+ };
1159
+ if (typeof result === "object" && result !== null && typeof result.then === "function") {
1160
+ result.then(onValue, () => {
1161
+ try {
1162
+ onRejected();
1163
+ } catch (error) {
1164
+ reportDegrade(error);
1165
+ }
1166
+ });
1167
+ return;
1168
+ }
1169
+ onValue(result);
1170
+ };
1171
+ const personaGate = (service, providerName, surface) => {
1172
+ if (typeof service.getProvider !== "function") return "unavailable";
1173
+ const provider = service.getProvider(providerName);
1174
+ if (provider === void 0) return "unknown";
1175
+ if (surface === "one-shot") return provider.capabilities?.persona === true ? "ok" : "unsupported";
1176
+ return typeof provider.prepareContinuable === "function" ? "ok" : "unsupported";
1177
+ };
1178
+ const withRolePersona = (service, providerName, request, role, surface) => {
1179
+ if (request.persona !== void 0) return {
1180
+ request,
1181
+ personaNotApplied: false
1182
+ };
1183
+ const readRoles = options.roles;
1184
+ if (readRoles === void 0) return {
1185
+ request,
1186
+ personaNotApplied: false
1187
+ };
1188
+ const persona = personaForRole(readRoles(), role);
1189
+ if (persona === void 0) return {
1190
+ request,
1191
+ personaNotApplied: false
1192
+ };
1193
+ const gate = personaGate(service, providerName, surface);
1194
+ if (gate === "unknown") {
1195
+ debug(`llm-fallbacks: no subagent provider '${providerName}' is registered on the ${surface} start — role persona for '${role}' not delivered (the native start fails loud its own way)`);
1196
+ return {
1197
+ request,
1198
+ personaNotApplied: true
1199
+ };
1200
+ }
1201
+ if (gate === "unavailable") {
1202
+ debug(`llm-fallbacks: role persona for '${role}' skipped on the ${surface} start — the '${SUBAGENT_SEAM_SERVICE}' runtime exposes no provider lookup, so the persona capability cannot be verified (the start proceeds unchanged)`);
1203
+ return {
1204
+ request,
1205
+ personaNotApplied: true
1206
+ };
1207
+ }
1208
+ if (gate === "unsupported") {
1209
+ debug(surface === "one-shot" ? `llm-fallbacks: subagent provider '${providerName}' lacks the persona capability on the ${surface} start — role persona for '${role}' skipped (the start proceeds unchanged)` : `llm-fallbacks: subagent provider '${providerName}' does not support continuable children on the ${surface} surface — role persona for '${role}' skipped (the native continuable start fails loud its own way)`);
1210
+ return {
1211
+ request,
1212
+ personaNotApplied: true
1213
+ };
1214
+ }
1215
+ debug(`llm-fallbacks: role persona delivered via the native subagent persona channel for role '${role}' (${surface} start on provider '${providerName}')`);
1216
+ return {
1217
+ request: {
1218
+ ...request,
1219
+ persona
1220
+ },
1221
+ personaNotApplied: false
1222
+ };
1223
+ };
1224
+ const mergePersonaAtSeam = (service, providerName, request, role, surface) => {
1225
+ try {
1226
+ return withRolePersona(service, providerName, request, role, surface);
1227
+ } catch (error) {
1228
+ reportDegrade(error);
1229
+ return {
1230
+ request,
1231
+ personaNotApplied: false
1232
+ };
1233
+ }
1234
+ };
1235
+ const wrapService = (value) => {
1236
+ if (typeof value !== "object" || value === null) {
1237
+ if (value === void 0 && !unreadableServiceLogged) {
1238
+ unreadableServiceLogged = true;
1239
+ debug(`llm-fallbacks: subagent role seam inactive — '${SUBAGENT_SEAM_SERVICE}' did not resolve a service on this read`);
1240
+ }
1241
+ return value;
1242
+ }
1243
+ const service = value;
1244
+ if (typeof service.start !== "function") {
1245
+ if (!unreadableServiceLogged) {
1246
+ unreadableServiceLogged = true;
1247
+ debug(`llm-fallbacks: subagent role seam skipped — '${SUBAGENT_SEAM_SERVICE}' is not a start-capable runtime`);
1248
+ }
1249
+ return value;
1250
+ }
1251
+ const cached = wrappers.get(value);
1252
+ if (cached !== void 0) return cached;
1253
+ const wrapper = Object.create(value);
1254
+ wrapper.start = (name, request) => {
1255
+ const role = resolveRoleAtSeam(request);
1256
+ if (role === void 0) return service.start(name, request);
1257
+ const persona = mergePersonaAtSeam(service, name, request, role, "one-shot");
1258
+ let pendingKey;
1259
+ try {
1260
+ pendingKey = beginCorrelation(request, role, persona.personaNotApplied);
1261
+ } catch (error) {
1262
+ reportDegrade(error);
1263
+ }
1264
+ let result;
1265
+ try {
1266
+ result = service.start(name, persona.request);
1267
+ } catch (error) {
1268
+ endCorrelation(pendingKey);
1269
+ throw error;
1270
+ }
1271
+ try {
1272
+ observeStartResult(result, (childSessionId) => {
1273
+ endCorrelation(pendingKey);
1274
+ recordChild(childSessionId, role, Date.now(), persona.personaNotApplied);
1275
+ }, () => {}, () => endCorrelation(pendingKey));
1276
+ } catch (error) {
1277
+ endCorrelation(pendingKey);
1278
+ reportDegrade(error);
1279
+ }
1280
+ return result;
1281
+ };
1282
+ const startContinuable = service.startContinuable;
1283
+ if (typeof startContinuable === "function") wrapper.startContinuable = (spec) => {
1284
+ const role = resolveRoleAtSeam(spec?.request) ?? (typeof spec?.childId === "string" ? records.get(spec.childId)?.role : void 0);
1285
+ if (role === void 0) return startContinuable.call(service, spec);
1286
+ const outcome = mergePersonaAtSeam(service, spec.provider, spec.request, role, "continuable");
1287
+ const effectiveSpec = outcome.request === spec.request ? spec : {
1288
+ ...spec,
1289
+ request: outcome.request
1290
+ };
1291
+ const result = startContinuable.call(service, effectiveSpec);
1292
+ try {
1293
+ observeStartResult(result, (childSessionId) => recordChild(childSessionId, role, Date.now(), outcome.personaNotApplied), () => {}, () => {});
1294
+ } catch (error) {
1295
+ reportDegrade(error);
1296
+ }
1297
+ return result;
1298
+ };
1299
+ wrappers.set(value, wrapper);
1300
+ return wrapper;
1301
+ };
1302
+ if (ownsWrapper) {
1303
+ disposers.push(ctx.on(SUBAGENT_SEAM_EVENT, (_readCtx, name, _error, next) => {
1304
+ const value = next();
1305
+ if (name !== "subagents") return value;
1306
+ try {
1307
+ return wrapService(value);
1308
+ } catch (error) {
1309
+ reportDegrade(error);
1310
+ return value;
1311
+ }
1312
+ }));
1313
+ disposers.push(ctx.on("session/event", (session, event) => {
1314
+ try {
1315
+ if (event.type !== SUBAGENT_CATALOG_EVENT) return;
1316
+ const data = event.data;
1317
+ const childSessionId = data?.childId;
1318
+ const label = data?.label;
1319
+ if (typeof childSessionId !== "string" || childSessionId === "") return;
1320
+ if (typeof label !== "string" || label === "") return;
1321
+ const key = correlationKey(session.id, label);
1322
+ const claim = pending.get(key);
1323
+ if (claim === void 0) return;
1324
+ pending.delete(key);
1325
+ recordChild(childSessionId, claim.role, claim.at, claim.personaNotApplied);
1326
+ } catch (error) {
1327
+ reportDegrade(error);
1328
+ }
1329
+ }));
1330
+ }
1331
+ disposers.push(installRoleNotice(ctx, {
1332
+ records,
1333
+ emitted: noticeEmitted,
1334
+ debug
1335
+ }));
1336
+ disposers.push(installRoleProjection(ctx, { debug }));
1337
+ const seam = {
1338
+ records,
1339
+ noticeEmitted,
1340
+ ownsWrapper,
1341
+ dispose: () => {
1342
+ if (ownsWrapper) subagentSeamRoots.delete(root);
1343
+ for (const dispose of disposers.splice(0)) dispose();
1344
+ }
1345
+ };
1346
+ subagentSeamStores.set(ctx, seam);
1347
+ return seam;
1348
+ }
1349
+ //#endregion
803
1350
  //#region .build/host/cooldown.js
804
1351
  var CooldownStore = class {
805
1352
  entries = new Map();
@@ -968,7 +1515,6 @@ function overrideConfigWithRouteRule(seed, to, explicitEffort) {
968
1515
  //#endregion
969
1516
  //#region .build/host/gateway.js
970
1517
  const FALLBACKS_SETTINGS_NAMESPACE = "fallbacks";
971
- const SUBAGENT_ROLES_MAX_IDS = 256;
972
1518
  const CONFIG_KEYS = {
973
1519
  enabled: true,
974
1520
  triggerCodes: true,
@@ -1003,13 +1549,11 @@ var FallbacksConfigGateway = class extends TypertRemoteService {
1003
1549
  seeds;
1004
1550
  settings;
1005
1551
  subagentPolicy;
1006
- subagentRolesSnapshot;
1007
- constructor(ctx, bridge, seeds, subagentPolicy, subagentRoles) {
1552
+ constructor(ctx, bridge, seeds, subagentPolicy) {
1008
1553
  super(ctx, "fallbacks");
1009
1554
  this.bridge = bridge;
1010
1555
  this.seeds = seeds;
1011
1556
  this.subagentPolicy = subagentPolicy;
1012
- this.subagentRolesSnapshot = subagentRoles;
1013
1557
  ctx.inject(["settings"], (sctx) => {
1014
1558
  this.settings = sctx.settings;
1015
1559
  return () => {
@@ -1044,23 +1588,6 @@ var FallbacksConfigGateway = class extends TypertRemoteService {
1044
1588
  outcome
1045
1589
  };
1046
1590
  }
1047
- subagentRoles(ids) {
1048
- if (!Array.isArray(ids) || ids.some((id) => typeof id !== "string")) throw new TypeError("dsh-llm-fallbacks: subagent-roles ids must be an array of strings");
1049
- if (ids.length > SUBAGENT_ROLES_MAX_IDS) throw new TypeError(`dsh-llm-fallbacks: subagent-roles ids batch exceeds ${SUBAGENT_ROLES_MAX_IDS} entries`);
1050
- if (this.subagentRolesSnapshot === void 0) return {};
1051
- let records;
1052
- try {
1053
- records = this.subagentRolesSnapshot();
1054
- } catch {
1055
- return {};
1056
- }
1057
- const projected = new Map();
1058
- for (const id of ids) {
1059
- const record = records.get(id);
1060
- if (record !== void 0) projected.set(id, projectSubagentRoleRecord(record));
1061
- }
1062
- return Object.fromEntries(projected);
1063
- }
1064
1591
  readConfig(source = this.bridge.source()) {
1065
1592
  const wire = {};
1066
1593
  for (const key of Object.keys(CONFIG_KEYS)) {
@@ -1127,17 +1654,6 @@ function projectSubagentPolicy(snapshot) {
1127
1654
  };
1128
1655
  return wire;
1129
1656
  }
1130
- function projectSubagentRoleRecord(record) {
1131
- const wire = {
1132
- role: record.role,
1133
- at: record.at
1134
- };
1135
- if (record.model !== void 0) wire.model = {
1136
- provider: record.model.provider,
1137
- model: record.model.model
1138
- };
1139
- return wire;
1140
- }
1141
1657
  function normalizeRoles(value) {
1142
1658
  if (typeof value !== "object" || value === null || Array.isArray(value)) return value;
1143
1659
  const roles = {};
@@ -1252,21 +1768,6 @@ function fallbacksTypertContribution() {
1252
1768
  codec: { mode: "src-json" }
1253
1769
  }],
1254
1770
  result: { mode: "src-json" }
1255
- },
1256
- {
1257
- id: "dsh-llm-fallbacks#fallbacks/subagent-roles",
1258
- service: "fallbacks",
1259
- namespace: "fallbacks",
1260
- method: "subagent-roles",
1261
- implementation: "subagentRoles",
1262
- invocation: { kind: "direct" },
1263
- parameters: [{
1264
- name: "ids",
1265
- wire: "ids",
1266
- source: "json",
1267
- codec: { mode: "src-json" }
1268
- }],
1269
- result: { mode: "src-json" }
1270
1771
  }
1271
1772
  ]
1272
1773
  };
@@ -1601,6 +2102,7 @@ function registerFallbacksCommands(registry, controller, locale = "zh") {
1601
2102
  var FallbacksSeedManager = class {
1602
2103
  logger;
1603
2104
  registry = new Map();
2105
+ declareSeq = 0;
1604
2106
  constructor(logger) {
1605
2107
  this.logger = logger;
1606
2108
  }
@@ -1646,28 +2148,69 @@ var FallbacksSeedManager = class {
1646
2148
  const config = io.read();
1647
2149
  const currentList = roleRows(config);
1648
2150
  const currentRules = roleRules(config);
1649
- const newList = materialize(currentList, personaView(entries), personaView(this.registry), outcome.conflicts);
2151
+ const candidate = new Map(this.registry);
2152
+ if (entries.size === 0) candidate.delete(source);
2153
+ else candidate.set(source, {
2154
+ seq: this.declareSeq + 1,
2155
+ entries
2156
+ });
2157
+ const prior = new Map();
2158
+ const incoming = new Map();
2159
+ for (const id of entries.keys()) {
2160
+ const before = this.resolveDeclared(id)?.persona;
2161
+ const after = this.resolveDeclared(id, candidate)?.persona;
2162
+ if (before !== void 0) prior.set(id, before);
2163
+ if (after !== void 0) incoming.set(id, after);
2164
+ }
2165
+ const newList = materialize(currentList, incoming, prior, outcome.conflicts);
1650
2166
  for (const conflict of outcome.conflicts) this.logger.warn(`llm-fallbacks: seeds: persona-source conflict for seed id ${JSON.stringify(conflict.id)} — operator row persona kept (never overwritten)`);
1651
2167
  const computed = {
1652
2168
  list: newList,
1653
2169
  rules: currentRules
1654
2170
  };
1655
2171
  if (!deepEqual(newList, currentList)) await io.writeRoles(computed);
1656
- this.registry = entries;
2172
+ if (entries.size === 0) this.registry.delete(source);
2173
+ else this.registry.set(source, {
2174
+ seq: ++this.declareSeq,
2175
+ entries
2176
+ });
1657
2177
  return outcome;
1658
2178
  }
2179
+ resolveDeclared(id, registry = this.registry) {
2180
+ const bundled = registry.get("bundled")?.entries.get(id);
2181
+ if (bundled !== void 0) return {
2182
+ persona: bundled.persona,
2183
+ source: "bundled"
2184
+ };
2185
+ let winner;
2186
+ for (const [label, slice] of registry) {
2187
+ if (label === "bundled") continue;
2188
+ const entry = slice.entries.get(id);
2189
+ if (entry === void 0) continue;
2190
+ if (winner === void 0 || slice.seq > winner.seq) winner = {
2191
+ persona: entry.persona,
2192
+ source: label,
2193
+ seq: slice.seq
2194
+ };
2195
+ }
2196
+ if (winner === void 0) return void 0;
2197
+ return {
2198
+ persona: winner.persona,
2199
+ source: winner.source
2200
+ };
2201
+ }
1659
2202
  effectiveRoles(io) {
1660
2203
  return { roles: roleRows(io.read()).map((row) => {
1661
- const entry = this.registry.get(row.id.trim());
1662
- const seeded = entry !== void 0;
2204
+ const declared = this.resolveDeclared(row.id.trim());
2205
+ const seeded = declared !== void 0;
1663
2206
  const effective = {
1664
2207
  id: row.id,
1665
2208
  persona: row.persona,
1666
2209
  seeded,
1667
- personaOverridden: seeded && row.persona !== entry.persona,
1668
- source: seeded ? entry.set : "user"
2210
+ personaOverridden: seeded && row.persona !== declared.persona,
2211
+ source: seeded ? declared.source : "user"
1669
2212
  };
1670
- if (seeded) effective.seedPersona = entry.persona;
2213
+ if (seeded) effective.seedPersona = declared.persona;
1671
2214
  if (row.chain !== void 0) effective.chain = row.chain;
1672
2215
  if (row.fallback !== void 0) effective.fallback = row.fallback;
1673
2216
  return effective;
@@ -1676,20 +2219,20 @@ var FallbacksSeedManager = class {
1676
2219
  wireStatus(io) {
1677
2220
  const status = [];
1678
2221
  for (const row of roleRows(io.read())) {
1679
- const entry = this.registry.get(row.id.trim());
1680
- if (entry === void 0) continue;
2222
+ const declared = this.resolveDeclared(row.id.trim());
2223
+ if (declared === void 0) continue;
1681
2224
  status.push({
1682
2225
  id: row.id,
1683
- overridden: row.persona !== entry.persona,
1684
- source: entry.set
2226
+ overridden: row.persona !== declared.persona,
2227
+ source: declared.source
1685
2228
  });
1686
2229
  }
1687
2230
  return status;
1688
2231
  }
1689
2232
  async revert(id, io) {
1690
2233
  const seedId = id.trim();
1691
- const entry = this.registry.get(seedId);
1692
- if (entry === void 0) return {
2234
+ const declared = this.resolveDeclared(seedId);
2235
+ if (declared === void 0) return {
1693
2236
  reverted: false,
1694
2237
  reason: "not-seeded"
1695
2238
  };
@@ -1701,13 +2244,13 @@ var FallbacksSeedManager = class {
1701
2244
  reverted: false,
1702
2245
  reason: "row-absent"
1703
2246
  };
1704
- if (rows[index].persona === entry.persona) return {
2247
+ if (rows[index].persona === declared.persona) return {
1705
2248
  reverted: true,
1706
- persona: entry.persona
2249
+ persona: declared.persona
1707
2250
  };
1708
2251
  const nextList = rows.map((row, i) => i === index ? {
1709
2252
  ...row,
1710
- persona: entry.persona
2253
+ persona: declared.persona
1711
2254
  } : row);
1712
2255
  await io.writeRoles({
1713
2256
  list: nextList,
@@ -1715,7 +2258,7 @@ var FallbacksSeedManager = class {
1715
2258
  });
1716
2259
  return {
1717
2260
  reverted: true,
1718
- persona: entry.persona
2261
+ persona: declared.persona
1719
2262
  };
1720
2263
  }
1721
2264
  warnSkip(id, reason) {
@@ -1740,43 +2283,38 @@ function resolveSource(options, logger) {
1740
2283
  logger.warn(`llm-fallbacks: seeds: ignoring declare set name ${shown} — invalid (empty after trim, non-string, or a reserved label ${reserved}); provenance degrades to "external", the seeds still apply`);
1741
2284
  return "external";
1742
2285
  }
1743
- function personaView(entries) {
1744
- const personas = new Map();
1745
- for (const [id, entry] of entries) personas.set(id, entry.persona);
1746
- return personas;
1747
- }
1748
- function materialize(rows, registry, previous, conflicts) {
2286
+ function materialize(rows, incoming, prior, conflicts) {
1749
2287
  const next = [];
1750
2288
  for (const row of rows) {
1751
2289
  const seedId = row.id.trim();
1752
- const incoming = registry.get(seedId);
1753
- if (incoming === void 0) {
2290
+ const inc = incoming.get(seedId);
2291
+ if (inc === void 0) {
1754
2292
  next.push(row);
1755
2293
  continue;
1756
2294
  }
1757
- const prior = previous.get(seedId);
1758
- if (prior === void 0) {
1759
- if (row.persona !== incoming) conflicts.push({
2295
+ const before = prior.get(seedId);
2296
+ if (before === void 0) {
2297
+ if (row.persona !== inc) conflicts.push({
1760
2298
  id: seedId,
1761
2299
  kind: "persona-source"
1762
2300
  });
1763
2301
  next.push(row);
1764
2302
  continue;
1765
2303
  }
1766
- if (row.persona === prior) {
2304
+ if (row.persona === before && inc !== before) {
1767
2305
  next.push({
1768
2306
  ...row,
1769
- persona: incoming
2307
+ persona: inc
1770
2308
  });
1771
2309
  continue;
1772
2310
  }
1773
- if (row.persona !== incoming) conflicts.push({
2311
+ if (row.persona !== inc) conflicts.push({
1774
2312
  id: seedId,
1775
2313
  kind: "persona-source"
1776
2314
  });
1777
2315
  next.push(row);
1778
2316
  }
1779
- for (const [id, persona] of registry) if (!rows.some((row) => row.id.trim() === id)) next.push({
2317
+ for (const [id, persona] of incoming) if (!rows.some((row) => row.id.trim() === id)) next.push({
1780
2318
  id,
1781
2319
  persona
1782
2320
  });
@@ -2230,6 +2768,37 @@ async function resolveHeadDisplayName(llm, head) {
2230
2768
  } catch {}
2231
2769
  return head.model;
2232
2770
  }
2771
+ function envelopeProvenance(replayState) {
2772
+ if (typeof replayState !== "object" || replayState === null) return void 0;
2773
+ const response = replayState.response;
2774
+ if (typeof response !== "object" || response === null) return void 0;
2775
+ const { provider, model } = response;
2776
+ if (typeof provider !== "string" || provider === "" || provider === "FallbacksChain") return void 0;
2777
+ if (typeof model !== "string" || model === "") return void 0;
2778
+ return {
2779
+ provider,
2780
+ model
2781
+ };
2782
+ }
2783
+ function restoreEnvelopeProvenance(messages) {
2784
+ let changed = false;
2785
+ const restored = messages.map((message) => {
2786
+ const source = message.source;
2787
+ if (message.role !== "assistant" || source.kind !== "model") return message;
2788
+ if (source.provider !== "FallbacksChain" || source.model !== "Auto") return message;
2789
+ const provenance = envelopeProvenance(source.replayState);
2790
+ if (provenance === void 0) return message;
2791
+ changed = true;
2792
+ return {
2793
+ ...message,
2794
+ source: {
2795
+ ...source,
2796
+ ...provenance
2797
+ }
2798
+ };
2799
+ });
2800
+ return changed ? restored : messages;
2801
+ }
2233
2802
  var FallbacksChainAdapter = class extends LlmAdapter {
2234
2803
  readConfig;
2235
2804
  getLlm;
@@ -2277,6 +2846,15 @@ var FallbacksChainAdapter = class extends LlmAdapter {
2277
2846
  name: model
2278
2847
  };
2279
2848
  }
2849
+ providerRetryPolicy(_provider) {
2850
+ try {
2851
+ const head = effectiveHeadOf(this.readConfig(), new Date());
2852
+ if (head === void 0 || head.provider === "FallbacksChain") return void 0;
2853
+ return this.getLlm()?.providerRetryPolicy(head.provider);
2854
+ } catch {
2855
+ return;
2856
+ }
2857
+ }
2280
2858
  imageRequestPricing(_provider, _model) {
2281
2859
  try {
2282
2860
  const head = effectiveHeadOf(this.readConfig(), new Date());
@@ -2297,7 +2875,8 @@ var FallbacksChainAdapter = class extends LlmAdapter {
2297
2875
  return llm.stream({
2298
2876
  ...options,
2299
2877
  provider: head.provider,
2300
- model: head.model
2878
+ model: head.model,
2879
+ messages: restoreEnvelopeProvenance(options.messages)
2301
2880
  });
2302
2881
  }
2303
2882
  };
@@ -2357,10 +2936,6 @@ const chainHeadStores = new WeakMap();
2357
2936
  function chainHeads(ctx) {
2358
2937
  return chainHeadStores.get(ctx);
2359
2938
  }
2360
- const subagentRoleRecordStores = new WeakMap();
2361
- function subagentRoleRecords(ctx) {
2362
- return subagentRoleRecordStores.get(ctx);
2363
- }
2364
2939
  function latestByAt(map) {
2365
2940
  let latest;
2366
2941
  for (const value of map.values()) if (latest === void 0 || value.at >= latest.at) latest = value;
@@ -2412,11 +2987,29 @@ function countRetryEvents(session, turn, step, provider) {
2412
2987
  }
2413
2988
  return count;
2414
2989
  }
2415
- function currentModel(agent, provider) {
2416
- return {
2990
+ function anchorServedRoute(config, route, now) {
2991
+ if (route.provider !== "FallbacksChain" || route.model !== "Auto") return route;
2992
+ const head = effectiveHeadOf(config, now);
2993
+ return head === void 0 ? route : {
2994
+ provider: head.provider,
2995
+ model: head.model
2996
+ };
2997
+ }
2998
+ function servedRouteFor(agent, config) {
2999
+ const provider = agent.options?.provider;
3000
+ const model = agent.options?.model;
3001
+ if (provider === void 0 || model === void 0) return void 0;
3002
+ const anchored = anchorServedRoute(config, {
3003
+ provider,
3004
+ model
3005
+ }, new Date());
3006
+ return anchored.provider === provider && anchored.model === model ? void 0 : anchored;
3007
+ }
3008
+ function currentModel(agent, provider, config, now) {
3009
+ return anchorServedRoute(config, {
2417
3010
  provider,
2418
3011
  model: agent.session.requestHeader()?.config.model ?? agent.options.model ?? ""
2419
- };
3012
+ }, now);
2420
3013
  }
2421
3014
  function slotWinnerKey(winner) {
2422
3015
  if (winner === "all-day") return "all-day";
@@ -2532,8 +3125,12 @@ function apply(ctx, config = defaultFallbacksConfig) {
2532
3125
  chainHeadStores.set(ctx, chainHeadMap);
2533
3126
  const blockedAttemptMap = new Map();
2534
3127
  blockedAttemptStores.set(ctx, blockedAttemptMap);
2535
- const subagentRoleRecordMap = new Map();
2536
- subagentRoleRecordStores.set(ctx, subagentRoleRecordMap);
3128
+ const subagentSeam = installSubagentSeam(ctx, {
3129
+ roleIds: () => roleIds,
3130
+ roles: () => source().roles.list,
3131
+ debug: (message) => logger.debug(message)
3132
+ });
3133
+ if (!subagentSeam.ownsWrapper) ctx.logger("llm-fallbacks").debug("subagent role seam wrapper already installed on this context root — this fiber registers the notice emitter and the projection unit only (multi-fiber dedupe)");
2537
3134
  try {
2538
3135
  new FallbacksConfigGateway(ctx, bridge, seeds, () => {
2539
3136
  try {
@@ -2548,7 +3145,7 @@ function apply(ctx, config = defaultFallbacksConfig) {
2548
3145
  } catch {
2549
3146
  return { policy: { state: "unprovable" } };
2550
3147
  }
2551
- }, () => subagentRoleRecordMap);
3148
+ });
2552
3149
  } catch (error) {
2553
3150
  if (!(error instanceof Error) || !error.message.includes("has been registered")) throw error;
2554
3151
  ctx.logger("llm-fallbacks").debug("fallbacks gateway already registered — no gateway on this fiber (multi-fiber dedupe)");
@@ -2573,7 +3170,7 @@ function apply(ctx, config = defaultFallbacksConfig) {
2573
3170
  states.syncStep(state, turn, step);
2574
3171
  if (state.stepFailures.switchCount >= config.maxSwitchesPerStep) return null;
2575
3172
  }
2576
- const role = resolveRole(agent, config.roles.rules, roleIds, logger.warn);
3173
+ const role = resolveRole(agent, config.roles.rules, roleIds, logger.warn, servedRouteFor(agent, config));
2577
3174
  const rootTail = agent.session?.header?.origin === "subagent" ? config.rootChain : resolveEffectiveChain(config, new Date(), config.tz ?? "Asia/Shanghai");
2578
3175
  const { all, wildcard } = resolveChainViews(config.roles.list, rootTail, role, current.provider, current.model, logger.warn);
2579
3176
  if (all.length === 0) return null;
@@ -2671,7 +3268,7 @@ function apply(ctx, config = defaultFallbacksConfig) {
2671
3268
  ctx.on("agent/request-error", async ({ agent, turn, step, provider, failure }, next) => {
2672
3269
  const config = source();
2673
3270
  if (!config.enabled || !config.triggerCodes.includes(failure.code)) return next();
2674
- const current = currentModel(agent, provider);
3271
+ const current = currentModel(agent, provider, config, new Date());
2675
3272
  if (!current.model) return next();
2676
3273
  try {
2677
3274
  const pending = await decide(agent, turn, step, current, "trigger-code", states.peek(agent.id), failure.code);
@@ -2706,17 +3303,6 @@ function apply(ctx, config = defaultFallbacksConfig) {
2706
3303
  label: slot.label
2707
3304
  });
2708
3305
  }
2709
- if (config.enabled && seed.provider === "FallbacksChain" && seed.model === "Auto" && agent.session?.header?.origin !== "subagent") {
2710
- if (!isAllDayConforming(config.rootChain)) logger.warn("llm-fallbacks: FallbacksChain/Auto selected but the all-day rootChain is not conforming (exactly one official model: deepseek-official/deepseek-flash or deepseek-official/deepseek-pro) — no primary override");
2711
- else {
2712
- const head = firstDispatchableExactHead(resolveEffectiveChain(config, new Date(), config.tz ?? "Asia/Shanghai"));
2713
- if (head === void 0) logger.warn("llm-fallbacks: FallbacksChain/Auto selected but the effective chain has no exact head (empty, wildcard-only, or self-route) — no primary override");
2714
- else {
2715
- logger.info("llm-fallbacks: FallbacksChain/Auto selection overrides to the effective head %s/%s", head.provider, head.model);
2716
- return overrideConfig(seed, head);
2717
- }
2718
- }
2719
- }
2720
3306
  if (config.enabled && hasChains && agent.session?.header?.origin === "subagent" && !dispatchInjected.has(agent.id)) {
2721
3307
  dispatchInjected.add(agent.id);
2722
3308
  try {
@@ -2739,7 +3325,8 @@ function apply(ctx, config = defaultFallbacksConfig) {
2739
3325
  const role = await resolveRoleAtDispatch(agent, config.roles.rules, roleIds, {
2740
3326
  automatchEnabled: config.roleAutoMatch ?? true,
2741
3327
  automatch: (agent) => pickRoleByLlm(ctx, config.roles, agent, { warn: logger.warn }),
2742
- warn: logger.warn
3328
+ warn: logger.warn,
3329
+ servedRoute: servedRouteFor(agent, config)
2743
3330
  });
2744
3331
  if (role !== "inherit") {
2745
3332
  const { all, wildcard } = resolveChainViews(config.roles.list, config.rootChain, role, seed.provider, seed.model, logger.warn);
@@ -2755,14 +3342,6 @@ function apply(ctx, config = defaultFallbacksConfig) {
2755
3342
  model: head.model
2756
3343
  };
2757
3344
  }
2758
- subagentRoleRecordMap.set(agent.id, {
2759
- role,
2760
- model: to ?? {
2761
- provider: seed.provider,
2762
- model: seed.model
2763
- },
2764
- at: Date.now()
2765
- });
2766
3345
  if (to !== void 0 && !(to.provider === seed.provider && to.model === seed.model)) {
2767
3346
  if (policy.state === "enabled") chainHeadMap.set(agent.id, {
2768
3347
  at: Date.now(),
@@ -2784,19 +3363,17 @@ function apply(ctx, config = defaultFallbacksConfig) {
2784
3363
  }
2785
3364
  if (hasChains && config.enabled && config.alwaysModeRetryCap > 0 && countRetryEvents(agent.session, turn, step, seed.provider) >= config.alwaysModeRetryCap) {
2786
3365
  const decisionState = states.peek(agent.id);
2787
- const pending = await decide(agent, turn, step, {
3366
+ const capRoute = anchorServedRoute(config, {
2788
3367
  provider: seed.provider,
2789
3368
  model: seed.model
2790
- }, "always-cap", decisionState);
3369
+ }, new Date());
3370
+ const pending = await decide(agent, turn, step, capRoute, "always-cap", decisionState);
2791
3371
  if (pending !== null) {
2792
3372
  const commitState = states.get(agent.id);
2793
3373
  commit(commitState, pending, turn, step);
2794
3374
  const appliedCap = states.applyPending(commitState, turn, step);
2795
3375
  if (appliedCap !== void 0) return overrideConfig(seed, appliedCap.to);
2796
- } else failHalfOpenProbe(agent.id, {
2797
- provider: seed.provider,
2798
- model: seed.model
2799
- });
3376
+ } else failHalfOpenProbe(agent.id, capRoute);
2800
3377
  }
2801
3378
  return seed;
2802
3379
  });
@@ -2811,18 +3388,23 @@ function apply(ctx, config = defaultFallbacksConfig) {
2811
3388
  slotWinners.delete(agent.id);
2812
3389
  blockedAttemptMap.delete(agent.id);
2813
3390
  chainHeadMap.delete(agent.id);
2814
- subagentRoleRecordMap.delete(agent.id);
3391
+ subagentSeam.records.delete(agent.id);
2815
3392
  lastKnownPolicySettings.delete(agent.id);
2816
3393
  });
2817
3394
  ctx.on("session/event", (session, event) => {
2818
3395
  if (event.type !== "assistant/message") return;
2819
- if ((source().recovery ?? "timer") !== "half-open") return;
3396
+ const config = source();
3397
+ if ((config.recovery ?? "timer") !== "half-open") return;
2820
3398
  if (event.data.interrupted === true) return;
2821
3399
  const message = event.data.message;
2822
3400
  if (message.source.kind !== "model") return;
2823
3401
  const state = states.peek(session.id);
2824
3402
  if (state === void 0) return;
2825
- states.observeSuccess(state, selectorKey(message.source.provider, message.source.model));
3403
+ const observed = anchorServedRoute(config, {
3404
+ provider: message.source.provider,
3405
+ model: message.source.model
3406
+ }, new Date());
3407
+ states.observeSuccess(state, selectorKey(observed.provider, observed.model));
2826
3408
  });
2827
3409
  ctx.effect(() => () => {
2828
3410
  states.clear();
@@ -2830,13 +3412,15 @@ function apply(ctx, config = defaultFallbacksConfig) {
2830
3412
  slotWinners.clear();
2831
3413
  blockedAttemptMap.clear();
2832
3414
  chainHeadMap.clear();
2833
- subagentRoleRecordMap.clear();
3415
+ subagentSeam.records.clear();
3416
+ subagentSeam.noticeEmitted.clear();
3417
+ subagentSeam.dispose();
2834
3418
  lastKnownPolicySettings.clear();
2835
3419
  }, "llm-fallbacks: clear per-agent state");
2836
3420
  const fallbacksCommandController = {
2837
3421
  getSnapshot(agent) {
2838
3422
  const config = source();
2839
- const role = resolveRole(agent, config.roles.rules, roleIds, logger.warn);
3423
+ const role = resolveRole(agent, config.roles.rules, roleIds, logger.warn, servedRouteFor(agent, config));
2840
3424
  const state = states.peek(agent.id);
2841
3425
  const recovery = config.recovery ?? "timer";
2842
3426
  if (state !== void 0 && recovery === "half-open") states.syncRecovery(state, Date.now(), recovery);
@@ -2909,4 +3493,4 @@ function apply(ctx, config = defaultFallbacksConfig) {
2909
3493
  });
2910
3494
  }
2911
3495
  //#endregion
2912
- export { Config, FallbacksSeedManager, INHERIT_ROLE_ID, ROLE_ID_PATTERN, SelectorError, annotateCandidates, apply, blockedAttempts, chainHeads, countRetryEvents, createCandidateFilter, defaultFallbacksConfig, detectLegacyKeys, hasWildcardEntry, name, parseSelector, presetRoles, provide, resolveCandidate, resolveChain, resolveChainViews, resolveRole, selectCandidates, stateStore, subagentRoleRecords, validateFallbacksConfig };
3496
+ export { Config, FallbacksSeedManager, INHERIT_ROLE_ID, ROLE_ID_PATTERN, SelectorError, annotateCandidates, apply, blockedAttempts, chainHeads, countRetryEvents, createCandidateFilter, defaultFallbacksConfig, detectLegacyKeys, hasWildcardEntry, name, parseSelector, presetRoles, provide, resolveCandidate, resolveChain, resolveChainViews, resolveRole, selectCandidates, stateStore, validateFallbacksConfig };