dsh-llm-fallbacks 0.6.5 → 0.6.7
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 +60 -231
- package/README.zh-CN.md +77 -248
- package/dist/authorized-route.d.ts +1 -1
- package/dist/index.d.ts +23 -3
- package/dist/index.js +170 -106
- package/dist/role-persona.d.ts +71 -0
- package/dist/role-resolution.d.ts +25 -12
- package/dist/roles.d.ts +6 -4
- package/dist/subagents-seam.d.ts +83 -70
- package/package.json +26 -25
- package/scripts/repair-session-logs.ts +68 -34
- package/scripts/session-logs/catalog.ts +103 -12
- package/scripts/session-logs/publish.ts +23 -9
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { LlmAdapter, LlmError, boundContextSummary, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { PERSONA_PREFIX_SECTION } from "@deepseek-ai/dsh-system-prompt";
|
|
4
6
|
import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
5
7
|
//#region .build/host/selectors.js
|
|
6
8
|
var SelectorError = class extends Error {
|
|
@@ -695,6 +697,11 @@ async function resolveRoleAtDispatch(agent, rules, roleIds, opts) {
|
|
|
695
697
|
const declared = roleIds.get(preset);
|
|
696
698
|
if (declared !== void 0) return declared;
|
|
697
699
|
}
|
|
700
|
+
const dispatch = opts.dispatchRole?.trim();
|
|
701
|
+
if (dispatch !== void 0 && dispatch !== "" && dispatch !== "inherit") {
|
|
702
|
+
const declared = roleIds.get(dispatch);
|
|
703
|
+
if (declared !== void 0) return declared;
|
|
704
|
+
}
|
|
698
705
|
const role = resolveRole(agent, rules, roleIds, opts.warn, opts.servedRoute);
|
|
699
706
|
if (role !== "inherit") return role;
|
|
700
707
|
if (opts.automatchEnabled && opts.automatch !== void 0) {
|
|
@@ -828,7 +835,7 @@ function buildRoleNotice(role, personaNotApplied) {
|
|
|
828
835
|
}
|
|
829
836
|
});
|
|
830
837
|
}
|
|
831
|
-
function errorMessage$
|
|
838
|
+
function errorMessage$3(error) {
|
|
832
839
|
return error instanceof Error ? error.message : String(error);
|
|
833
840
|
}
|
|
834
841
|
function installRoleNotice(ctx, options) {
|
|
@@ -855,7 +862,7 @@ function installRoleNotice(ctx, options) {
|
|
|
855
862
|
messages: [...decision.messages, notice]
|
|
856
863
|
};
|
|
857
864
|
} catch (error) {
|
|
858
|
-
debug(`llm-fallbacks: role notice for agent "${agent.id}" skipped (contained, the step is unchanged): ${errorMessage$
|
|
865
|
+
debug(`llm-fallbacks: role notice for agent "${agent.id}" skipped (contained, the step is unchanged): ${errorMessage$3(error)}`);
|
|
859
866
|
return decision;
|
|
860
867
|
}
|
|
861
868
|
}, { prepend: true });
|
|
@@ -863,6 +870,9 @@ function installRoleNotice(ctx, options) {
|
|
|
863
870
|
//#endregion
|
|
864
871
|
//#region .build/host/role-projection-key.js
|
|
865
872
|
const ROLE_PROJECTION_KEY = "fallbacksSubagentRole";
|
|
873
|
+
function readRoleProjectionValue(value) {
|
|
874
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
875
|
+
}
|
|
866
876
|
const ROLE_STATE_SCHEMA = { parse(value) {
|
|
867
877
|
if (value === null) return null;
|
|
868
878
|
if (typeof value === "object") {
|
|
@@ -942,7 +952,7 @@ const roleProjectionUnit = {
|
|
|
942
952
|
view: (state) => state === null ? null : state.role ?? null
|
|
943
953
|
}
|
|
944
954
|
};
|
|
945
|
-
function errorMessage$
|
|
955
|
+
function errorMessage$2(error) {
|
|
946
956
|
return error instanceof Error ? error.message : String(error);
|
|
947
957
|
}
|
|
948
958
|
function installRoleProjection(ctx, options = {}) {
|
|
@@ -962,12 +972,12 @@ function installRoleProjection(ctx, options = {}) {
|
|
|
962
972
|
}
|
|
963
973
|
return registry.register(roleProjectionUnit);
|
|
964
974
|
} catch (error) {
|
|
965
|
-
debug(`llm-fallbacks: role projection not registered (contained): ${errorMessage$
|
|
975
|
+
debug(`llm-fallbacks: role projection not registered (contained): ${errorMessage$2(error)}`);
|
|
966
976
|
return () => {};
|
|
967
977
|
}
|
|
968
978
|
});
|
|
969
979
|
} catch (error) {
|
|
970
|
-
debug(`llm-fallbacks: role projection install skipped (contained): ${errorMessage$
|
|
980
|
+
debug(`llm-fallbacks: role projection install skipped (contained): ${errorMessage$2(error)}`);
|
|
971
981
|
}
|
|
972
982
|
return () => {
|
|
973
983
|
try {
|
|
@@ -1066,7 +1076,7 @@ function parentSessionIdOf(request) {
|
|
|
1066
1076
|
function correlationKey(parentSessionId, label) {
|
|
1067
1077
|
return `${parentSessionId}\u0000${label}`;
|
|
1068
1078
|
}
|
|
1069
|
-
function errorMessage(error) {
|
|
1079
|
+
function errorMessage$1(error) {
|
|
1070
1080
|
return error instanceof Error ? error.message : String(error);
|
|
1071
1081
|
}
|
|
1072
1082
|
const subagentSeamStores = new WeakMap();
|
|
@@ -1082,6 +1092,9 @@ function seamStateFor(root) {
|
|
|
1082
1092
|
return created;
|
|
1083
1093
|
}
|
|
1084
1094
|
const subagentSeamRoots = new WeakSet();
|
|
1095
|
+
function subagentSeamOf(ctx) {
|
|
1096
|
+
return subagentSeamStores.get(ctx);
|
|
1097
|
+
}
|
|
1085
1098
|
function installSubagentSeam(ctx, options) {
|
|
1086
1099
|
const root = ctx.root;
|
|
1087
1100
|
const state = seamStateFor(root);
|
|
@@ -1100,20 +1113,20 @@ function installSubagentSeam(ctx, options) {
|
|
|
1100
1113
|
} catch {}
|
|
1101
1114
|
};
|
|
1102
1115
|
const reportDegrade = (error) => {
|
|
1103
|
-
debug(`llm-fallbacks: subagent role seam degraded to the native path (start unaffected): ${errorMessage(error)}`);
|
|
1116
|
+
debug(`llm-fallbacks: subagent role seam degraded to the native path (start unaffected): ${errorMessage$1(error)}`);
|
|
1104
1117
|
};
|
|
1105
|
-
const recordChild = (
|
|
1106
|
-
const existing = records.get(
|
|
1107
|
-
records.set(
|
|
1118
|
+
const recordChild = (childId, role, at, personaMissed) => {
|
|
1119
|
+
const existing = records.get(childId);
|
|
1120
|
+
records.set(childId, {
|
|
1108
1121
|
role,
|
|
1109
1122
|
at,
|
|
1110
1123
|
firstNoticePending: existing?.firstNoticePending ?? true,
|
|
1111
|
-
...personaNotApplied ||
|
|
1124
|
+
...existing?.personaNotApplied === true || personaMissed === true ? { personaNotApplied: true } : {}
|
|
1112
1125
|
});
|
|
1113
1126
|
};
|
|
1114
|
-
const beginCorrelation = (
|
|
1115
|
-
const parentSessionId = parentSessionIdOf(request);
|
|
1116
|
-
const label = typeof
|
|
1127
|
+
const beginCorrelation = (spec, role, at) => {
|
|
1128
|
+
const parentSessionId = parentSessionIdOf(spec?.request);
|
|
1129
|
+
const label = typeof spec?.label === "string" ? spec.label : void 0;
|
|
1117
1130
|
if (parentSessionId === void 0 || label === void 0 || label === "") return void 0;
|
|
1118
1131
|
const key = correlationKey(parentSessionId, label);
|
|
1119
1132
|
if (!pending.has(key) && pending.size >= 64) {
|
|
@@ -1123,8 +1136,7 @@ function installSubagentSeam(ctx, options) {
|
|
|
1123
1136
|
pending.delete(key);
|
|
1124
1137
|
pending.set(key, {
|
|
1125
1138
|
role,
|
|
1126
|
-
at
|
|
1127
|
-
personaNotApplied
|
|
1139
|
+
at
|
|
1128
1140
|
});
|
|
1129
1141
|
return key;
|
|
1130
1142
|
};
|
|
@@ -1168,68 +1180,13 @@ function installSubagentSeam(ctx, options) {
|
|
|
1168
1180
|
}
|
|
1169
1181
|
onValue(result);
|
|
1170
1182
|
};
|
|
1171
|
-
const
|
|
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) => {
|
|
1183
|
+
const isLocalBackend = (service, providerName) => {
|
|
1225
1184
|
try {
|
|
1226
|
-
|
|
1185
|
+
if (typeof service.getProvider !== "function") return false;
|
|
1186
|
+
return typeof service.getProvider(providerName)?.prepareContinuable === "function";
|
|
1227
1187
|
} catch (error) {
|
|
1228
1188
|
reportDegrade(error);
|
|
1229
|
-
return
|
|
1230
|
-
request,
|
|
1231
|
-
personaNotApplied: false
|
|
1232
|
-
};
|
|
1189
|
+
return false;
|
|
1233
1190
|
}
|
|
1234
1191
|
};
|
|
1235
1192
|
const wrapService = (value) => {
|
|
@@ -1241,61 +1198,71 @@ function installSubagentSeam(ctx, options) {
|
|
|
1241
1198
|
return value;
|
|
1242
1199
|
}
|
|
1243
1200
|
const service = value;
|
|
1244
|
-
if (typeof service.
|
|
1201
|
+
if (typeof service.startActivation !== "function") {
|
|
1245
1202
|
if (!unreadableServiceLogged) {
|
|
1246
1203
|
unreadableServiceLogged = true;
|
|
1247
|
-
debug(`llm-fallbacks: subagent role seam skipped — '${SUBAGENT_SEAM_SERVICE}' is not
|
|
1204
|
+
debug(`llm-fallbacks: subagent role seam skipped — '${SUBAGENT_SEAM_SERVICE}' is not an activation-capable runtime`);
|
|
1248
1205
|
}
|
|
1249
1206
|
return value;
|
|
1250
1207
|
}
|
|
1251
1208
|
const cached = wrappers.get(value);
|
|
1252
1209
|
if (cached !== void 0) return cached;
|
|
1253
1210
|
const wrapper = Object.create(value);
|
|
1254
|
-
wrapper.
|
|
1255
|
-
const role = resolveRoleAtSeam(request);
|
|
1256
|
-
if (role === void 0) return service.
|
|
1257
|
-
const
|
|
1211
|
+
wrapper.startActivation = (spec) => {
|
|
1212
|
+
const role = resolveRoleAtSeam(spec?.request) ?? (typeof spec?.childId === "string" ? records.get(spec.childId)?.role : void 0);
|
|
1213
|
+
if (role === void 0) return service.startActivation(spec);
|
|
1214
|
+
const at = Date.now();
|
|
1215
|
+
let effectiveSpec = spec;
|
|
1216
|
+
let reservedId;
|
|
1217
|
+
let reservationPersonaMissed;
|
|
1218
|
+
try {
|
|
1219
|
+
if (isLocalBackend(service, spec.provider)) {
|
|
1220
|
+
reservedId = spec.childId ?? randomUUID();
|
|
1221
|
+
if (reservedId !== spec.childId) effectiveSpec = {
|
|
1222
|
+
...spec,
|
|
1223
|
+
childId: reservedId
|
|
1224
|
+
};
|
|
1225
|
+
recordChild(reservedId, role, at);
|
|
1226
|
+
}
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
if (reservedId !== void 0) records.delete(reservedId);
|
|
1229
|
+
effectiveSpec = spec;
|
|
1230
|
+
reservedId = void 0;
|
|
1231
|
+
reportDegrade(error);
|
|
1232
|
+
try {
|
|
1233
|
+
reservationPersonaMissed = personaForRole(options.roles?.() ?? [], role) !== void 0;
|
|
1234
|
+
} catch (personaError) {
|
|
1235
|
+
reportDegrade(personaError);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1258
1238
|
let pendingKey;
|
|
1259
1239
|
try {
|
|
1260
|
-
pendingKey = beginCorrelation(
|
|
1240
|
+
pendingKey = beginCorrelation(spec, role, at);
|
|
1261
1241
|
} catch (error) {
|
|
1262
1242
|
reportDegrade(error);
|
|
1263
1243
|
}
|
|
1264
1244
|
let result;
|
|
1265
1245
|
try {
|
|
1266
|
-
result = service.
|
|
1246
|
+
result = service.startActivation(effectiveSpec);
|
|
1267
1247
|
} catch (error) {
|
|
1268
1248
|
endCorrelation(pendingKey);
|
|
1249
|
+
if (reservedId !== void 0) records.delete(reservedId);
|
|
1269
1250
|
throw error;
|
|
1270
1251
|
}
|
|
1271
1252
|
try {
|
|
1272
|
-
observeStartResult(result, (
|
|
1253
|
+
observeStartResult(result, (childId) => {
|
|
1254
|
+
endCorrelation(pendingKey);
|
|
1255
|
+
recordChild(childId, role, at, reservationPersonaMissed);
|
|
1256
|
+
}, () => {}, () => {
|
|
1273
1257
|
endCorrelation(pendingKey);
|
|
1274
|
-
|
|
1275
|
-
}
|
|
1258
|
+
if (reservedId !== void 0) records.delete(reservedId);
|
|
1259
|
+
});
|
|
1276
1260
|
} catch (error) {
|
|
1277
1261
|
endCorrelation(pendingKey);
|
|
1278
1262
|
reportDegrade(error);
|
|
1279
1263
|
}
|
|
1280
1264
|
return result;
|
|
1281
1265
|
};
|
|
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
1266
|
wrappers.set(value, wrapper);
|
|
1300
1267
|
return wrapper;
|
|
1301
1268
|
};
|
|
@@ -1322,7 +1289,7 @@ function installSubagentSeam(ctx, options) {
|
|
|
1322
1289
|
const claim = pending.get(key);
|
|
1323
1290
|
if (claim === void 0) return;
|
|
1324
1291
|
pending.delete(key);
|
|
1325
|
-
recordChild(childSessionId, claim.role, claim.at
|
|
1292
|
+
recordChild(childSessionId, claim.role, claim.at);
|
|
1326
1293
|
} catch (error) {
|
|
1327
1294
|
reportDegrade(error);
|
|
1328
1295
|
}
|
|
@@ -1347,6 +1314,96 @@ function installSubagentSeam(ctx, options) {
|
|
|
1347
1314
|
return seam;
|
|
1348
1315
|
}
|
|
1349
1316
|
//#endregion
|
|
1317
|
+
//#region .build/host/role-persona.js
|
|
1318
|
+
function errorMessage(error) {
|
|
1319
|
+
return error instanceof Error ? error.message : String(error);
|
|
1320
|
+
}
|
|
1321
|
+
const rolePersonaStates = new WeakMap();
|
|
1322
|
+
function rolePersonaStateFor(root) {
|
|
1323
|
+
const existing = rolePersonaStates.get(root);
|
|
1324
|
+
if (existing !== void 0) return existing;
|
|
1325
|
+
const created = new Map();
|
|
1326
|
+
rolePersonaStates.set(root, created);
|
|
1327
|
+
return created;
|
|
1328
|
+
}
|
|
1329
|
+
function installRolePersona(ctx, options) {
|
|
1330
|
+
const root = ctx.root;
|
|
1331
|
+
const registered = rolePersonaStateFor(root);
|
|
1332
|
+
const owner = {};
|
|
1333
|
+
const fibers = new Map();
|
|
1334
|
+
const debug = (message) => {
|
|
1335
|
+
try {
|
|
1336
|
+
options.debug?.(message);
|
|
1337
|
+
} catch {}
|
|
1338
|
+
};
|
|
1339
|
+
const contain = (agent, record, error) => {
|
|
1340
|
+
debug(`llm-fallbacks: role persona for agent "${agent.id}" (role "${record.role}") not applied — contained, the child runs unchanged: ${errorMessage(error)}`);
|
|
1341
|
+
record.personaNotApplied = true;
|
|
1342
|
+
};
|
|
1343
|
+
const release = (id) => {
|
|
1344
|
+
const fiber = fibers.get(id);
|
|
1345
|
+
if (fiber === void 0) return;
|
|
1346
|
+
fibers.delete(id);
|
|
1347
|
+
fiber.dispose().catch((error) => {
|
|
1348
|
+
debug(`llm-fallbacks: role persona section for agent "${id}" not withdrawn (contained): ${errorMessage(error)}`);
|
|
1349
|
+
});
|
|
1350
|
+
};
|
|
1351
|
+
const installPersona = async (agent) => {
|
|
1352
|
+
const record = options.records.get(agent.id);
|
|
1353
|
+
if (record === void 0) return;
|
|
1354
|
+
if (registered.has(agent.id)) return;
|
|
1355
|
+
let persona;
|
|
1356
|
+
try {
|
|
1357
|
+
persona = personaForRole(options.roles(), record.role);
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
debug(`llm-fallbacks: role persona for agent "${agent.id}" not resolved — contained, the child runs unchanged: ${errorMessage(error)}`);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
if (persona === void 0) return;
|
|
1363
|
+
const text = persona;
|
|
1364
|
+
registered.set(agent.id, owner);
|
|
1365
|
+
try {
|
|
1366
|
+
const fiber = agent.ctx.inject(["systemPrompt"], (scope) => {
|
|
1367
|
+
try {
|
|
1368
|
+
scope.systemPrompt.section({
|
|
1369
|
+
name: PERSONA_PREFIX_SECTION,
|
|
1370
|
+
order: scope.systemPrompt.getSectionOrder("DEPLOYMENT_PERSONA_PREFIX"),
|
|
1371
|
+
text
|
|
1372
|
+
});
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
if (error instanceof Error && error.message === `prompt section "${PERSONA_PREFIX_SECTION}" is already registered in this scope`) {
|
|
1375
|
+
debug(`llm-fallbacks: role persona for agent "${agent.id}" not installed — a section named "${PERSONA_PREFIX_SECTION}" already exists in its scope: ${errorMessage(error)}`);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
contain(agent, record, error);
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
fibers.set(agent.id, fiber);
|
|
1382
|
+
await fiber;
|
|
1383
|
+
} catch (error) {
|
|
1384
|
+
release(agent.id);
|
|
1385
|
+
contain(agent, record, error);
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
const disposers = [];
|
|
1389
|
+
disposers.push(ctx.on("agent/created", async ({ agent }) => {
|
|
1390
|
+
await installPersona(agent);
|
|
1391
|
+
}));
|
|
1392
|
+
disposers.push(ctx.on("agent/disposed", ({ agent }) => {
|
|
1393
|
+
release(agent.id);
|
|
1394
|
+
registered.delete(agent.id);
|
|
1395
|
+
}));
|
|
1396
|
+
const agents = ctx.get("agents");
|
|
1397
|
+
if (typeof agents?.list === "function") for (const agent of agents.list()) installPersona(agent);
|
|
1398
|
+
const dispose = () => {
|
|
1399
|
+
for (const off of disposers.splice(0)) off();
|
|
1400
|
+
for (const id of [...fibers.keys()]) release(id);
|
|
1401
|
+
for (const [id, holder] of [...registered]) if (holder === owner) registered.delete(id);
|
|
1402
|
+
};
|
|
1403
|
+
ctx.effect(() => dispose, "llm-fallbacks: role persona listener");
|
|
1404
|
+
return { dispose };
|
|
1405
|
+
}
|
|
1406
|
+
//#endregion
|
|
1350
1407
|
//#region .build/host/cooldown.js
|
|
1351
1408
|
var CooldownStore = class {
|
|
1352
1409
|
entries = new Map();
|
|
@@ -3088,7 +3145,8 @@ function apply(ctx, config = defaultFallbacksConfig) {
|
|
|
3088
3145
|
detectLegacyKeys,
|
|
3089
3146
|
declareSeeds: (declarations, options) => seeds.declare(declarations, seedsIo, options?.set === void 0 ? void 0 : { set: options.set }),
|
|
3090
3147
|
getEffectiveRoles: () => seeds.effectiveRoles(seedsIo),
|
|
3091
|
-
revertSeededPersona: (id) => seeds.revert(id, seedsIo)
|
|
3148
|
+
revertSeededPersona: (id) => seeds.revert(id, seedsIo),
|
|
3149
|
+
getAgentRole: (agentId) => subagentSeamOf(ctx)?.records.get(agentId)?.role ?? null
|
|
3092
3150
|
});
|
|
3093
3151
|
serviceOwned = true;
|
|
3094
3152
|
} catch (error) {
|
|
@@ -3151,6 +3209,11 @@ function apply(ctx, config = defaultFallbacksConfig) {
|
|
|
3151
3209
|
debug: (message) => logger.debug(message)
|
|
3152
3210
|
});
|
|
3153
3211
|
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)");
|
|
3212
|
+
installRolePersona(ctx, {
|
|
3213
|
+
records: subagentSeam.records,
|
|
3214
|
+
roles: () => source().roles.list,
|
|
3215
|
+
debug: (message) => logger.debug(message)
|
|
3216
|
+
});
|
|
3154
3217
|
try {
|
|
3155
3218
|
new FallbacksConfigGateway(ctx, bridge, seeds, () => {
|
|
3156
3219
|
try {
|
|
@@ -3345,6 +3408,7 @@ function apply(ctx, config = defaultFallbacksConfig) {
|
|
|
3345
3408
|
const role = await resolveRoleAtDispatch(agent, config.roles.rules, roleIds, {
|
|
3346
3409
|
automatchEnabled: config.roleAutoMatch ?? true,
|
|
3347
3410
|
automatch: (agent) => pickRoleByLlm(ctx, config.roles, agent, { warn: logger.warn }),
|
|
3411
|
+
dispatchRole: subagentSeam.records.get(agent.id)?.role,
|
|
3348
3412
|
warn: logger.warn,
|
|
3349
3413
|
servedRoute: servedRouteFor(agent, config)
|
|
3350
3414
|
});
|
|
@@ -3513,4 +3577,4 @@ function apply(ctx, config = defaultFallbacksConfig) {
|
|
|
3513
3577
|
});
|
|
3514
3578
|
}
|
|
3515
3579
|
//#endregion
|
|
3516
|
-
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 };
|
|
3580
|
+
export { Config, FallbacksSeedManager, INHERIT_ROLE_ID, ROLE_ID_PATTERN, ROLE_PROJECTION_KEY, SelectorError, annotateCandidates, apply, blockedAttempts, chainHeads, countRetryEvents, createCandidateFilter, defaultFallbacksConfig, detectLegacyKeys, hasWildcardEntry, name, parseSelector, presetRoles, provide, readRoleProjectionValue, resolveCandidate, resolveChain, resolveChainViews, resolveRole, selectCandidates, stateStore, validateFallbacksConfig };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role-persona delivery over `agent/created` (plan dsh-021-subagent-seam-repair
|
|
3
|
+
* Task 1).
|
|
4
|
+
*
|
|
5
|
+
* rc.1 removed `SubagentStartRequest.persona`, so the resolved dispatch role's
|
|
6
|
+
* persona is delivered as a SCOPED system-prompt section on the CHILD's own
|
|
7
|
+
* context — the shipped `file-reference-local` pattern (no request mutation, no
|
|
8
|
+
* provider capability gate, no Agent Preset). The section carries the SAME name
|
|
9
|
+
* and order the global deployment persona uses (`SystemPrompt.section`: a
|
|
10
|
+
* scoped section shadows a global section with the same name), so the child's
|
|
11
|
+
* persona REPLACES the deployment prefix instead of stacking under it — the
|
|
12
|
+
* semantics the removed request slot had.
|
|
13
|
+
*
|
|
14
|
+
* Timing: `agent/created` is a serial event awaited before the child's first
|
|
15
|
+
* prompt assembly, and this listener AWAITS the injected section fiber: cordis
|
|
16
|
+
* activates a child fiber on a later tick, so awaiting it is what makes the
|
|
17
|
+
* persona observable by the time the dispatch resolves (the shipped
|
|
18
|
+
* `file-reference-local` shape). The seam's `agent/pre-step` notice emitter
|
|
19
|
+
* therefore always observes a child whose persona section is already
|
|
20
|
+
* registered; both surfaces read the SAME ROOT-shared record map, which is what
|
|
21
|
+
* keeps the ` (persona not applied)` suffix truthful.
|
|
22
|
+
*
|
|
23
|
+
* Ownership: the injected fiber belongs to the CHILD's scope, so it does not
|
|
24
|
+
* die with this listener's fiber — each listener holds ITS OWN fibers per agent
|
|
25
|
+
* and releases them on `agent/disposed`, on plugin-fiber teardown, and on
|
|
26
|
+
* explicit `dispose()`. The root-shared claim that dedupes sibling listeners
|
|
27
|
+
* records which listener owns it, so one installer's teardown never releases
|
|
28
|
+
* another's fibers or claims, and a repeated notification never replaces the
|
|
29
|
+
* handle to the fiber that holds the live registration.
|
|
30
|
+
*
|
|
31
|
+
* Containment (the seam's degrade-never-crash invariant, and required here
|
|
32
|
+
* because a throwing serial `agent/created` listener fails agent creation):
|
|
33
|
+
* only an agent whose id hits the shared record map is touched (a miss is a
|
|
34
|
+
* silent no-op), the registry's exact same-name SCOPED section duplicate means
|
|
35
|
+
* a persona section is already on that scope (one attribution-free debug line,
|
|
36
|
+
* no verdict — the registry cannot say who registered it), and every other
|
|
37
|
+
* throw — including a failed fiber activation — is contained to ONE debug line
|
|
38
|
+
* plus the record's sticky `personaNotApplied`.
|
|
39
|
+
*
|
|
40
|
+
* @module dsh-llm-fallbacks/role-persona
|
|
41
|
+
*/
|
|
42
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
43
|
+
import { type SubagentSeamRecord } from './subagents-seam.ts';
|
|
44
|
+
import type { FallbacksRole } from './config.ts';
|
|
45
|
+
/** Options for {@link installRolePersona}. */
|
|
46
|
+
export interface RolePersonaOptions {
|
|
47
|
+
/**
|
|
48
|
+
* The seam's ROOT-shared per-child records — the SAME map the notice
|
|
49
|
+
* emitter reads; the listener writes `personaNotApplied` verdicts back
|
|
50
|
+
* into it (sticky per the existing {@link SubagentSeamRecord} contract).
|
|
51
|
+
*/
|
|
52
|
+
records: Map<string, SubagentSeamRecord>;
|
|
53
|
+
/** Live declared roles (`roles.list`) — the persona source, read per agent/created. */
|
|
54
|
+
roles: () => readonly FallbacksRole[];
|
|
55
|
+
/** Contained debug sink (at most one line per contained degrade). */
|
|
56
|
+
debug?: (message: string) => void;
|
|
57
|
+
}
|
|
58
|
+
/** The installed listener surface (explicit teardown; fiber teardown also stops it). */
|
|
59
|
+
export interface RolePersona {
|
|
60
|
+
dispose(): void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Install the role-persona listener on `ctx`: register the resolved role's
|
|
64
|
+
* persona as a scoped `deployment:persona-prefix` section on every created
|
|
65
|
+
* agent whose id hits the shared record map, plus a `agents.list()` backfill
|
|
66
|
+
* for children that already exist at install time.
|
|
67
|
+
*
|
|
68
|
+
* Returns the installed listener (its `dispose` withdraws this listener's OWN
|
|
69
|
+
* registrations; a fiber teardown stops it as well).
|
|
70
|
+
*/
|
|
71
|
+
export declare function installRolePersona(ctx: Context, options: RolePersonaOptions): RolePersona;
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dispatch-time
|
|
3
|
-
* Task
|
|
2
|
+
* Dispatch-time role resolution (plan fallbacks-role-automatch Task 2; the
|
|
3
|
+
* dispatch-role stage added by plan dsh-148-agent-team-role-delivery Task 1).
|
|
4
4
|
*
|
|
5
|
-
* Precedence (first hit wins):
|
|
5
|
+
* Precedence (first hit wins) across four ordered stages:
|
|
6
6
|
* 1. explicit — `session.header.agentPreset` trimmed matches a declared role
|
|
7
7
|
* id → that role's DECLARED RAW id (via the `roleIds` trimmed-id map, the
|
|
8
8
|
* same canonicalization the rules path uses); the reserved `'inherit'`
|
|
9
9
|
* and undeclared presets are NEVER dispatchable explicit roles and fall
|
|
10
|
-
* through to
|
|
11
|
-
* 2.
|
|
10
|
+
* through to the next stage;
|
|
11
|
+
* 2. dispatch — `opts.dispatchRole`: the role the dispatch seam resolved from
|
|
12
|
+
* the Assignment/teammate prompt (`**Execute as**: <id>`, the single
|
|
13
|
+
* parser in `src/subagents-seam.ts`), canonicalized by the same trim +
|
|
14
|
+
* `roleIds` lookup. An absent, empty, `'inherit'` or no-longer-declared
|
|
15
|
+
* reference falls through silently — a role removed after the dispatch is
|
|
16
|
+
* not an error;
|
|
17
|
+
* 3. rules — the existing `resolveRole` passthrough, unchanged (a declared
|
|
12
18
|
* role wins; no-rule-match / an explicit rule targeting `'inherit'`
|
|
13
19
|
* resolve to `'inherit'`);
|
|
14
|
-
*
|
|
20
|
+
* 4. auto-match — only when stage 3 resolved to `'inherit'` AND
|
|
15
21
|
* `opts.automatchEnabled`: call `opts.automatch` with the agent. The hook
|
|
16
22
|
* (Task 3 supplies the bounded LLM caller) MUST return a declared raw id
|
|
17
23
|
* or `null`; the resolver defensively validates the return against
|
|
@@ -35,10 +41,16 @@ import type { Selector } from './selectors.ts';
|
|
|
35
41
|
export type { FallbacksRoleRule } from './config.ts';
|
|
36
42
|
/** Options for {@link resolveRoleAtDispatch}. */
|
|
37
43
|
export interface ResolveRoleAtDispatchOptions {
|
|
44
|
+
/**
|
|
45
|
+
* Dispatch-declared role: the DECLARED RAW id the seam resolved from the
|
|
46
|
+
* Assignment/teammate prompt (`**Execute as**: <id>`). Validated against the
|
|
47
|
+
* live `roleIds` map; absent, `inherit`, or no-longer-declared → skipped.
|
|
48
|
+
*/
|
|
49
|
+
dispatchRole?: string;
|
|
38
50
|
/**
|
|
39
51
|
* Whether the auto-match stage may run (the `roleAutoMatch` config switch).
|
|
40
|
-
* When `false`, stage
|
|
41
|
-
* rules
|
|
52
|
+
* When `false`, stage 4 never runs and the outcome is identical to the
|
|
53
|
+
* explicit/dispatch/rules stages alone.
|
|
42
54
|
*/
|
|
43
55
|
automatchEnabled: boolean;
|
|
44
56
|
/**
|
|
@@ -64,10 +76,11 @@ export interface ResolveRoleAtDispatchOptions {
|
|
|
64
76
|
};
|
|
65
77
|
}
|
|
66
78
|
/**
|
|
67
|
-
* Resolve the dispatch-time role for an agent across
|
|
68
|
-
* (explicit → rules → auto-match hook). Never throws on its own
|
|
69
|
-
* hook's own rejection is deliberately NOT caught here (the
|
|
70
|
-
* bounds the LLM call so it never throws out of the request
|
|
79
|
+
* Resolve the dispatch-time role for an agent across four ordered stages
|
|
80
|
+
* (explicit → dispatch → rules → auto-match hook). Never throws on its own
|
|
81
|
+
* inputs; the hook's own rejection is deliberately NOT caught here (the
|
|
82
|
+
* Task-3/4 wiring bounds the LLM call so it never throws out of the request
|
|
83
|
+
* path).
|
|
71
84
|
*
|
|
72
85
|
* `roleIds` is the canonical trimmed-id map (`trimmed id → declared raw id`,
|
|
73
86
|
* built in `src/index.ts` apply()): every stage canonicalizes its reference
|
package/dist/roles.d.ts
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
* Task 1).
|
|
4
4
|
*
|
|
5
5
|
* This module is the RULES stage. Dispatch-time role resolution
|
|
6
|
-
* (plan fallbacks-role-automatch
|
|
7
|
-
* `role-resolution.ts` — explicit
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* (plan fallbacks-role-automatch; plan dsh-148 added the dispatch stage) is
|
|
7
|
+
* the four-stage resolver in `role-resolution.ts` — explicit
|
|
8
|
+
* (`session.header.agentPreset`) → dispatch-declared role (the seam record
|
|
9
|
+
* resolved from `**Execute as**`) → this rules stage → an optional auto-match
|
|
10
|
+
* hook; `AgentLike` carries the additive `session.header.agentPreset?`
|
|
11
|
+
* carrier for that explicit stage.
|
|
10
12
|
*
|
|
11
13
|
* Precedence (first hit wins):
|
|
12
14
|
* 1. the first `rules` entry whose specified provider/model patterns match
|