@sema-agent/server 1.220.0 → 1.221.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/main.js CHANGED
@@ -18,7 +18,7 @@ import { withLedgerRecording } from "./plugins/send-file-ledger.js";
18
18
  import { basename, resolve } from "node:path";
19
19
  import { stat as fsStat, readFile as fsReadFile } from "node:fs/promises";
20
20
  import { buildEnvFacts, egressForRemoteExec, ensureScratchpadDir, purgeScratchpadDir, sweepStaleScratchpads } from "./env-facts.js";
21
- import { normalizeSuggestNextPrompts, normalizeResilience, normalizeAttachments, normalizeResumeAtMode, resolveTaskLimits, taskAgentsSpecFragment, retainBackgroundProcessesFromBody } from "./spec-fields.js";
21
+ import { normalizeSuggestNextPrompts, normalizeResilience, normalizeAttachments, normalizeResumeAtMode, resolveTaskLimits, taskAgentsSpecFragment, retainBackgroundProcessesFromBody, toolNameListFromBody } from "./spec-fields.js";
22
22
  import { createBrain, brainSummary } from "./brain.js";
23
23
  import { loadConfig, logConfigDiagnostics } from "./config.js";
24
24
  import { resourceSuspendOptIn } from "./resource-suspend.js";
@@ -69,7 +69,7 @@ import { QuestionCoordinator } from "./question.js";
69
69
  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
- import { createConfigProvider } from "./config-provider.js";
72
+ import { createConfigProvider, raceBootFetch, BOOT_FETCH_DEFERRED } from "./config-provider.js";
73
73
  import { createPrincipalCapsClient, gateExecutionLane, scopedTokenNeedsWorker, applyObserverEnvOptIn } from "./runtime-caps-resolver.js";
74
74
  import { applyRuntimeGovernance, stripDelegationTools } from "./runtime-governance.js";
75
75
  import { parseTaskSettings, applyTaskSettings, coercePermissionMode, withPermissionMode, effectiveThinking } from "./task-settings.js";
@@ -154,10 +154,22 @@ async function main() {
154
154
  logger.info("model_roster_landed", { note: "ready gate open — billable submissions accepted" });
155
155
  }
156
156
  };
