@sema-agent/server 1.221.0 → 1.223.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/budget.d.ts +2 -0
- package/dist/budget.d.ts.map +1 -1
- package/dist/budget.js +1 -1
- package/dist/budget.js.map +1 -1
- package/dist/capabilities/center-prompts.d.ts +16 -0
- package/dist/capabilities/center-prompts.d.ts.map +1 -0
- package/dist/capabilities/center-prompts.js +64 -0
- package/dist/capabilities/center-prompts.js.map +1 -0
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.d.ts.map +1 -1
- package/dist/capabilities/scenarios.js +1 -10
- package/dist/capabilities/scenarios.js.map +1 -1
- package/dist/config-lkg.d.ts +17 -0
- package/dist/config-lkg.d.ts.map +1 -0
- package/dist/config-lkg.js +72 -0
- package/dist/config-lkg.js.map +1 -0
- package/dist/config-provider.d.ts.map +1 -1
- package/dist/config-provider.js +0 -1
- package/dist/config-provider.js.map +1 -1
- package/dist/main.js +157 -9
- package/dist/main.js.map +1 -1
- package/dist/sema-registry.d.ts +2 -3
- package/dist/sema-registry.d.ts.map +1 -1
- package/dist/sema-registry.js +40 -4
- package/dist/sema-registry.js.map +1 -1
- package/package.json +3 -3
package/dist/main.js
CHANGED
|
@@ -70,6 +70,8 @@ import { ToolApprovalCoordinator } from "./tool-approval.js";
|
|
|
70
70
|
import { applyEffective, mutateInPlace, logEffectiveDiff, applyCenterSkills, resolveMcpServers, mcpForScenario, restartReasons } from "./sema-registry.js";
|
|
71
71
|
import { ensureSealedKeyStore, reportExecutionPublicKey } from "./sealed-key.js";
|
|
72
72
|
import { createConfigProvider, raceBootFetch, BOOT_FETCH_DEFERRED } from "./config-provider.js";
|
|
73
|
+
import { validateCenterPrompts, centerPromptProvider } from "./capabilities/center-prompts.js";
|
|
74
|
+
import { defaultLkgPath, defaultSkillCacheDir, saveLkg, loadLkg } from "./config-lkg.js";
|
|
73
75
|
import { createPrincipalCapsClient, gateExecutionLane, scopedTokenNeedsWorker, applyObserverEnvOptIn } from "./runtime-caps-resolver.js";
|
|
74
76
|
import { applyRuntimeGovernance, stripDelegationTools } from "./runtime-governance.js";
|
|
75
77
|
import { parseTaskSettings, applyTaskSettings, coercePermissionMode, withPermissionMode, effectiveThinking } from "./task-settings.js";
|
|
@@ -154,24 +156,100 @@ async function main() {
|
|
|
154
156
|
logger.info("model_roster_landed", { note: "ready gate open — billable submissions accepted" });
|
|
155
157
|
}
|
|
156
158
|
};
|
|
159
|
+
let centerPrompts;
|
|
160
|
+
const adoptCenterPrompts = (eff, phase) => {
|
|
161
|
+
const raw = eff?.prompts;
|
|
162
|
+
if (raw === undefined) {
|
|
163
|
+
if (centerPrompts)
|
|
164
|
+
logger.info("center_prompts_cleared", { phase, note: "effective carries no prompts key (publish gate off) — built-in providers resume" });
|
|
165
|
+
centerPrompts = undefined;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const v = validateCenterPrompts(raw);
|
|
169
|
+
if (!v.ok) {
|
|
170
|
+
logger.warn("center_prompts_invalid", { phase, error: v.error, kept: centerPrompts?.packId ?? "(builtin)", note: "malformed prompts face rejected (core assemble would THROW per task) — keeping the previous pack/built-ins; fix the center payload" });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (centerPrompts?.contentDigest !== v.value.contentDigest) {
|
|
174
|
+
logger.info("center_prompts_adopted", { phase, packId: v.value.packId, contentDigest: v.value.contentDigest, sections: v.value.sections.length, scenarioOverrides: Object.keys(v.value.scenarioOverrides ?? {}).length, note: "new tasks assemble the new pack (epoch re-pins at the next task boundary); in-flight sessions stay pinned" });
|
|
175
|
+
}
|
|
176
|
+
centerPrompts = v.value;
|
|
177
|
+
};
|
|
157
178
|
let bootConfigPending;
|
|
179
|
+
const lkgEnabled = configProvider?.kind === "remote" && process.env.CONFIG_LKG_DISABLED !== "true";
|
|
180
|
+
const lkgPath = process.env.CONFIG_LKG_PATH ?? defaultLkgPath(cc?.worker);
|
|
181
|
+
let lkgBooted = false;
|
|
182
|
+
let bootLkgCandidate;
|
|
183
|
+
const persistLkgDurable = async (effectiveObj, etag) => {
|
|
184
|
+
if (!lkgEnabled)
|
|
185
|
+
return;
|
|
186
|
+
try {
|
|
187
|
+
if (effectiveObj.skills && config.configCenter) {
|
|
188
|
+
await applyCenterSkills([], effectiveObj.skills, config.configCenter.baseUrl, config.configCenter.token, logger, undefined, defaultSkillCacheDir());
|
|
189
|
+
}
|
|
190
|
+
await saveLkg(lkgPath, cc?.worker, effectiveObj, etag);
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
logger.warn("config_lkg_save_failed", { path: lkgPath, err: String(err), note: "durable persist failed — restart signal proceeds anyway (a dead disk + dead center double-fault can re-loop; fix the disk)" });
|
|
194
|
+
}
|
|
195
|
+
};
|
|
158
196
|
if (configProvider) {
|
|
159
197
|
const dryRun = cc?.dryRun ?? false;
|
|
198
|
+
const bootFromLkg = async (why) => {
|
|
199
|
+
if (!lkgEnabled || dryRun)
|
|
200
|
+
return null;
|
|
201
|
+
const read = await loadLkg(lkgPath, cc?.worker);
|
|
202
|
+
if (!read.lkg) {
|
|
203
|
+
if (read.reason !== "absent")
|
|
204
|
+
logger.warn("config_lkg_unusable", { path: lkgPath, reason: read.reason, note: "ignoring the on-disk LKG — env fallback serves; the cache rewrites on the next clean candidate" });
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
logger.info("config_loaded_lkg", { path: lkgPath, savedAt: read.lkg.savedAt, why, note: "booting from the persisted last-known-good (all restart-to-apply faces included) — the live pull continues in the background and reconciles on arrival" });
|
|
208
|
+
return { effective: read.lkg.effective, ...(read.lkg.etag !== undefined ? { etag: read.lkg.etag } : {}) };
|
|
209
|
+
};
|
|
210
|
+
const bootApplyLkgInline = (lkgR) => {
|
|
211
|
+
applyEffective(config, lkgR.effective, logger, { sealedKeys });
|
|
212
|
+
adoptCenterPrompts(lkgR.effective, "boot-lkg");
|
|
213
|
+
markRosterLanded(lkgR.effective);
|
|
214
|
+
effective = lkgR.effective;
|
|
215
|
+
latestEffective = lkgR.effective;
|
|
216
|
+
if (lkgR.etag !== undefined)
|
|
217
|
+
ccEtag = lkgR.etag;
|
|
218
|
+
const cfgModels = (lkgR.effective.models?.models ?? []).filter((m) => m.enabled !== false).length;
|
|
219
|
+
logger.info("config_loaded", { source: "lkg", version: lkgR.effective.version, worker: cc?.worker ?? "(global)", models: cfgModels });
|
|
220
|
+
};
|
|
160
221
|
try {
|
|
161
222
|
const bootFetch = configProvider.fetchEffective(undefined);
|
|
162
223
|
const raced = await raceBootFetch(bootFetch, config.configBootFetchBudgetMs);
|
|
224
|
+
let lkgFallback = null;
|
|
163
225
|
if (raced === BOOT_FETCH_DEFERRED) {
|
|
164
226
|
bootConfigPending = bootFetch;
|
|
165
227
|
bootFetch.catch(() => { });
|
|
228
|
+
lkgFallback = await bootFromLkg("boot fetch deferred (budget exceeded)");
|
|
229
|
+
lkgBooted = lkgFallback !== null;
|
|
166
230
|
logger.warn("config_boot_fetch_deferred", {
|
|
167
231
|
source: configProvider.kind,
|
|
168
232
|
budgetMs: config.configBootFetchBudgetMs,
|
|
169
|
-
|
|
233
|
+
lkg: lkgBooted,
|
|
234
|
+
note: lkgBooted
|
|
235
|
+
? "center slow — booting from the persisted LKG; the pull continues in the background and reconciles via the refresh path on arrival"
|
|
236
|
+
: "center slow/unreachable and no usable LKG — serving on the env fallback; the pull continues in the background (late-boot adoption on arrival). Raise CONFIG_BOOT_FETCH_BUDGET_MS to block boot instead.",
|
|
170
237
|
});
|
|
171
238
|
}
|
|
239
|
+
if (raced === BOOT_FETCH_DEFERRED && lkgFallback) {
|
|
240
|
+
try {
|
|
241
|
+
bootApplyLkgInline(lkgFallback);
|
|
242
|
+
}
|
|
243
|
+
catch (applyErr) {
|
|
244
|
+
lkgBooted = false;
|
|
245
|
+
logger.warn("config_lkg_apply_failed", { err: String(applyErr), note: "persisted LKG failed to apply — env fallback serves; the cache rewrites on the next clean candidate" });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
172
248
|
const r = raced === BOOT_FETCH_DEFERRED ? null : raced;
|
|
173
249
|
if (r) {
|
|
174
|
-
const
|
|
250
|
+
const bootPromptsRaw = r.effective.prompts;
|
|
251
|
+
const bootPromptsOk = bootPromptsRaw === undefined || validateCenterPrompts(bootPromptsRaw).ok;
|
|
252
|
+
const bootClean = (r.domainErrors ?? []).length === 0 && bootPromptsOk;
|
|
175
253
|
if (bootClean)
|
|
176
254
|
ccEtag = r.etag;
|
|
177
255
|
for (const de of r.domainErrors ?? [])
|
|
@@ -181,10 +259,13 @@ async function main() {
|
|
|
181
259
|
}
|
|
182
260
|
else {
|
|
183
261
|
applyEffective(config, r.effective, logger, { sealedKeys });
|
|
262
|
+
adoptCenterPrompts(r.effective, "boot");
|
|
184
263
|
markRosterLanded(r.effective);
|
|
185
264
|
effective = r.effective;
|
|
186
265
|
if (bootClean)
|
|
187
266
|
latestEffective = r.effective;
|
|
267
|
+
if (bootClean && !lkgBooted)
|
|
268
|
+
bootLkgCandidate = { effective: r.effective, etag: r.etag };
|
|
188
269
|
const cfgModels = (r.effective.models?.models ?? []).filter((m) => m.enabled !== false).length;
|
|
189
270
|
const cfgDir = configProvider.kind === "local" ? { dir: configProvider.root } : {};
|
|
190
271
|
logger.info("config_loaded", { source: configProvider.kind, version: r.effective.version, worker: cc?.worker ?? "(global)", models: cfgModels, ...cfgDir });
|
|
@@ -195,6 +276,17 @@ async function main() {
|
|
|
195
276
|
}
|
|
196
277
|
catch (err) {
|
|
197
278
|
logger.warn("config_source_unreachable_using_env", { source: configProvider.kind, err: String(err) });
|
|
279
|
+
const lkgFallback = await bootFromLkg(`boot fetch failed: ${String(err).slice(0, 120)}`);
|
|
280
|
+
if (lkgFallback) {
|
|
281
|
+
lkgBooted = true;
|
|
282
|
+
try {
|
|
283
|
+
bootApplyLkgInline(lkgFallback);
|
|
284
|
+
}
|
|
285
|
+
catch (applyErr) {
|
|
286
|
+
lkgBooted = false;
|
|
287
|
+
logger.warn("config_lkg_apply_failed", { err: String(applyErr), note: "persisted LKG failed to apply — env fallback serves" });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
198
290
|
}
|
|
199
291
|
}
|
|
200
292
|
if (!modelReadyState.ready)
|
|
@@ -948,7 +1040,10 @@ async function main() {
|
|
|
948
1040
|
: undefined;
|
|
949
1041
|
let skills = loadSkills(config.skillsDir);
|
|
950
1042
|
if (effective?.skills && config.configCenter) {
|
|
951
|
-
skills = await applyCenterSkills(skills, effective.skills, config.configCenter.baseUrl, config.configCenter.token, logger);
|
|
1043
|
+
skills = await applyCenterSkills(skills, effective.skills, config.configCenter.baseUrl, config.configCenter.token, logger, undefined, process.env.CONFIG_LKG_DISABLED !== "true" ? defaultSkillCacheDir() : undefined);
|
|
1044
|
+
}
|
|
1045
|
+
if (bootLkgCandidate && lkgEnabled) {
|
|
1046
|
+
await saveLkg(lkgPath, cc?.worker, bootLkgCandidate.effective, bootLkgCandidate.etag).catch((err) => logger.warn("config_lkg_save_failed", { path: lkgPath, err: String(err), note: "boot LKG persist failed — next clean candidate rewrites" }));
|
|
952
1047
|
}
|
|
953
1048
|
if (effective?.mcp)
|
|
954
1049
|
config.mcpServers = resolveMcpServers(effective.mcp, logger);
|
|
@@ -1054,21 +1149,30 @@ async function main() {
|
|
|
1054
1149
|
if (configProvider) {
|
|
1055
1150
|
const ccRef = config.configCenter;
|
|
1056
1151
|
let refreshInFlight = false;
|
|
1057
|
-
const refreshTick = async () => {
|
|
1152
|
+
const refreshTick = async (prefetched) => {
|
|
1058
1153
|
if (refreshInFlight)
|
|
1059
1154
|
return;
|
|
1060
1155
|
refreshInFlight = true;
|
|
1061
1156
|
try {
|
|
1062
|
-
const r = await configProvider.fetchEffective(ccEtag);
|
|
1157
|
+
const r = prefetched !== undefined ? prefetched : await configProvider.fetchEffective(ccEtag);
|
|
1063
1158
|
if (r) {
|
|
1064
|
-
const
|
|
1159
|
+
const promptsRaw = r.effective.prompts;
|
|
1160
|
+
const promptsGate = promptsRaw !== undefined ? validateCenterPrompts(promptsRaw) : { ok: true };
|
|
1161
|
+
const badDomains = [
|
|
1162
|
+
...new Set([...(r.domainErrors ?? []).map((de) => de.domain), ...(promptsGate.ok ? [] : ["prompts"])]),
|
|
1163
|
+
]
|
|
1164
|
+
.sort()
|
|
1165
|
+
.join(",");
|
|
1065
1166
|
if (badDomains) {
|
|
1066
1167
|
if (lastRejectedCandidate?.version !== r.effective.version || lastRejectedCandidate.domains !== badDomains) {
|
|
1067
1168
|
lastRejectedCandidate = { version: r.effective.version, domains: badDomains, at: Date.now() };
|
|
1068
1169
|
logger.warn("config_candidate_rejected", {
|
|
1069
1170
|
version: r.effective.version,
|
|
1070
1171
|
domains: badDomains,
|
|
1071
|
-
errors:
|
|
1172
|
+
errors: [
|
|
1173
|
+
...(r.domainErrors ?? []).map((de) => ({ domain: de.domain, error: de.error })),
|
|
1174
|
+
...(promptsGate.ok ? [] : [{ domain: "prompts", error: promptsGate.error }]),
|
|
1175
|
+
],
|
|
1072
1176
|
lkgVersion: latestEffective?.version ?? "(env)",
|
|
1073
1177
|
note: "candidate rejected whole (§9.5-5) — live config keeps the last-known-good; fix the named files to unblock",
|
|
1074
1178
|
});
|
|
@@ -1085,6 +1189,7 @@ async function main() {
|
|
|
1085
1189
|
markRosterLanded(r.effective);
|
|
1086
1190
|
mutateInPlace(pricing, buildPricing(config.models));
|
|
1087
1191
|
keyResolver = buildKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
|
|
1192
|
+
await persistLkgDurable(r.effective, r.etag);
|
|
1088
1193
|
const reasons = restartReasons(effective, r.effective);
|
|
1089
1194
|
if (reasons.length === 0) {
|
|
1090
1195
|
pendingRestart = undefined;
|
|
@@ -1114,8 +1219,51 @@ async function main() {
|
|
|
1114
1219
|
};
|
|
1115
1220
|
const ccTimer = setInterval(() => void refreshTick(), 60_000);
|
|
1116
1221
|
ccTimer.unref?.();
|
|
1222
|
+
const deferredBootApply = async (r) => {
|
|
1223
|
+
if (!r)
|
|
1224
|
+
return;
|
|
1225
|
+
if (refreshInFlight)
|
|
1226
|
+
return;
|
|
1227
|
+
refreshInFlight = true;
|
|
1228
|
+
try {
|
|
1229
|
+
await deferredBootApplyInner(r);
|
|
1230
|
+
}
|
|
1231
|
+
finally {
|
|
1232
|
+
refreshInFlight = false;
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
const deferredBootApplyInner = async (r) => {
|
|
1236
|
+
const promptsRaw = r.effective.prompts;
|
|
1237
|
+
const promptsGate = promptsRaw !== undefined ? validateCenterPrompts(promptsRaw) : { ok: true };
|
|
1238
|
+
if ((r.domainErrors ?? []).length > 0 || !promptsGate.ok) {
|
|
1239
|
+
logger.warn("config_boot_deferred_candidate_rejected", {
|
|
1240
|
+
version: r.effective.version,
|
|
1241
|
+
errors: [...(r.domainErrors ?? []).map((de) => ({ domain: de.domain, error: de.error })), ...(promptsGate.ok ? [] : [{ domain: "prompts", error: promptsGate.error }])],
|
|
1242
|
+
note: "late boot pull carried an invalid candidate — env fallback keeps serving; the refresh cadence re-judges the same tree (etag not advanced)",
|
|
1243
|
+
});
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
if (ccRef?.dryRun) {
|
|
1247
|
+
logEffectiveDiff(config, r.effective, logger);
|
|
1248
|
+
ccEtag = r.etag;
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
applyEffective(config, r.effective, logger, { teamsOnly: true, sealedKeys });
|
|
1252
|
+
adoptCenterPrompts(r.effective, "boot-deferred");
|
|
1253
|
+
markRosterLanded(r.effective);
|
|
1254
|
+
mutateInPlace(pricing, buildPricing(config.models));
|
|
1255
|
+
keyResolver = buildKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
|
|
1256
|
+
effective = r.effective;
|
|
1257
|
+
latestEffective = r.effective;
|
|
1258
|
+
ccEtag = r.etag;
|
|
1259
|
+
await persistLkgDurable(r.effective, r.etag);
|
|
1260
|
+
const reasons = restartReasons(undefined, r.effective).filter((sl) => sl !== "prompts");
|
|
1261
|
+
if (reasons.length > 0)
|
|
1262
|
+
pendingRestart = { restartRequired: true, reasons, version: r.effective.version, since: Date.now() };
|
|
1263
|
+
logger.info("config_loaded_deferred", { source: configProvider.kind, version: r.effective.version, models: (r.effective.models?.models ?? []).filter((m) => m.enabled !== false).length, ...(reasons.length > 0 ? { restartRequired: true, restartReasons: reasons } : {}) });
|
|
1264
|
+
};
|
|
1117
1265
|
if (bootConfigPending)
|
|
1118
|
-
void bootConfigPending.then(() => refreshTick(), () => { });
|
|
1266
|
+
void bootConfigPending.then((r) => (lkgBooted ? refreshTick(r) : deferredBootApply(r)), () => { });
|
|
1119
1267
|
}
|
|
1120
1268
|
let keyResolver = buildKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
|
|
1121
1269
|
const mysqlPool = backend?.mysqlPool();
|
|
@@ -1447,7 +1595,7 @@ async function main() {
|
|
|
1447
1595
|
})(cap.tools),
|
|
1448
1596
|
skills: mergeUserSkills(cap.skills, body.skills, logger),
|
|
1449
1597
|
mcp: resolveRequestMcp(mcpForScenario(config.mcpServers, scenarioName), body.mcpServers, config, logger),
|
|
1450
|
-
promptProvider: cap.promptProvider,
|
|
1598
|
+
promptProvider: centerPrompts ? centerPromptProvider(centerPrompts, scenarioName) : cap.promptProvider,
|
|
1451
1599
|
toolPolicy: durableEnabled
|
|
1452
1600
|
? combinePolicies(createDurableQuestionPolicy(), createDurableAskPolicy({
|
|
1453
1601
|
requireApproval: config.approvalRequire, deny: config.approvalDeny, autoBudget: config.approvalAutoBudget, neverAuto: config.approvalNeverAuto,
|