@sema-agent/core 5.49.0 → 5.51.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/dist/agents/roster-store.js +4 -1
  3. package/dist/agents/send-message-tool.js +5 -5
  4. package/dist/agents/subagent.d.ts +6 -0
  5. package/dist/agents/subagent.js +142 -5
  6. package/dist/agents/teacher.js +4 -1
  7. package/dist/brain/anthropic.js +11 -20
  8. package/dist/brain/open-responses.js +6 -14
  9. package/dist/brain/openai.js +6 -18
  10. package/dist/brain/reasoning.d.ts +100 -8
  11. package/dist/brain/reasoning.js +39 -15
  12. package/dist/brain/request-params.d.ts +37 -1
  13. package/dist/brain/request-params.js +40 -2
  14. package/dist/core/auto-mode-prompt.js +9 -1
  15. package/dist/core/hooks.d.ts +24 -1
  16. package/dist/core/hooks.js +26 -4
  17. package/dist/core/mcp.d.ts +7 -1
  18. package/dist/core/mcp.js +64 -8
  19. package/dist/core/memory-engine/engine.d.ts +30 -1
  20. package/dist/core/memory-engine/engine.js +219 -18
  21. package/dist/core/memory-engine/layout.d.ts +43 -0
  22. package/dist/core/memory-engine/layout.js +59 -0
  23. package/dist/core/memory-engine/memory-backend-contract.js +87 -0
  24. package/dist/core/memory-engine/types.d.ts +13 -1
  25. package/dist/core/runner/assemble-result.d.ts +6 -0
  26. package/dist/core/runner/assemble-result.js +1 -1
  27. package/dist/core/runner/prepare-task.d.ts +15 -0
  28. package/dist/core/runner/prepare-task.js +104 -44
  29. package/dist/core/runner/runtask.d.ts +5 -1
  30. package/dist/core/runner/runtask.js +14 -7
  31. package/dist/core/task-registry-agent.js +9 -3
  32. package/dist/core/task-registry-shared.d.ts +6 -0
  33. package/dist/core/task-registry.js +4 -2
  34. package/dist/core/tool-policy.d.ts +37 -0
  35. package/dist/core/tool-policy.js +36 -3
  36. package/dist/core/tools.js +7 -0
  37. package/dist/core/types.d.ts +53 -1
  38. package/dist/engine/loop/agent-loop.js +95 -30
  39. package/dist/engine/loop/types.d.ts +32 -0
  40. package/dist/orchestration/run-workflow-tool.d.ts +12 -0
  41. package/dist/orchestration/run-workflow-tool.js +1 -1
  42. package/dist/orchestration/workflow-governance.d.ts +27 -0
  43. package/dist/orchestration/workflow-governance.js +13 -0
  44. package/dist/orchestration/workflow-primitives.d.ts +8 -1
  45. package/dist/orchestration/workflow-primitives.js +11 -3
  46. package/package.json +1 -1
