@sema-agent/core 6.0.0 → 7.0.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/agents/launch-receipt-contract.d.ts +34 -0
  3. package/dist/agents/launch-receipt-contract.js +5 -0
  4. package/dist/agents/subagent.d.ts +134 -2
  5. package/dist/agents/subagent.js +132 -31
  6. package/dist/core/file-history-store.js +24 -2
  7. package/dist/core/governance-codes.d.ts +11 -2
  8. package/dist/core/governance-codes.js +1 -0
  9. package/dist/core/permission-rule-consent.d.ts +42 -2
  10. package/dist/core/permission-rule-consent.js +93 -11
  11. package/dist/core/permission-rule-model.d.ts +51 -9
  12. package/dist/core/permission-rule-model.js +4 -2
  13. package/dist/core/permission-rule-session.d.ts +124 -0
  14. package/dist/core/permission-rule-session.js +121 -0
  15. package/dist/core/permission-rule-store.d.ts +65 -2
  16. package/dist/core/permission-rule-store.js +60 -6
  17. package/dist/core/permission-rule-sync.d.ts +9 -0
  18. package/dist/core/permission-rule-sync.js +37 -8
  19. package/dist/core/roles.js +1 -1
  20. package/dist/core/runner/prepare-task.js +35 -2
  21. package/dist/core/runner/runtask.js +2 -0
  22. package/dist/core/store-contracts/permission-rule-sync-contract.js +15 -1
  23. package/dist/core/task-notification.d.ts +20 -0
  24. package/dist/core/trace.d.ts +7 -2
  25. package/dist/core/types.d.ts +85 -1
  26. package/dist/core/wiring-manifest.d.ts +18 -1
  27. package/dist/index.d.ts +2 -1
  28. package/dist/index.js +2 -1
  29. package/dist/orchestration/run-workflow-tool.js +2 -2
  30. package/dist/orchestration/workflow.js +18 -10
  31. package/dist/stores/file/permission-rule-store.d.ts +11 -0
  32. package/dist/stores/file/permission-rule-store.js +22 -9
  33. package/package.json +1 -1
  34. package/test/export-surface.snapshot.json +15 -1
@@ -54,11 +54,14 @@ function forReview(text) {
54
54
  return text;
55
55
  return `${text.slice(0, MAX_REVIEWED_PROMPT_CHARS)}\n[…TRUNCATED FOR REVIEW: ${text.length - MAX_REVIEWED_PROMPT_CHARS} further characters follow that the child WILL receive and this review did NOT see]`;
56
56
  }
