@sema-agent/server 1.230.0 → 1.232.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
@@ -1,8 +1,9 @@
1
1
  import { readFileSync, existsSync, realpathSync } from "node:fs";
2
+ import { createHash } from "node:crypto";
2
3
  import { homedir } from "node:os";
3
4
  import { join } from "node:path";
4
5
  import { loadRemoteExec } from "@sema-agent/registry-core/node";
5
- import { Runner, TtlSessionStore, uuidv7, FileRosterStore, defaultTaskRegistry, combinePolicies, createDurableQuestionPolicy, createAllowDenyPolicy, tightenTaskSpec, isThinkingLevel, QUESTION_AWAITS_RESUME, FileWorkflowRunStore, InMemoryWorkflowRunStore, workflowsCapability, NodeLspManager, createFileWorkflowScriptStore, expandTiers, NodeExecutionEnv } from "@sema-agent/core";
6
+ import { Runner, TtlSessionStore, uuidv7, FileRosterStore, defaultTaskRegistry, combinePolicies, createDurableQuestionPolicy, createAllowDenyPolicy, tightenTaskSpec, isThinkingLevel, QUESTION_AWAITS_RESUME, FileWorkflowRunStore, InMemoryWorkflowRunStore, workflowsCapability, NodeLspManager, createFileWorkflowScriptStore, expandTiers, NodeExecutionEnv, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore } from "@sema-agent/core";
6
7
  import { PgMemoryEngineBackend, ensurePgMemoryEngineSchema } from "./plugins/memory-engine-pg.js";
7
8
  import { TiDBMemoryEngineBackend, ensureTiDBMemoryEngineSchema } from "./plugins/memory-engine-tidb.js";
8
9
  import { PgMemoryHistoryStore, ensurePgMemoryHistorySchema, PgMemorySyncStore, ensurePgMemorySyncSchema } from "./plugins/memory-sync-store-pg.js";
@@ -71,7 +72,7 @@ import { ToolApprovalCoordinator } from "./tool-approval.js";
71
72
  import { applyEffective, mutateInPlace, logEffectiveDiff, applyCenterSkills, resolveMcpServers, mcpForScenario, restartReasons } from "./sema-registry.js";
72
73
  import { ensureSealedKeyStore, reportExecutionPublicKey } from "./sealed-key.js";
73
74
  import { createConfigProvider, raceBootFetch, BOOT_FETCH_DEFERRED } from "./config-provider.js";
74
- import { validateCenterPrompts, centerPromptProvider } from "./capabilities/center-prompts.js";
75
+ import { validatePromptsDomain, centerPromptProvider, applyCatalogToSource, CORE_ENGINE_VERSION } from "./capabilities/center-prompts.js";
75
76
  import { defaultLkgPath, defaultSkillCacheDir, saveLkg, loadLkg } from "./config-lkg.js";
76
77
  import { TiDBRosterStore, PgRosterStore, ensureTiDBRosterSchema, ensurePgRosterSchema } from "./plugins/roster-store-sql.js";
77
78
  import { createPrincipalCapsClient, gateExecutionLane, scopedTokenNeedsWorker, applyObserverEnvOptIn } from "./runtime-caps-resolver.js";
@@ -159,23 +160,78 @@ async function main() {
159
160
  }
160
161
  };
161
162
  let centerPrompts;
