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/README.md +86 -25
- package/README.zh-CN.md +86 -25
- package/dist/client/SubagentRoleBadge.d.ts +40 -15
- package/dist/client/fallbacks-store.d.ts +0 -53
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +77 -150
- package/dist/client/locales.d.ts +2 -0
- package/dist/events.d.ts +2 -2
- package/dist/gateway.d.ts +2 -55
- package/dist/index.d.ts +6 -27
- package/dist/index.js +713 -129
- package/dist/role-notice.d.ts +156 -0
- package/dist/role-projection-key.d.ts +41 -0
- package/dist/role-projection.d.ts +151 -0
- package/dist/role-resolution.d.ts +11 -0
- package/dist/roles.d.ts +4 -1
- package/dist/seeds.d.ts +63 -21
- package/dist/subagents-seam.d.ts +292 -0
- package/dist/time-slots.d.ts +3 -2
- package/dist/virtual-adapter.d.ts +42 -6
- package/package.json +27 -25
- package/scripts/build-client.ts +1 -1
- package/scripts/repair-session-logs.ts +2040 -0
- package/scripts/session-logs/catalog.ts +547 -0
- package/scripts/session-logs/classify.ts +47 -0
- package/scripts/session-logs/publish.ts +583 -0
- package/scripts/session-logs/rules.ts +1227 -0
- package/scripts/repair-fallbacks-switch-logs.ts +0 -342
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
|
-
|
|
675
|
-
if (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
1662
|
-
const seeded =
|
|
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 !==
|
|
1668
|
-
source: seeded ?
|
|
2210
|
+
personaOverridden: seeded && row.persona !== declared.persona,
|
|
2211
|
+
source: seeded ? declared.source : "user"
|
|
1669
2212
|
};
|
|
1670
|
-
if (seeded) effective.seedPersona =
|
|
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
|
|
1680
|
-
if (
|
|
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 !==
|
|
1684
|
-
source:
|
|
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
|
|
1692
|
-
if (
|
|
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 ===
|
|
2247
|
+
if (rows[index].persona === declared.persona) return {
|
|
1705
2248
|
reverted: true,
|
|
1706
|
-
persona:
|
|
2249
|
+
persona: declared.persona
|
|
1707
2250
|
};
|
|
1708
2251
|
const nextList = rows.map((row, i) => i === index ? {
|
|
1709
2252
|
...row,
|
|
1710
|
-
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:
|
|
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
|
|
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
|
|
1753
|
-
if (
|
|
2290
|
+
const inc = incoming.get(seedId);
|
|
2291
|
+
if (inc === void 0) {
|
|
1754
2292
|
next.push(row);
|
|
1755
2293
|
continue;
|
|
1756
2294
|
}
|
|
1757
|
-
const
|
|
1758
|
-
if (
|
|
1759
|
-
if (row.persona !==
|
|
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 ===
|
|
2304
|
+
if (row.persona === before && inc !== before) {
|
|
1767
2305
|
next.push({
|
|
1768
2306
|
...row,
|
|
1769
|
-
persona:
|
|
2307
|
+
persona: inc
|
|
1770
2308
|
});
|
|
1771
2309
|
continue;
|
|
1772
2310
|
}
|
|
1773
|
-
if (row.persona !==
|
|
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
|
|
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
|
|
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
|
|
2536
|
-
|
|
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
|
-
}
|
|
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
|
|
3366
|
+
const capRoute = anchorServedRoute(config, {
|
|
2788
3367
|
provider: seed.provider,
|
|
2789
3368
|
model: seed.model
|
|
2790
|
-
},
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 };
|