57
- function workflowModelLabel(spec) {
57
+ function workflowModelLabel(spec, catalog) {
58
58
  const model = spec.model;
59
59
  if (model === undefined)
60
60
  return undefined;
61
- return typeof model === "string" ? resolveModelDisplayLabel(model) : model.id ?? model.name;
61
+ if (typeof model !== "string")
62
+ return model.id ?? model.name;
63
+ const served = catalog?.[model]?.id;
64
+ return served ?? resolveModelDisplayLabel(model);
62
65
  }
63
66
  export const WORKFLOW_SUBAGENT_PROMPT = `You are a subagent spawned by a workflow orchestration script. Use the tools available to complete the task.
64
67
 
@@ -441,6 +444,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
441
444
  }
442
445
  const concurrency = normalizeConcurrency(opts.concurrency);
443
446
  const agentRegistry = workflowAgentRegistry(opts);
447
+ const modelCatalog = runner.agentCatalog?.models;
444
448
  const store = opts.store;
445
449
  const maxAgents = normalizeWorkflowHardCap("maxAgents", opts.maxAgents);
446
450
  const maxLogChars = normalizeWorkflowHardCap("maxLogChars", opts.maxLogChars);
@@ -633,11 +637,11 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
633
637
  const bceParentToolCall = opts.parentToolCallId !== undefined ? { parentToolCallId: opts.parentToolCallId } : {};
634
638
  const waIdOf = (callKey) => `wa${createHash("sha256").update(`${runId}:${callKey}`).digest("hex").slice(0, 16)}`;
635
639
  const bceLive = new Map();
636
- const bceSpawn = (callKey, label, agentType, replayed, sessionId) => {
640
+ const bceSpawn = (callKey, label, agentType, replayed, sessionId, model) => {
637
641
  if (!bceSink)
638
642
  return;
639
643
  const id = waIdOf(callKey);
640
- bceLive.set(id, { callKey, label, ...(agentType !== undefined ? { agentType } : {}), ...(sessionId !== undefined ? { sessionId } : {}) });
644
+ bceLive.set(id, { callKey, label, ...(agentType !== undefined ? { agentType } : {}), ...(sessionId !== undefined ? { sessionId } : {}), ...(model !== undefined ? { model } : {}) });
641
645
  bceEmit({
642
646
  kind: "spawn",
643
647
  taskId: id,
@@ -648,6 +652,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
648
652
  ...(scope !== undefined ? { scope } : {}),
649
653
  description: replayed ? `${label} (replayed)` : label,
650
654
  agentType: agentType ?? "workflow-agent",
655
+ ...(model !== undefined ? { model } : {}),
651
656
  name: label,
652
657
  ...(opts.parentTaskId !== undefined ? { parentTaskId: opts.parentTaskId } : {}),
653
658
  workflowRunId: runId,
@@ -679,6 +684,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
679
684
  workflowRunId: runId,
680
685
  ...(scope !== undefined ? { scope } : {}),
681
686
  ...(row.agentType !== undefined ? { agentType: row.agentType } : { agentType: "workflow-agent" }),
687
+ ...(row.model !== undefined ? { model: row.model } : {}),
682
688
  ...(row.sessionId !== undefined ? { sessionId: row.sessionId, transcriptId: row.sessionId } : {}),
683
689
  name: e.name ?? row.label,
684
690
  progressTaskId: e.taskId,
@@ -1003,7 +1009,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1003
1009
  const specForIdentity = inheritedModelSnap !== undefined ? { ...spec, model: inheritedModelSnap } : spec;
1004
1010
  const callKey = workflowAgentCallKey(run.agents.length, specForIdentity, agentOpts);
1005
1011
  const prompt = boundedRedactedSummary(spec.systemPrompt ? `${spec.systemPrompt}\n\n${spec.objective}` : spec.objective, MAX_TRANSCRIPT_CHARS);
1006
- const model = workflowModelLabel(specForIdentity);
1012
+ const specForLabel = spec.model === undefined && typeDefModel !== undefined ? { ...spec, model: typeDefModel } : specForIdentity;
1013
+ const model = workflowModelLabel(specForLabel, modelCatalog);
1007
1014
  return { label, phase, phaseInstance, groupId, inheritedModelSnap, callKey, prompt, model };
1008
1015
  };
1009
1016
  const reviewSpawnBeforeLaunch = async (lane, label, callKey, runSpec, effectiveSignal) => {
@@ -1161,13 +1168,14 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1161
1168
  const rs = r.stats;
1162
1169
  const cachedOutput = boundedRedactedSummary(r.structuredOutput ?? r.result, MAX_TRANSCRIPT_CHARS);
1163
1170
  const at = now();
1171
+ const replayModel = r.model;
1164
1172
  const replayRec = {
1165
1173
  label,
1166
1174
  callKey,
1167
1175
  ...(groupId !== undefined ? { groupId } : {}),
1168
1176
  phase,
1169
1177
  prompt,
1170
- ...(model !== undefined ? { model } : {}),
1178
+ ...(replayModel !== undefined ? { model: replayModel } : {}),
1171
1179
  replayed: true,
1172
1180
  status: r.status === "completed" ? "completed" : "failed",
1173
1181
  taskStatus: r.status,
@@ -1185,9 +1193,9 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1185
1193
  run.agents.push(replayRec);
1186
1194
  if (phaseInstance)
1187
1195
  agentPhaseOf.set(replayRec, phaseInstance);
1188
- emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(model !== undefined ? { model } : {}), replayed: true, ts: at });
1196
+ emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(replayModel !== undefined ? { model: replayModel } : {}), replayed: true, ts: at });
1189
1197
  emit({ type: "agent_end", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), status: replayRec.status, output: cachedOutput, ...(rs.toolCalls !== undefined ? { toolCalls: rs.toolCalls } : {}), replayed: true, ts: at });
1190
- bceSpawn(callKey, label, agentOpts.agentType, true, r.sessionId || undefined);
1198
+ bceSpawn(callKey, label, agentOpts.agentType, true, r.sessionId || undefined, replayModel);
1191
1199
  bceTerminal(callKey, replayRec.status === "completed" ? "completed" : "failed", cachedOutput, r.sessionId || undefined, replayRec.stats);
1192
1200
  accumulateStats(r, false);
1193
1201
  void persist("update");
@@ -1233,7 +1241,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1233
1241
  }
1234
1242
  rec.startedAt = now();
1235
1243
  const bornChildSessionId = resolveChildSessionIdAtSpawn(spec);
1236
- bceSpawn(callKey, label, agentOpts.agentType, false, bornChildSessionId);
1244
+ bceSpawn(callKey, label, agentOpts.agentType, false, bornChildSessionId, model);
1237
1245
  const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
1238
1246
  const typedSpec = typedSpec0.model === undefined && inheritedModelSnap !== undefined ? { ...typedSpec0, model: inheritedModelSnap } : typedSpec0;
1239
1247
  const framedSpec = withWorkflowChildPersona(typedSpec, agentOpts.schema ?? typedSpec.outputSchema);
@@ -1545,7 +1553,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1545
1553
  }
1546
1554
  rec.startedAt = now();
1547
1555
  const childSessionId = resolveChildSessionIdAtSpawn(spec);
1548
- bceSpawn(callKey, label, agentOpts.agentType, false, childSessionId);
1556
+ bceSpawn(callKey, label, agentOpts.agentType, false, childSessionId, model);
1549
1557
  let stream;
1550
1558
  try {
1551
1559
  const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
@@ -111,6 +111,17 @@ declare class FilePermissionRuleStore implements WritablePermissionRuleStore {
111
111
  /** Read and validate. Any refusal answers with an empty set AND says so — never a silent empty store. */
112
112
  private read;
113
113
  private disclose;
114
+ /**
115
+ * design/382 §4.3 — the AT-REST half of the durable two-member scope face: rows carrying a session
116
+ * scope, read out of persisted bytes (a hand edit or a foreign writer — the engine's own write arms
117
+ * refuse them loudly), are DROPPED from every read face and DISCLOSED. Never quarantined (the
118
+ * quarantine area is itself durable), never silently ridden: a session authorization must not gain
119
+ * an afterlife by being planted where the store cannot legally hold it. The write base
120
+ * (`current()`) is deliberately NOT screened — foreign bytes are left in place for a person to look
121
+ * at, exactly like every other damaged-row posture here; they simply never reach a live view, the
122
+ * sync wire (`readRaw`) or adjudication.
123
+ */
124
+ private screenDurableRows;
114
125
  private write;
115
126
  list(): Promise<StoredAllowRules>;
116
127
  /** The quarantine area (design/182 §5.2/§8.3): introspection only — never part of `list()`. */
@@ -1,7 +1,7 @@
1
1
  import { closeSync, constants as FS, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
- import { PERMISSION_RULE_WRITER, applySyncJoin, applyTombstones, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, foldDelta, } from "../../core/permission-rule-store.js";
4
+ import { PERMISSION_RULE_WRITER, applySyncJoin, applyTombstones, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, foldDelta, } from "../../core/permission-rule-store.js";
5
5
  import { canonicalize } from "../../core/canonical-json.js";
6
6
  import { BootLock } from "./fs-atomic.js";
7
7
  import { assertAdoptionBootGate } from "./adoption/marker.js";
@@ -158,6 +158,17 @@ class FilePermissionRuleStore {
158
158
  catch {
159
159
  }
160
160
  }
161
+ screenDurableRows(file) {
162
+ const isSession = (scope) => scope?.kind === "session";
163
+ const rules = file.rules.filter((r) => !isSession(r?.scope));
164
+ const tombstones = file.tombstones.filter((t) => !isSession(t?.scope));
165
+ const quarantined = (file.quarantined ?? []).filter((q) => !isSession(q?.scope));
166
+ const dropped = file.rules.length - rules.length + (file.tombstones.length - tombstones.length) + ((file.quarantined ?? []).length - quarantined.length);
167
+ if (dropped > 0) {
168
+ this.disclose(`${this.file} carries ${dropped} session-scope row(s) — a session authorization cannot live in the durable store (design/382 §4.3); those rows are ignored on every read face and never ride sync`);
169
+ }
170
+ return { rules, tombstones, quarantined };
171
+ }
161
172
  write(next) {
162
173
  assertSafeDir(this.dir);
163
174
  atomicPublish(this.dir, this.file, JSON.stringify({ ...next, checksum: checksumOf(next) }, null, 2));
@@ -166,16 +177,17 @@ class FilePermissionRuleStore {
166
177
  const r = this.read();
167
178
  if (!("file" in r))
168
179
  return { ...EMPTY_READ };
180
+ const screened = this.screenDurableRows(r.file);
169
181
  return {
170
- rules: applyTombstones(r.file.rules, r.file.tombstones),
171
- tombstones: r.file.tombstones,
182
+ rules: applyTombstones(screened.rules, screened.tombstones),
183
+ tombstones: screened.tombstones,
172
184
  rev: r.file.rev,
173
185
  checksum: r.file.checksum,
174
186
  };
175
187
  }
176
188
  async quarantined() {
177
189
  const r = this.read();
178
- return "file" in r ? (r.file.quarantined ?? []) : [];
190
+ return "file" in r ? this.screenDurableRows(r.file).quarantined : [];
179
191
  }
180
192
  async readOrgState() {
181
193
  const r = this.read();
@@ -245,14 +257,15 @@ class FilePermissionRuleStore {
245
257
  }),
246
258
  readRaw: async () => this.serialize(() => {
247
259
  const cur = this.current();
260
+ const screened = this.screenDurableRows(cur);
248
261
  return {
249
262
  actor: cur.actor,
250
263
  counter: cur.counter,
251
264
  rev: cur.rev,
252
- rules: structuredClone(cur.rules),
253
- tombstones: structuredClone(cur.tombstones),
265
+ rules: structuredClone(screened.rules),
266
+ tombstones: structuredClone(screened.tombstones),
254
267
  ...(cur.sync?.observedVector !== undefined ? { observedVector: structuredClone(cur.sync.observedVector) } : {}),
255
- quarantined: structuredClone(cur.quarantined ?? []),
268
+ quarantined: structuredClone(screened.quarantined),
256
269
  };
257
270
  }),
258
271
  apply: async (delta, opts) => this.serialize(() => {
@@ -287,8 +300,8 @@ class FilePermissionRuleStore {
287
300
  return { rev: cur.rev + 1, sync: report };
288
301
  }
289
302
  const next = delta.kind === "redemption-add"
290
- ? (assertRedemptionNotQuarantined(cur.quarantined ?? [], delta), { ...cur, rev: cur.rev + 1, rules: foldDelta(cur.rules, delta) })
291
- : (assertDeleteDeltaCarriesNoAdd(delta), { ...cur, rev: cur.rev + 1, tombstones: [...cur.tombstones, delta.tombstone] });
303
+ ? (assertWriteDeltaScopeDurable(delta), assertRedemptionNotQuarantined(cur.quarantined ?? [], delta), { ...cur, rev: cur.rev + 1, rules: foldDelta(cur.rules, delta) })
304
+ : (assertWriteDeltaScopeDurable(delta), assertDeleteDeltaCarriesNoAdd(delta), { ...cur, rev: cur.rev + 1, tombstones: [...cur.tombstones, delta.tombstone] });
292
305
  if (!this.writeAndVerify(next)) {
293
306
  throw new Error("the permission-rule store did not survive its own write; the change was not committed");
294
307
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "6.0.0",
3
+ "version": "7.0.1",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_comment": "design/87 L3 — frozen public export surface of src/index.ts (name -> kind). DO NOT edit by hand to silence a red test. A removed/changed entry = a SemVer-BREAKING change; bump MAJOR and update this fixture in the SAME commit (design/87 §4.2 / §5.2). Regenerate via REGEN in test/export-surface.test.ts.",
3
3
  "_tierComment": "#435 v1 — machine-readable layering of the public surface: stable = demonstrated by README.md / src/examples; internal = an `Internal`-marked name or a runner/engine deep-subtree declaration (the model seam src/engine/llm is excluded — it is the BYOM contract, not an engine internal); advanced = a supported export the front door does not walk you through. THIS IS AN INITIAL HEURISTIC, derived mechanically and expected to be refined ticket by ticket: no human reviewed these 1700+ entries one by one, and nothing here claims otherwise. Known bias: a short or English-word export name (ok, err, Result, Usage) can match ordinary prose in README.md and land `stable` on a coincidence. Every export MUST carry a tier — a new export with no row fails the gate in export-surface.test.ts.",
4
- "count": 1834,
4
+ "count": 1841,
5
5
  "exports": {
6
6
  "A2ATaskState": "type",
7
7
  "A2ATaskStateReversal": "type",
@@ -439,6 +439,7 @@
439
439
  "InMemoryRuleApprovalRecordStore": "class",
440
440
  "InMemorySessionPolicyStore": "class",
441
441
  "InMemorySessionRepo": "class",
442
+ "InMemorySessionRuleOverlay": "class",
442
443
  "InMemorySessionStorage": "class",
443
444
  "InMemoryStrategyStore": "class",
444
445
  "InMemoryToolResultStore": "class",
@@ -1002,6 +1003,9 @@
1002
1003
  "SessionPolicyError": "class",
1003
1004
  "SessionPolicyStore": "interface",
1004
1005
  "SessionRepo": "interface",
1006
+ "SessionRuleOverlay": "interface",
1007
+ "SessionRuleOverlayAdd": "interface",
1008
+ "SessionRuleOverlayApplyResult": "type",
1005
1009
  "SessionRulesRecord": "interface",
1006
1010
  "SessionStorage": "interface",
1007
1011
  "SessionStore": "interface",
@@ -1273,6 +1277,7 @@
1273
1277
  "assertWorkflowDeterminism": "function",
1274
1278
  "assertWorkflowPrimitivesWiring": "function",
1275
1279
  "assertWorkflowSandboxConformance": "function",
1280
+ "assertWriteDeltaScopeDurable": "function",
1276
1281
  "atomicWriteFile": "function",
1277
1282
  "attachToolContract": "function",
1278
1283
  "autoModeArmingRecipeOf": "function",
@@ -1537,7 +1542,9 @@
1537
1542
  "isTerminalTaskNotification": "function",
1538
1543
  "isTerminalWorkflowStatus": "function",
1539
1544
  "isThinkingLevel": "function",
1545
+ "isValidConsentScope": "function",
1540
1546
  "isValidCronExpr": "function",
1547
+ "isValidDurableScope": "function",
1541
1548
  "isValidReminderMark": "function",
1542
1549
  "isWslBashLauncher": "function",
1543
1550
  "isolationPermitsAutoAccept": "function",
@@ -2275,6 +2282,7 @@
2275
2282
  "InMemoryRuleApprovalRecordStore": "advanced",
2276
2283
  "InMemorySessionPolicyStore": "advanced",
2277
2284
  "InMemorySessionRepo": "internal",
2285
+ "InMemorySessionRuleOverlay": "advanced",
2278
2286
  "InMemorySessionStorage": "internal",
2279
2287
  "InMemoryStrategyStore": "advanced",
2280
2288
  "InMemoryToolResultStore": "stable",
@@ -2838,6 +2846,9 @@
2838
2846
  "SessionPolicyError": "advanced",
2839
2847
  "SessionPolicyStore": "advanced",
2840
2848
  "SessionRepo": "stable",
2849
+ "SessionRuleOverlay": "advanced",
2850
+ "SessionRuleOverlayAdd": "advanced",
2851
+ "SessionRuleOverlayApplyResult": "advanced",
2841
2852
  "SessionRulesRecord": "advanced",
2842
2853
  "SessionStorage": "internal",
2843
2854
  "SessionStore": "advanced",
@@ -3109,6 +3120,7 @@
3109
3120
  "assertWorkflowDeterminism": "advanced",
3110
3121
  "assertWorkflowPrimitivesWiring": "advanced",
3111
3122
  "assertWorkflowSandboxConformance": "advanced",
3123
+ "assertWriteDeltaScopeDurable": "advanced",
3112
3124
  "atomicWriteFile": "advanced",
3113
3125
  "attachToolContract": "advanced",
3114
3126
  "autoModeArmingRecipeOf": "advanced",
@@ -3373,7 +3385,9 @@
3373
3385
  "isTerminalTaskNotification": "advanced",
3374
3386
  "isTerminalWorkflowStatus": "advanced",
3375
3387
  "isThinkingLevel": "advanced",
3388
+ "isValidConsentScope": "advanced",
3376
3389
  "isValidCronExpr": "advanced",
3390
+ "isValidDurableScope": "advanced",
3377
3391
  "isValidReminderMark": "advanced",
3378
3392
  "isWslBashLauncher": "internal",
3379
3393
  "isolationPermitsAutoAccept": "advanced",