dsh-llm-fallbacks 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 {
@@ -804,6 +804,549 @@ function effectivePolicy(event, settings) {
804
804
  };
805
805
  }
806
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
807
1350
  //#region .build/host/cooldown.js
808
1351
  var CooldownStore = class {
809
1352
  entries = new Map();
@@ -972,7 +1515,6 @@ function overrideConfigWithRouteRule(seed, to, explicitEffort) {
972
1515
  //#endregion
973
1516
  //#region .build/host/gateway.js
974
1517
  const FALLBACKS_SETTINGS_NAMESPACE = "fallbacks";
975
- const SUBAGENT_ROLES_MAX_IDS = 256;
976
1518
  const CONFIG_KEYS = {
977
1519
  enabled: true,
978
1520
  triggerCodes: true,
@@ -1007,13 +1549,11 @@ var FallbacksConfigGateway = class extends TypertRemoteService {
1007
1549
  seeds;
1008
1550
  settings;
1009
1551
  subagentPolicy;
1010
- subagentRolesSnapshot;
1011
- constructor(ctx, bridge, seeds, subagentPolicy, subagentRoles) {
1552
+ constructor(ctx, bridge, seeds, subagentPolicy) {
1012
1553
  super(ctx, "fallbacks");
1013
1554
  this.bridge = bridge;
1014
1555
  this.seeds = seeds;
1015
1556
  this.subagentPolicy = subagentPolicy;
1016
- this.subagentRolesSnapshot = subagentRoles;
1017
1557
  ctx.inject(["settings"], (sctx) => {
1018
1558
  this.settings = sctx.settings;
1019
1559
  return () => {
@@ -1048,23 +1588,6 @@ var FallbacksConfigGateway = class extends TypertRemoteService {
1048
1588
  outcome
1049
1589
  };
1050
1590
  }
1051
- subagentRoles(ids) {
1052
- 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");
1053
- if (ids.length > SUBAGENT_ROLES_MAX_IDS) throw new TypeError(`dsh-llm-fallbacks: subagent-roles ids batch exceeds ${SUBAGENT_ROLES_MAX_IDS} entries`);
1054
- if (this.subagentRolesSnapshot === void 0) return {};
1055
- let records;
1056
- try {
1057
- records = this.subagentRolesSnapshot();
1058
- } catch {
1059
- return {};
1060
- }
1061
- const projected = new Map();
1062
- for (const id of ids) {
1063
- const record = records.get(id);
1064
- if (record !== void 0) projected.set(id, projectSubagentRoleRecord(record));
1065
- }
1066
- return Object.fromEntries(projected);
1067
- }
1068
1591
  readConfig(source = this.bridge.source()) {
1069
1592
  const wire = {};
1070
1593
  for (const key of Object.keys(CONFIG_KEYS)) {
@@ -1131,17 +1654,6 @@ function projectSubagentPolicy(snapshot) {
1131
1654
  };
1132
1655
  return wire;
1133
1656
  }
1134
- function projectSubagentRoleRecord(record) {
1135
- const wire = {
1136
- role: record.role,
1137
- at: record.at
1138
- };
1139
- if (record.model !== void 0) wire.model = {
1140
- provider: record.model.provider,
1141
- model: record.model.model
1142
- };
1143
- return wire;
1144
- }
1145
1657
  function normalizeRoles(value) {
1146
1658
  if (typeof value !== "object" || value === null || Array.isArray(value)) return value;
1147
1659
  const roles = {};
@@ -1256,21 +1768,6 @@ function fallbacksTypertContribution() {
1256
1768
  codec: { mode: "src-json" }
1257
1769
  }],
1258
1770
  result: { mode: "src-json" }
1259
- },
1260
- {
1261
- id: "dsh-llm-fallbacks#fallbacks/subagent-roles",
1262
- service: "fallbacks",
1263
- namespace: "fallbacks",
1264
- method: "subagent-roles",
1265
- implementation: "subagentRoles",
1266
- invocation: { kind: "direct" },
1267
- parameters: [{
1268
- name: "ids",
1269
- wire: "ids",
1270
- source: "json",
1271
- codec: { mode: "src-json" }
1272
- }],
1273
- result: { mode: "src-json" }
1274
1771
  }
1275
1772
  ]
1276
1773
  };
@@ -2439,10 +2936,6 @@ const chainHeadStores = new WeakMap();
2439
2936
  function chainHeads(ctx) {
2440
2937
  return chainHeadStores.get(ctx);
2441
2938
  }
2442
- const subagentRoleRecordStores = new WeakMap();
2443
- function subagentRoleRecords(ctx) {
2444
- return subagentRoleRecordStores.get(ctx);
2445
- }
2446
2939
  function latestByAt(map) {
2447
2940
  let latest;
2448
2941
  for (const value of map.values()) if (latest === void 0 || value.at >= latest.at) latest = value;
@@ -2632,8 +3125,12 @@ function apply(ctx, config = defaultFallbacksConfig) {
2632
3125
  chainHeadStores.set(ctx, chainHeadMap);
2633
3126
  const blockedAttemptMap = new Map();
2634
3127
  blockedAttemptStores.set(ctx, blockedAttemptMap);
2635
- const subagentRoleRecordMap = new Map();
2636
- 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)");
2637
3134
  try {
2638
3135
  new FallbacksConfigGateway(ctx, bridge, seeds, () => {
2639
3136
  try {
@@ -2648,7 +3145,7 @@ function apply(ctx, config = defaultFallbacksConfig) {
2648
3145
  } catch {
2649
3146
  return { policy: { state: "unprovable" } };
2650
3147
  }
2651
- }, () => subagentRoleRecordMap);
3148
+ });
2652
3149
  } catch (error) {
2653
3150
  if (!(error instanceof Error) || !error.message.includes("has been registered")) throw error;
2654
3151
  ctx.logger("llm-fallbacks").debug("fallbacks gateway already registered — no gateway on this fiber (multi-fiber dedupe)");
@@ -2845,14 +3342,6 @@ function apply(ctx, config = defaultFallbacksConfig) {
2845
3342
  model: head.model
2846
3343
  };
2847
3344
  }
2848
- subagentRoleRecordMap.set(agent.id, {
2849
- role,
2850
- model: to ?? {
2851
- provider: seed.provider,
2852
- model: seed.model
2853
- },
2854
- at: Date.now()
2855
- });
2856
3345
  if (to !== void 0 && !(to.provider === seed.provider && to.model === seed.model)) {
2857
3346
  if (policy.state === "enabled") chainHeadMap.set(agent.id, {
2858
3347
  at: Date.now(),
@@ -2899,7 +3388,7 @@ function apply(ctx, config = defaultFallbacksConfig) {
2899
3388
  slotWinners.delete(agent.id);
2900
3389
  blockedAttemptMap.delete(agent.id);
2901
3390
  chainHeadMap.delete(agent.id);
2902
- subagentRoleRecordMap.delete(agent.id);
3391
+ subagentSeam.records.delete(agent.id);
2903
3392
  lastKnownPolicySettings.delete(agent.id);
2904
3393
  });
2905
3394
  ctx.on("session/event", (session, event) => {
@@ -2923,7 +3412,9 @@ function apply(ctx, config = defaultFallbacksConfig) {
2923
3412
  slotWinners.clear();
2924
3413
  blockedAttemptMap.clear();
2925
3414
  chainHeadMap.clear();
2926
- subagentRoleRecordMap.clear();
3415
+ subagentSeam.records.clear();
3416
+ subagentSeam.noticeEmitted.clear();
3417
+ subagentSeam.dispose();
2927
3418
  lastKnownPolicySettings.clear();
2928
3419
  }, "llm-fallbacks: clear per-agent state");
2929
3420
  const fallbacksCommandController = {
@@ -3002,4 +3493,4 @@ function apply(ctx, config = defaultFallbacksConfig) {
3002
3493
  });
3003
3494
  }
3004
3495
  //#endregion
3005
- 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 };