162
- const adoptCenterPrompts = (eff, phase) => {
163
+ let pendingCatalogFaces;
164
+ const retryPendingCatalog = async (phase) => {
165
+ if (!pendingCatalogFaces)
166
+ return;
167
+ const { target } = pendingCatalogFaces;
168
+ if (await applyCatalogToSource(promptSource, target, centerPrompts, logger, phase)) {
169
+ centerPrompts = target;
170
+ pendingCatalogFaces = undefined;
171
+ }
172
+ };
173
+ const promptEpochDir = join(config.localDataRoot ?? localRoot, "prompt-epoch");
174
+ const promptStateDir = join(promptEpochDir, "state", cc?.worker === undefined ? "global" : `worker-${createHash("sha256").update(cc.worker).digest("hex").slice(0, 16)}`);
175
+ const promptSource = (() => {
176
+ try {
177
+ return new CenterPromptSource({
178
+ store: new FilePromptArtifactStore(promptEpochDir),
179
+ stateStore: new FilePromptSourceStateStore(promptStateDir),
180
+ engineVersion: CORE_ENGINE_VERSION,
181
+ });
182
+ }
183
+ catch (e) {
184
+ logger.warn("center_prompts_store_unwritable", { dir: promptEpochDir, err: String(e), note: "file-backed prompt-epoch store unavailable — memory fallback (catalog candidate not restart-durable); built-ins/declaration axis unaffected" });
185
+ return new CenterPromptSource({ store: new MemoryPromptArtifactStore(), stateStore: new MemoryPromptSourceStateStore(), engineVersion: CORE_ENGINE_VERSION });
186
+ }
187
+ })();
188
+ if (configProvider && !cc?.dryRun) {
189
+ try {
190
+ await promptSource.restore();
191
+ }
192
+ catch (e) {
193
+ logger.warn("center_prompts_catalog_restore_failed", { err: String(e), note: "disk LKG unavailable — catalog candidate absent until the next adopt; declaration axis and built-ins unaffected" });
194
+ }
195
+ }
196
+ const adoptCenterPrompts = async (eff, phase) => {
163
197
  const raw = eff?.prompts;
164
198
  if (raw === undefined) {
165
199
  if (centerPrompts)
166
200
  logger.info("center_prompts_cleared", { phase, note: "effective carries no prompts key (publish gate off) — built-in providers resume" });
167
- centerPrompts = undefined;
201
+ const cleared = await applyCatalogToSource(promptSource, undefined, centerPrompts, logger, phase);
202
+ if (cleared) {
203
+ centerPrompts = undefined;
204
+ pendingCatalogFaces = undefined;
205
+ }
206
+ else {
207
+ pendingCatalogFaces = { target: undefined };
208
+ }
168
209
  return;
169
210
  }
170
- const v = validateCenterPrompts(raw);
211
+ const v = validatePromptsDomain(raw);
171
212
  if (!v.ok) {
172
- 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" });
213
+ logger.warn("center_prompts_invalid", { phase, error: v.error, kept: centerPrompts?.declarations?.packId ?? (centerPrompts?.catalog ? "(catalog)" : "(builtin)"), note: "malformed prompts face rejected (core assemble would THROW per task) — keeping the previous pack/built-ins; fix the center payload" });
173
214
  return;
174
215
  }
175
- if (centerPrompts?.contentDigest !== v.value.contentDigest) {
176
- 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" });
216
+ if (centerPrompts?.identity !== v.value.identity) {
217
+ logger.info("center_prompts_adopted", {
218
+ phase,
219
+ axes: v.value.axes,
220
+ ...(v.value.declarations
221
+ ? { packId: v.value.declarations.packId, contentDigest: v.value.declarations.contentDigest, sections: v.value.declarations.sections.length, scenarioOverrides: Object.keys(v.value.declarations.scenarioOverrides ?? {}).length }
222
+ : {}),
223
+ ...(v.value.catalog ? { artifactDigest: v.value.catalog.artifact.artifactDigest, catalogDigest: v.value.catalog.artifact.payload.catalogDigest, sourceRevision: v.value.catalog.sourceRevision } : {}),
224
+ note: "new tasks assemble the new pack (epoch re-pins at the next task boundary); in-flight sessions stay pinned",
225
+ });
226
+ }
227
+ const transitioned = await applyCatalogToSource(promptSource, v.value, centerPrompts, logger, phase);
228
+ if (transitioned) {
229
+ centerPrompts = v.value;
230
+ pendingCatalogFaces = undefined;
231
+ }
232
+ else {
233
+ pendingCatalogFaces = { target: v.value };
177
234
  }
178
- centerPrompts = v.value;
179
235
  };
180
236
  let bootConfigPending;
181
237
  const lkgEnabled = configProvider?.kind === "remote" && process.env.CONFIG_LKG_DISABLED !== "true";
@@ -209,9 +265,9 @@ async function main() {
209
265
  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" });
210
266
  return { effective: read.lkg.effective, ...(read.lkg.etag !== undefined ? { etag: read.lkg.etag } : {}) };
211
267
  };
212
- const bootApplyLkgInline = (lkgR) => {
268
+ const bootApplyLkgInline = async (lkgR) => {
213
269
  applyEffective(config, lkgR.effective, logger, { sealedKeys });
214
- adoptCenterPrompts(lkgR.effective, "boot-lkg");
270
+ await adoptCenterPrompts(lkgR.effective, "boot-lkg");
215
271
  markRosterLanded(lkgR.effective);
216
272
  effective = lkgR.effective;
217
273
  latestEffective = lkgR.effective;
@@ -240,7 +296,7 @@ async function main() {
240
296
  }
241
297
  if (raced === BOOT_FETCH_DEFERRED && lkgFallback) {
242
298
  try {
243
- bootApplyLkgInline(lkgFallback);
299
+ await bootApplyLkgInline(lkgFallback);
244
300
  }
245
301
  catch (applyErr) {
246
302
  lkgBooted = false;
@@ -250,7 +306,7 @@ async function main() {
250
306
  const r = raced === BOOT_FETCH_DEFERRED ? null : raced;
251
307
  if (r) {
252
308
  const bootPromptsRaw = r.effective.prompts;
253
- const bootPromptsOk = bootPromptsRaw === undefined || validateCenterPrompts(bootPromptsRaw).ok;
309
+ const bootPromptsOk = bootPromptsRaw === undefined || validatePromptsDomain(bootPromptsRaw).ok;
254
310
  const bootClean = (r.domainErrors ?? []).length === 0 && bootPromptsOk;
255
311
  if (bootClean)
256
312
  ccEtag = r.etag;
@@ -261,7 +317,7 @@ async function main() {
261
317
  }
262
318
  else {
263
319
  applyEffective(config, r.effective, logger, { sealedKeys });
264
- adoptCenterPrompts(r.effective, "boot");
320
+ await adoptCenterPrompts(r.effective, "boot");
265
321
  markRosterLanded(r.effective);
266
322
  effective = r.effective;
267
323
  if (bootClean)
@@ -282,7 +338,7 @@ async function main() {
282
338
  if (lkgFallback) {
283
339
  lkgBooted = true;
284
340
  try {
285
- bootApplyLkgInline(lkgFallback);
341
+ await bootApplyLkgInline(lkgFallback);
286
342
  }
287
343
  catch (applyErr) {
288
344
  lkgBooted = false;
@@ -742,9 +798,9 @@ async function main() {
742
798
  })
743
799
  : undefined;
744
800
  const fleetBus = new FleetEventBus();
745
- const workflowRunStore = baseWorkflowRunStore && workflowNotifyGate
801
+ const workflowRunStore = baseWorkflowRunStore
746
802
  ? new JournalingWorkflowRunStore(baseWorkflowRunStore, workflowNotifyGate, fleetBus)
747
- : baseWorkflowRunStore;
803
+ : undefined;
748
804
  const workflowJournalStore = config.selfOrchestrationEnabled && backend ? backend.workflowJournal() : undefined;
749
805
  const outcomeSink = backend?.outcomeSink();
750
806
  const workflowAgentRegistry = config.selfOrchestrationEnabled ? new WorkflowAgentRegistry() : undefined;
@@ -815,6 +871,7 @@ async function main() {
815
871
  warn: (msg, fields) => logger.warn(msg, fields),
816
872
  });
817
873
  const runnerDeps = {
874
+ promptSource,
818
875
  ...(rosterStore ? { rosterStore } : {}),
819
876
  brain,
820
877
  models: config.models,
@@ -1003,7 +1060,7 @@ async function main() {
1003
1060
  if (sessionTitler)
1004
1061
  logger.info("session_auto_title_enabled", {});
1005
1062
  const workflowsCapable = workflowsCapability(runnerDeps);
1006
- const subRunner = new Runner({ brain, models: config.models, roles: config.roles, pricing, tracer, ...(executionEnvFactory ? { executionEnvFactory } : {}), ...(lspManager ? { lspManager } : {}) });
1063
+ const subRunner = new Runner({ brain, models: config.models, roles: config.roles, pricing, tracer, promptSource, ...(executionEnvFactory ? { executionEnvFactory } : {}), ...(lspManager ? { lspManager } : {}) });
1007
1064
  const authorize = createAuthorizer(config, sessionStore);
1008
1065
  const runStore = backend ? backend.run() : undefined;
1009
1066
  const resumeAnchorStore = backend ? backend.resumeAnchor() : undefined;
@@ -1043,6 +1100,11 @@ async function main() {
1043
1100
  hint: "host-semantics lane with no approval face: permissionMode default/auto Write/Edit asks will auto-deny (fail-closed); wire TOOL_APPROVAL_ENABLED=true or DURABLE_APPROVAL=true to answer them",
1044
1101
  });
1045
1102
  }
1103
+ if (config.selfOrchestrationEnabled && config.remoteExec !== undefined && config.remoteExec.provider !== "host" && !toolApproval && !durableEnabled) {
1104
+ logger.warn("workflow_gate_no_responder", {
1105
+ hint: "self-orchestration with no approval face: permissionMode default/auto/acceptEdits run_workflow asks will auto-deny (fail-closed); wire TOOL_APPROVAL_ENABLED=true or DURABLE_APPROVAL=true to answer them",
1106
+ });
1107
+ }
1046
1108
  if (singleUserAutoAcceptBaseline) {
1047
1109
  logger.info("tool_policy_single_user_default", {
1048
1110
  posture: "adjudicated auto-accept — ALL write/hand/MCP tool surfaces auto-allowed (core per-surface UNGATED enumeration suppressed for single-user turnkey)",
@@ -1176,10 +1238,11 @@ async function main() {
1176
1238
  return;
1177
1239
  refreshInFlight = true;
1178
1240
  try {
1241
+ await retryPendingCatalog("refresh-retry");
1179
1242
  const r = prefetched !== undefined ? prefetched : await configProvider.fetchEffective(ccEtag);
1180
1243
  if (r) {
1181
1244
  const promptsRaw = r.effective.prompts;
1182
- const promptsGate = promptsRaw !== undefined ? validateCenterPrompts(promptsRaw) : { ok: true };
1245
+ const promptsGate = promptsRaw !== undefined ? validatePromptsDomain(promptsRaw) : { ok: true };
1183
1246
  const badDomains = [
1184
1247
  ...new Set([...(r.domainErrors ?? []).map((de) => de.domain), ...(promptsGate.ok ? [] : ["prompts"])]),
1185
1248
  ]
@@ -1208,7 +1271,7 @@ async function main() {
1208
1271
  }
1209
1272
  else {
1210
1273
  applyEffective(config, r.effective, logger, { teamsOnly: true, sealedKeys });
1211
- adoptCenterPrompts(r.effective, "refresh");
1274
+ await adoptCenterPrompts(r.effective, "refresh");
1212
1275
  markRosterLanded(r.effective);
1213
1276
  mutateInPlace(pricing, buildPricing(config.models));
1214
1277
  keyResolver = buildKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
@@ -1257,7 +1320,7 @@ async function main() {
1257
1320
  };
1258
1321
  const deferredBootApplyInner = async (r) => {
1259
1322
  const promptsRaw = r.effective.prompts;
1260
- const promptsGate = promptsRaw !== undefined ? validateCenterPrompts(promptsRaw) : { ok: true };
1323
+ const promptsGate = promptsRaw !== undefined ? validatePromptsDomain(promptsRaw) : { ok: true };
1261
1324
  if ((r.domainErrors ?? []).length > 0 || !promptsGate.ok) {
1262
1325
  logger.warn("config_boot_deferred_candidate_rejected", {
1263
1326
  version: r.effective.version,
@@ -1272,7 +1335,7 @@ async function main() {
1272
1335
  return;
1273
1336
  }
1274
1337
  applyEffective(config, r.effective, logger, { teamsOnly: true, sealedKeys });
1275
- adoptCenterPrompts(r.effective, "boot-deferred");
1338
+ await adoptCenterPrompts(r.effective, "boot-deferred");
1276
1339
  markRosterLanded(r.effective);
1277
1340
  mutateInPlace(pricing, buildPricing(config.models));
1278
1341
  keyResolver = buildKeyResolver(config.modelApiKeyEnv, process.env, config.modelApiKeys);
@@ -1633,7 +1696,7 @@ async function main() {
1633
1696
  })(cap.tools),
1634
1697
  skills: mergeUserSkills(cap.skills, body.skills, logger),
1635
1698
  mcp: resolveRequestMcp(mcpForScenario(config.mcpServers, scenarioName), body.mcpServers, config, logger),
1636
- promptProvider: centerPrompts ? centerPromptProvider(centerPrompts, scenarioName) : cap.promptProvider,
1699
+ promptProvider: centerPrompts?.declarations ? centerPromptProvider(centerPrompts.declarations, scenarioName) : cap.promptProvider,
1637
1700
  toolPolicy: durableEnabled
1638
1701
  ? combinePolicies(createDurableQuestionPolicy(), createDurableAskPolicy({
1639
1702
  requireApproval: config.approvalRequire, deny: config.approvalDeny, autoBudget: config.approvalAutoBudget, neverAuto: config.approvalNeverAuto,
@@ -1714,8 +1777,18 @@ async function main() {
1714
1777
  };
1715
1778
  })()
1716
1779
  : undefined;
1780
+ const workflowGate = approvalExemptionStore && auth?.sessionId
1781
+ ? {
1782
+ isExempt: async (toolName) => {
1783
+ const hit = await approvalExemptionStore.has(auth.sessionId, toolName);
1784
+ if (hit)
1785
+ logger.info("workflow_gate_exempted", { toolName, sessionId: auth.sessionId });
1786
+ return hit;
1787
+ },
1788
+ }
1789
+ : undefined;
1717
1790
  try {
1718
- governed = applyTaskSettings(governedBase, effectiveSettings, fsWriteGate);
1791
+ governed = applyTaskSettings(governedBase, effectiveSettings, fsWriteGate, workflowGate);
1719
1792
  }
1720
1793
  catch (e) {
1721
1794
  throw new HttpError(422, `settings are tighten-only and cannot loosen the deployment policy: ${e.message}`);