157
+ let bootConfigPending;
157
158
  if (configProvider) {
158
159
  const dryRun = cc?.dryRun ?? false;
159
160
  try {
160
- const r = await configProvider.fetchEffective(undefined);
161
+ const bootFetch = configProvider.fetchEffective(undefined);
162
+ const raced = await raceBootFetch(bootFetch, config.configBootFetchBudgetMs);
163
+ if (raced === BOOT_FETCH_DEFERRED) {
164
+ bootConfigPending = bootFetch;
165
+ bootFetch.catch(() => { });
166
+ logger.warn("config_boot_fetch_deferred", {
167
+ source: configProvider.kind,
168
+ budgetMs: config.configBootFetchBudgetMs,
169
+ note: "center slow/unreachable — serving on the env fallback now; the pull continues in the background and hot-applies on arrival (roster gate opens then). Raise CONFIG_BOOT_FETCH_BUDGET_MS to block boot instead.",
170
+ });
171
+ }
172
+ const r = raced === BOOT_FETCH_DEFERRED ? null : raced;
161
173
  if (r) {
162
174
  const bootClean = (r.domainErrors ?? []).length === 0;
163
175
  if (bootClean)
@@ -1042,67 +1054,68 @@ async function main() {
1042
1054
  if (configProvider) {
1043
1055
  const ccRef = config.configCenter;
1044
1056
  let refreshInFlight = false;
1045
- const ccTimer = setInterval(() => {
1046
- void (async () => {
1047
- if (refreshInFlight)
1048
- return;
1049
- refreshInFlight = true;
1050
- try {
1051
- const r = await configProvider.fetchEffective(ccEtag);
1052
- if (r) {
1053
- const badDomains = [...new Set((r.domainErrors ?? []).map((de) => de.domain))].sort().join(",");
1054
- if (badDomains) {
1055
- if (lastRejectedCandidate?.version !== r.effective.version || lastRejectedCandidate.domains !== badDomains) {
1056
- lastRejectedCandidate = { version: r.effective.version, domains: badDomains, at: Date.now() };
1057
- logger.warn("config_candidate_rejected", {
1058
- version: r.effective.version,
1059
- domains: badDomains,
1060
- errors: (r.domainErrors ?? []).map((de) => ({ domain: de.domain, error: de.error })),
1061
- lkgVersion: latestEffective?.version ?? "(env)",
1062
- note: "candidate rejected whole (§9.5-5) — live config keeps the last-known-good; fix the named files to unblock",
1063
- });
1064
- }
1065
- return;
1057
+ const refreshTick = async () => {
1058
+ if (refreshInFlight)
1059
+ return;
1060
+ refreshInFlight = true;
1061
+ try {
1062
+ const r = await configProvider.fetchEffective(ccEtag);
1063
+ if (r) {
1064
+ const badDomains = [...new Set((r.domainErrors ?? []).map((de) => de.domain))].sort().join(",");
1065
+ if (badDomains) {
1066
+ if (lastRejectedCandidate?.version !== r.effective.version || lastRejectedCandidate.domains !== badDomains) {
1067
+ lastRejectedCandidate = { version: r.effective.version, domains: badDomains, at: Date.now() };
1068
+ logger.warn("config_candidate_rejected", {
1069
+ version: r.effective.version,
1070
+ domains: badDomains,
1071
+ errors: (r.domainErrors ?? []).map((de) => ({ domain: de.domain, error: de.error })),
1072
+ lkgVersion: latestEffective?.version ?? "(env)",
1073
+ note: "candidate rejected whole (§9.5-5) — live config keeps the last-known-good; fix the named files to unblock",
1074
+ });
1066
1075
  }
1067
- lastRejectedCandidate = undefined;
1068
- if (ccRef?.dryRun) {
1069
- logEffectiveDiff(config, r.effective, logger);
1070
- ccEtag = r.etag;
1076
+ return;
1077
+ }
1078
+ lastRejectedCandidate = undefined;
1079
+ if (ccRef?.dryRun) {
1080
+ logEffectiveDiff(config, r.effective, logger);
1081
+ ccEtag = r.etag;
1082
+ }
1083
+ else {
1084
+ applyEffective(config, r.effective, logger, { teamsOnly: true, sealedKeys });
1085
+ markRosterLanded(r.effective);
1086
+ mutateInPlace(pricing, buildPricing(config.models));
1087
+ keyResolver = buildKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
1088
+ const reasons = restartReasons(effective, r.effective);
1089
+ if (reasons.length === 0) {
1090
+ pendingRestart = undefined;
1091
+ }
1092
+ else if (!pendingRestart || pendingRestart.reasons.join(",") !== reasons.join(",")) {
1093
+ pendingRestart = { restartRequired: true, reasons, version: r.effective.version, since: Date.now() };
1071
1094
  }
1072
1095
  else {
1073
- applyEffective(config, r.effective, logger, { teamsOnly: true, sealedKeys });
1074
- markRosterLanded(r.effective);
1075
- mutateInPlace(pricing, buildPricing(config.models));
1076
- keyResolver = buildKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
1077
- const reasons = restartReasons(effective, r.effective);
1078
- if (reasons.length === 0) {
1079
- pendingRestart = undefined;
1080
- }
1081
- else if (!pendingRestart || pendingRestart.reasons.join(",") !== reasons.join(",")) {
1082
- pendingRestart = { restartRequired: true, reasons, version: r.effective.version, since: Date.now() };
1083
- }
1084
- else {
1085
- pendingRestart = { ...pendingRestart, version: r.effective.version };
1086
- }
1087
- logger.info("sema_registry_refreshed", {
1088
- version: r.effective.version,
1089
- note: "teams + runtime governance + models/roles/roster hot-applied; skills/mcp/runtime-gates/scenarios are restart-to-apply",
1090
- ...(pendingRestart ? { restartRequired: true, restartReasons: pendingRestart.reasons } : {}),
1091
- });
1092
- latestEffective = r.effective;
1093
- ccEtag = r.etag;
1096
+ pendingRestart = { ...pendingRestart, version: r.effective.version };
1094
1097
  }
1098
+ logger.info("sema_registry_refreshed", {
1099
+ version: r.effective.version,
1100
+ note: "teams + runtime governance + models/roles/roster hot-applied; skills/mcp/runtime-gates/scenarios are restart-to-apply",
1101
+ ...(pendingRestart ? { restartRequired: true, restartReasons: pendingRestart.reasons } : {}),
1102
+ });
1103
+ latestEffective = r.effective;
1104
+ ccEtag = r.etag;
1095
1105
  }
1096
1106
  }
1097
- catch (err) {
1098
- logger.warn("sema_registry_refresh_failed", { err: String(err), note: "etag NOT advanced — the same candidate is re-fetched and re-applied next poll" });
1099
- }
1100
- finally {
1101
- refreshInFlight = false;
1102
- }
1103
- })();
1104
- }, 60_000);
1107
+ }
1108
+ catch (err) {
1109
+ logger.warn("sema_registry_refresh_failed", { err: String(err), note: "etag NOT advanced — the same candidate is re-fetched and re-applied next poll" });
1110
+ }
1111
+ finally {
1112
+ refreshInFlight = false;
1113
+ }
1114
+ };
1115
+ const ccTimer = setInterval(() => void refreshTick(), 60_000);
1105
1116
  ccTimer.unref?.();
1117
+ if (bootConfigPending)
1118
+ void bootConfigPending.then(() => refreshTick(), () => { });
1106
1119
  }
1107
1120
  let keyResolver = buildKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
1108
1121
  const mysqlPool = backend?.mysqlPool();
@@ -1397,6 +1410,8 @@ async function main() {
1397
1410
  ...(retainBackgroundProcessesFromBody(body.retainBackgroundProcesses, config.requirePrincipal, (event, fields) => logger.warn(event, { ...fields, sessionId: auth?.sessionId ?? null })) === true
1398
1411
  ? { retainBackgroundProcesses: true }
1399
1412
  : {}),
1413
+ ...(() => { const v = toolNameListFromBody(body.excludeTools); return v ? { excludeTools: v } : {}; })(),
1414
+ ...(() => { const v = toolNameListFromBody(body.deferTools); return v ? { deferTools: v } : {}; })(),
1400
1415
  ...(typeof body.interactiveTools === "boolean"
1401
1416
  ? { interactiveTools: body.interactiveTools }
1402
1417
  : {}),