dsh-llm-fallbacks 0.5.0 → 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 +5 -1
- package/README.zh-CN.md +5 -1
- 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 +65 -142
- package/dist/gateway.d.ts +2 -55
- package/dist/index.d.ts +0 -23
- package/dist/index.js +557 -66
- 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/subagents-seam.d.ts +292 -0
- package/package.json +24 -23
- package/scripts/build-client.ts +1 -1
- package/scripts/repair-session-logs.ts +239 -57
- package/scripts/session-logs/publish.ts +79 -26
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
|
-
|
|
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
|
|
2636
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 };
|