@@ -234,6 +234,18 @@ export interface RunWorkflowToolDeps {
234
234
  parentReadFace?: () => import("../core/types.js").TaskSpec["readFace"];
235
235
  /** Twin of the above for the deny-set additions (union, not replace). */
236
236
  parentReadDenyPatterns?: () => import("../core/types.js").TaskSpec["readDenyPatterns"];
237
+ /** #345 ③ — the HOST task's write-hands clamp, the read-face clamp's sibling on this lane (the
238
+ * subagent delegation lane has carried it as `ToolExecuteContext.handsReadOnly` since its own
239
+ * tighten-only ruling; the workflow lane was the remaining gap — a read-only host's workflow
240
+ * children spawned with full write hands). Filled ONLY when the clamp is ON (`true`), exactly
241
+ * like the ctx seat, so an unclamped deployment's mount gains no key. Known at prepare-time
242
+ * (frozen TaskSpec snapshot — no lazy getter needed, the `parentCheckpointStoreDisabled` shape). */
243
+ parentHandsReadOnly?: true;
244
+ /** #345 ③ — the HOST task's hard-headless clamp (`interactiveTools: false`), same carriage rules
245
+ * as `parentHandsReadOnly` above: only the DISABLING value travels, mirroring the subagent lane's
246
+ * `ToolExecuteContext.interactiveTools` seat — without it a headless-clamped host's workflow
247
+ * children could re-mount the interactive tool the host run disabled. */
248
+ parentInteractiveTools?: false;
237
249
  /** [1238](A) — call-time getter for the HOST task's RESOLVED Model object: a spawned agent whose
238
250
  * fold chain produced no model inherits the parent's full object (baseUrl/key routing included),
239
251
  * mirroring the subagent lane's ctx.model semantics. */
@@ -468,7 +468,7 @@ export async function createRunWorkflowTool(d) {
468
468
  }
469
469
  : governance;
470
470
  const scriptFn = (wfCtx) => {
471
- const primitives = buildWorkflowPrimitives(wfCtx, runGovernance, d.onAgentSpawn, d.parentThinking, principal, ctx.checkpointStoreDisabledForChildren === true || d.parentCheckpointStoreDisabled === true, d.parentReadFace, d.parentReadDenyPatterns);
471
+ const primitives = buildWorkflowPrimitives(wfCtx, runGovernance, d.onAgentSpawn, d.parentThinking, principal, ctx.checkpointStoreDisabledForChildren === true || d.parentCheckpointStoreDisabled === true, d.parentReadFace, d.parentReadDenyPatterns, ctx.handsReadOnly === true || d.parentHandsReadOnly === true, ctx.interactiveTools === false || d.parentInteractiveTools === false);
472
472
  return d.scriptRunner.run({ scriptSource: script, primitives, scriptArgs: effectiveArgs, signal: wfCtx.signal }).then((r) => r.result);
473
473
  };
474
474
  if (ctx.signal?.aborted) {
@@ -125,6 +125,33 @@ export interface ResourceClampNote {
125
125
  /** The effective value the child actually runs under (min of script/baseline/caps, or a forced ceiling). */
126
126
  applied: number;
127
127
  }
128
+ /**
129
+ * The worktree-overlay merge — `{ ...base, ...worktreeBase }` with ONE amendment: an overlay key
130
+ * whose value is OWN NULLISH (`undefined` OR `null` — both plain-JS deployment shapes) never
131
+ * overrides `base`; it reads as the absent key its value spells ("an absent overlay field inherits
132
+ * from base", the documented semantics). The GENERIC successor of the per-key protections that grew
133
+ * one field at a time (three face arrays, then `onAsk` — the mount-side deletes, which remain for
134
+ * their base-slot readings) while every OTHER same-shaped key stayed destructive: a surviving own
135
+ * nullish overlay key erased the base value — `toolPolicy` / `shellGate` / `limits` / `hooks` /
136
+ * `restoreGatedTools` / `readFace` / … — for exactly the children an untrusted script can route to
137
+ * the overlay (`isolation: "worktree"`), own-`undefined` keys included (a spread copies them; the
138
+ * old `=== null` face drop missed that half). One rule at the one merge point: a new baseline key
139
+ * is safe with no list to extend. The single exception is {@link NULL_VALUED_BASELINE_KEYS}
140
+ * (`checkpointStore: null` — the deployment disarming durable suspend for isolated children is a
141
+ * capability removal it must be able to spell). Explicit non-nullish overlay values keep winning
142
+ * wholesale — the documented deployment override.
143
+ *
144
+ * ACCEPTED COST, stated (adversarial-review round 2): the rule is direction-blind — a CAPABILITY
145
+ * key's out-of-type overlay `null` used to WITHHOLD the base value by the same spread accident
146
+ * (e.g. `worktreeBase: { getApiKeyAndHeaders: null }` overwrote the base resolver AND defeated the
147
+ * parent-credential `=== undefined` fill in workflow.ts — silently blocking the documented
148
+ * inheritance, the same seam-defeat disease the onAsk r3 ruling deleted null for), and such a key
149
+ * now inherits per the documented contract. Deployments withhold capabilities from isolated
150
+ * children with IN-TYPE spellings (`tools: []`, a deny `toolPolicy`, `handsReadOnly: true`);
151
+ * `getApiKeyAndHeaders` currently has no in-type disable value — that expressiveness gap is a
152
+ * baseline-contract question, deliberately not solved by resurrecting undocumented null semantics.
153
+ */
154
+ export declare function overlayWorktreeBaseline(base: WorkflowGovernanceBaseline["base"], worktreeBase: WorkflowGovernanceBaseline["base"]): WorkflowGovernanceBaseline["base"];
128
155
  /** Resolve an LLM-supplied model NAME to a deploy-configured `Model`, FAIL-CLOSED against the allowlist.
129
156
  * Throws (never falls open to the whole catalog) when no allowlist is configured or the name is not on it. */
130
157
  export declare function resolveModelName(name: string, allowlist: string[] | undefined, models: Record<string, Model> | undefined): Model;
@@ -57,6 +57,19 @@ function emitStrippedKeysNotice(survey, onNotice) {
57
57
  }
58
58
  }
59
59
  const VALID_THINKING = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
60
+ const NULL_VALUED_BASELINE_KEYS = new Set(["checkpointStore"]);
61
+ export function overlayWorktreeBaseline(base, worktreeBase) {
62
+ let overlay = worktreeBase;
63
+ for (const key of Object.keys(worktreeBase)) {
64
+ const v = worktreeBase[key];
65
+ if (v !== undefined && (v !== null || NULL_VALUED_BASELINE_KEYS.has(key)))
66
+ continue;
67
+ if (overlay === worktreeBase)
68
+ overlay = { ...worktreeBase };
69
+ delete overlay[key];
70
+ }
71
+ return { ...base, ...overlay };
72
+ }
60
73
  export function resolveModelName(name, allowlist, models) {
61
74
  if (!allowlist || allowlist.length === 0) {
62
75
  throw new WorkflowModelNotAllowedError(name, "no workflowModelAllowlist is configured (fail-closed: an LLM-authored script cannot pick a model)");
@@ -44,4 +44,11 @@ parentCheckpointStoreDisabled?: boolean,
44
44
  parentReadFace?: () => TaskSpec["readFace"],
45
45
  /** Twin of the above for the deny-set additions (built-ins always apply; these are the extra
46
46
  * entries the host's own deployment/task layers stacked on). */
47
- parentReadDenyPatterns?: () => TaskSpec["readDenyPatterns"]): WorkflowPrimitives;
47
+ parentReadDenyPatterns?: () => TaskSpec["readDenyPatterns"],
48
+ /** #345 ③ — the HOST task's write-hands clamp is ON (`spec.handsReadOnly === true`). Injected
49
+ * tighten-only into every spawned child, like the read-face clamp above; see the injection
50
+ * below for why it outranks even a `worktreeBase: { handsReadOnly: false }` overlay. */
51
+ parentHandsReadOnly?: boolean,
52
+ /** #345 ③ — the HOST task's hard-headless clamp is ON (`spec.interactiveTools === false`).
53
+ * Same tighten-only injection; only the disabling value ever arrives here as `true`. */
54
+ parentInteractiveToolsOff?: boolean): WorkflowPrimitives;
@@ -1,5 +1,5 @@
1
1
  import { assertSupportedAgentIsolation } from "./workflow.js";
2
- import { buildGovernedChildSpec } from "./workflow-governance.js";
2
+ import { buildGovernedChildSpec, overlayWorktreeBaseline } from "./workflow-governance.js";
3
3
  function safeAgentOptions(opts) {
4
4
  if (typeof opts !== "object" || opts === null)
5
5
  return {};
@@ -22,12 +22,12 @@ function formatResourceClampNote(notes) {
22
22
  const parts = notes.map((n) => `${n.field}: requested ${n.requested === undefined ? "unset" : n.requested} → applied ${n.applied}`);
23
23
  return `workflow governance tightened this agent's resource limits (${parts.join("; ")})`;
24
24
  }
25
- export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentThinking, parentPrincipal, parentCheckpointStoreDisabled, parentReadFace, parentReadDenyPatterns) {
25
+ export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentThinking, parentPrincipal, parentCheckpointStoreDisabled, parentReadFace, parentReadDenyPatterns, parentHandsReadOnly, parentInteractiveToolsOff) {
26
26
  const agent = (spec, opts) => {
27
27
  if (typeof spec === "string")
28
28
  spec = { objective: spec };
29
29
  const agentOpts = safeAgentOptions(opts);
30
- const effectiveBaseline = (b) => agentOpts.isolation === "worktree" && b.worktreeBase !== undefined ? { ...b, base: { ...b.base, ...b.worktreeBase } } : b;
30
+ const effectiveBaseline = (b) => agentOpts.isolation === "worktree" && b.worktreeBase != null ? { ...b, base: overlayWorktreeBaseline(b.base, b.worktreeBase) } : b;
31
31
  const childSpec = governance
32
32
  ? buildGovernedChildSpec(spec, effectiveBaseline(governance.baseline), governance.models, governance.caps, (notes) => ctx.log(formatResourceClampNote(notes)), governance.onNotice)
33
33
  : { ...spec };
@@ -53,6 +53,14 @@ export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentThi
53
53
  childSpec.readDenyPatterns = childSpec.readDenyPatterns !== undefined ? [...childSpec.readDenyPatterns, ...pd] : [...pd];
54
54
  }
55
55
  }
56
+ if (parentHandsReadOnly === true) {
57
+ childSpec.handsReadOnly = true;
58
+ }
59
+ if (parentInteractiveToolsOff === true) {
60
+ childSpec.interactiveTools = false;
61
+ if (childSpec.interactionPosture === "interactive")
62
+ delete childSpec.interactionPosture;
63
+ }
56
64
  if (onAgentSpawn) {
57
65
  return ctx.agentStream(childSpec, agentOpts).then((handle) => {
58
66
  onAgentSpawn(handle);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "5.49.0",
3
+ "version": "5.51.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",