@sema-agent/core 7.23.5 → 7.24.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 (59) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/dist/agents/list-agents-tool.d.ts +7 -4
  3. package/dist/agents/list-agents-tool.js +12 -1
  4. package/dist/agents/observer.js +5 -8
  5. package/dist/agents/subagent.js +2 -2
  6. package/dist/agents/team.js +9 -0
  7. package/dist/brain/degrading.js +8 -9
  8. package/dist/brain/route-adjudicator.d.ts +25 -12
  9. package/dist/brain/route-adjudicator.js +89 -23
  10. package/dist/brain/route-conformance.js +9 -0
  11. package/dist/brain/stream-engine.js +11 -9
  12. package/dist/core/arg-summary.js +17 -6
  13. package/dist/core/auto-compaction.d.ts +42 -1
  14. package/dist/core/auto-compaction.js +21 -1
  15. package/dist/core/auto-mode.js +2 -8
  16. package/dist/core/exec-gate.js +3 -5
  17. package/dist/core/file-history-store.js +11 -10
  18. package/dist/core/hooks.d.ts +7 -0
  19. package/dist/core/mcp.js +7 -5
  20. package/dist/core/memory-engine/distiller.d.ts +7 -4
  21. package/dist/core/memory-engine/distiller.js +11 -3
  22. package/dist/core/memory.js +1 -0
  23. package/dist/core/model-seat.d.ts +4 -1
  24. package/dist/core/model-seat.js +1 -1
  25. package/dist/core/roles.d.ts +1 -0
  26. package/dist/core/roles.js +1 -0
  27. package/dist/core/runner/assemble-result.js +12 -19
  28. package/dist/core/runner/contracts.d.ts +1 -0
  29. package/dist/core/runner/prepare-file-history.js +19 -0
  30. package/dist/core/runner/run-recovery-lanes.js +4 -20
  31. package/dist/core/runner/run-settle-and-teardown.js +2 -16
  32. package/dist/core/runner/run-turn-boundary.js +4 -20
  33. package/dist/core/runner/runtask.js +3 -5
  34. package/dist/core/runner/stream-settle-backstop.js +5 -5
  35. package/dist/core/runner/tool-end-body.js +5 -4
  36. package/dist/core/runner/tool-output-projection.js +3 -2
  37. package/dist/core/runner/turn-attachments.d.ts +0 -20
  38. package/dist/core/runner/turn-attachments.js +41 -36
  39. package/dist/core/session-store.js +2 -4
  40. package/dist/core/session.d.ts +4 -1
  41. package/dist/core/session.js +2 -1
  42. package/dist/core/task-event.d.ts +19 -9
  43. package/dist/core/task-registry-shared.js +1 -0
  44. package/dist/core/thrown-value.d.ts +24 -13
  45. package/dist/core/thrown-value.js +13 -1
  46. package/dist/core/tool-errors.d.ts +6 -0
  47. package/dist/core/tool-errors.js +6 -14
  48. package/dist/core/tools.js +8 -9
  49. package/dist/core/types.d.ts +1 -0
  50. package/dist/core/types.js +1 -0
  51. package/dist/engine/llm/types.d.ts +4 -4
  52. package/dist/index.d.ts +2 -2
  53. package/dist/index.js +2 -2
  54. package/dist/orchestration/workflow.js +44 -23
  55. package/dist/stores/file/file-history-store.js +12 -11
  56. package/package.json +1 -1
  57. package/test/export-surface.snapshot.json +9 -3
  58. package/wire-changes/core@7.24.0.json +249 -0
  59. package/wire-changes/core@7.24.1.json +85 -0
@@ -6,7 +6,7 @@ function normalizeContent(ret) {
6
6
  if (typeof ret === "string") {
7
7
  return { content: [{ type: "text", text: ret }], details: undefined, terminate: false, isError: false };
8
8
  }
9
- const raw = ret.content;
9
+ const raw = readMember(ret, "content");
10
10
  if (typeof raw !== "string" && !Array.isArray(raw)) {
11
11
  return {
12
12
  content: [{ type: "text", text: "Tool error: content must be a string or an array of content blocks." }],
@@ -16,7 +16,7 @@ function normalizeContent(ret) {
16
16
  };
17
17
  }
18
18
  const content = typeof raw === "string" ? [{ type: "text", text: raw }] : raw;
19
- return { content, details: ret.details, terminate: ret.terminate ?? false, isError: ret.isError === true };
19
+ return { content, details: readMember(ret, "details"), terminate: readMember(ret, "terminate") === true, isError: readMember(ret, "isError") === true };
20
20
  }
21
21
  function isEmptyToolContent(content) {
22
22
  if (content.length === 0)
@@ -108,11 +108,15 @@ export function defineTool(spec, options) {
108
108
  isError: true,
109
109
  };
110
110
  }
111
- let ret;
112
111
  try {
113
112
  const baseCtx = { toolCallId, signal };
114
113
  const ctx = options?.enrichCtx ? stampCallIdentity(options.enrichCtx(baseCtx), toolCallId, signal) : baseCtx;
115
- ret = await spec.execute(params, ctx);
114
+ const ret = await spec.execute(params, ctx);
115
+ const { content, details, terminate, isError } = normalizeContent(ret);
116
+ const guarded = isEmptyToolContent(content)
117
+ ? [{ type: "text", text: isError ? `(${spec.name} reported an error with no further output)` : `(${spec.name} completed with no output)` }]
118
+ : content;
119
+ return { content: guarded, details, terminate, ...(isError ? { isError: true } : {}) };
116
120
  }
117
121
  catch (err) {
118
122
  const wrapped = new Error(formatToolError(err));
@@ -124,11 +128,6 @@ export function defineTool(spec, options) {
124
128
  wrapped.errorKind = errorKind;
125
129
  throw wrapped;
126
130
  }
127
- const { content, details, terminate, isError } = normalizeContent(ret);
128
- const guarded = isEmptyToolContent(content)
129
- ? [{ type: "text", text: isError ? `(${spec.name} reported an error with no further output)` : `(${spec.name} completed with no output)` }]
130
- : content;
131
- return { content: guarded, details, terminate, ...(isError ? { isError: true } : {}) };
132
131
  },
133
132
  };
134
133
  if (spec.contract) {
@@ -12,6 +12,7 @@
12
12
  import type { AgentTool, ThinkingLevel, ToolInputValidationContext, ToolInputVerdict } from "../internal/harness.js";
13
13
  import type { Model, StreamFn } from "../internal/llm.js";
14
14
  export type { ModelRef, ModelRole, RoleSpec, ModelRoles, Brain } from "./model-seat.js";
15
+ export { MODEL_ROLES } from "./model-seat.js";
15
16
  export type { StaleToolResultOffloadOptions, ImageInput, TaskSpec, ShellGateDoctrine } from "./task-spec.js";
16
17
  export type { ToolEffect, ToolContentOrigin, ReversibilityVerdict, ReadRootGrantCandidate, ToolSpec, ToolReturn, NestedUsage, NestedUsageAccum, ToolExecuteContext } from "./tool-spec.js";
17
18
  export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditRequest, TrackEditResult, TrackFileEditHook, FileEditedNotice, FileEditedHook, HandsBandOptions } from "./hands-band.js";
@@ -1,3 +1,4 @@
1
+ export { MODEL_ROLES } from "./model-seat.js";
1
2
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
2
3
  export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
3
4
  export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, settleOwedUserInput, undrainedUserInputNotices } from "./engine-notice.js";
@@ -911,14 +911,14 @@ export interface RoutePairingConfig {
911
911
  export type RoutePairingPosture = "per-model" | "paired" | "unpinned" | "keyless";
912
912
  /** Machine codes an adjudication refusal carries (minted here, forwarded verbatim by consumers). */
913
913
  export type RouteRefusalCode = "route.credential_mismatch" | "route.credential_missing";
914
- /** The structured account of a refusal — enough for a caller to render the two halves and the fix. */
914
+ /** The structured account of a refusal — the two halves and the fix, every URL a DISPLAY spelling. */
915
915
  export interface RouteRefusalDetail {
916
916
  modelId: string;
917
- /** The entry's declared URL half (`Model.baseUrl`, normalized). */
917
+ /** The entry's declared URL half (`Model.baseUrl`), via `displayUrlOf` — NOT route identity. */
918
918
  entryBaseUrl: string;
919
- /** The root the request would actually target (normalized). */
919
+ /** The root the request would actually target, rendered (use `routeKeyOf` for identity). */
920
920
  requestBaseUrl: string;
921
- /** The deployment's declared root the credential is paired with (mismatch refusals). */
921
+ /** The declared root the credential is paired with (mismatch refusals), rendered. */
922
922
  configBaseUrl?: string;
923
923
  /** One-line configuration fix. */
924
924
  fixHint: string;
package/dist/index.d.ts CHANGED
@@ -40,7 +40,7 @@ export { LspDiagnosticsRegistry, formatDiagnosticsBlock, formatDiagnosticsSummar
40
40
  export { NodeLspManager, DEFAULT_LSP_SERVERS, languageFor, defaultLspSpawn, type NodeLspManagerOptions, type LspSpawn, } from "./engine/lsp/node-lsp-manager.js";
41
41
  export { StdioLspTransport, type LspChildProcess } from "./engine/lsp/stdio-lsp-transport.js";
42
42
  export { encodeFrame, createFrameDecoder } from "./engine/lsp/frame-decoder.js";
43
- export { resolveModel, resolveTaskModel, parseModelMention, expandTiers, DEFAULT_TIER_ORDER, CC_MODEL_TIER_ALIASES, ROLE_TIER_DEFAULTS, type ModelRole, type RoleSpec, type ModelRoles, type ResolvedRole, type ModelMention, } from "./core/roles.js";
43
+ export { resolveModel, resolveTaskModel, parseModelMention, expandTiers, DEFAULT_TIER_ORDER, CC_MODEL_TIER_ALIASES, ROLE_TIER_DEFAULTS, MODEL_ROLES, type ModelRole, type RoleSpec, type ModelRoles, type ResolvedRole, type ModelMention, } from "./core/roles.js";
44
44
  export { selectModel, selectModelOrThrow, type ModelCriteria, type ModelTier } from "./core/select-model.js";
45
45
  export { BrainError, classifyHttp, type BrainErrorCode } from "./brain/errors.js";
46
46
  export { looksDegenerate, inspectDegenerate, trimDegenerateTail } from "./brain/repetition.js";
@@ -325,7 +325,7 @@ export { createCircuitBreakerBrain, type CircuitBreakerOptions, type BreakerStat
325
325
  export { createDegradingBrain, readDegradation, DEGRADED_DIAGNOSTIC_TYPE, type DegradingBrainOptions, type DegradeReason, type DegradationInfo, } from "./brain/degrading.js";
326
326
  export { retryBackoffMs, parseRetryAfter } from "./brain/retry.js";
327
327
  export { WAITING_FIRST_TOKEN_AFTER_MS, WAITING_FIRST_TOKEN_EVERY_MS } from "./brain/stream-engine.js";
328
- export { adjudicateModelRoute, resolveRouteCredential, routeRefusalText, routePairingStatus, normalizeBaseUrl, hasAuthCarrier, type RoutePairingStatus, } from "./brain/route-adjudicator.js";
328
+ export { adjudicateModelRoute, resolveRouteCredential, routeRefusalText, routePairingStatus, routeKeyOf, displayUrlOf, resolveRequestRoot, hasAuthCarrier, type RoutePairingStatus, } from "./brain/route-adjudicator.js";
329
329
  export type { RouteAdjudication, RouteCredential, RouteCredentialSource, RoutePairingConfig, RoutePairingPosture, RouteRefusalCode, RouteRefusalDetail, } from "./internal/llm.js";
330
330
  export { ROUTE_ADJUDICATION_CONFORMANCE_CORPUS, type RouteAdjudicationVector } from "./brain/route-conformance.js";
331
331
  export { type BrainTimeoutConfig } from "./brain/timeout.js";
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ export { LspDiagnosticsRegistry, formatDiagnosticsBlock, formatDiagnosticsSummar
27
27
  export { NodeLspManager, DEFAULT_LSP_SERVERS, languageFor, defaultLspSpawn, } from "./engine/lsp/node-lsp-manager.js";
28
28
  export { StdioLspTransport } from "./engine/lsp/stdio-lsp-transport.js";
29
29
  export { encodeFrame, createFrameDecoder } from "./engine/lsp/frame-decoder.js";
30
- export { resolveModel, resolveTaskModel, parseModelMention, expandTiers, DEFAULT_TIER_ORDER, CC_MODEL_TIER_ALIASES, ROLE_TIER_DEFAULTS, } from "./core/roles.js";
30
+ export { resolveModel, resolveTaskModel, parseModelMention, expandTiers, DEFAULT_TIER_ORDER, CC_MODEL_TIER_ALIASES, ROLE_TIER_DEFAULTS, MODEL_ROLES, } from "./core/roles.js";
31
31
  export { selectModel, selectModelOrThrow } from "./core/select-model.js";
32
32
  export { BrainError, classifyHttp } from "./brain/errors.js";
33
33
  export { looksDegenerate, inspectDegenerate, trimDegenerateTail } from "./brain/repetition.js";
@@ -279,7 +279,7 @@ export { createCircuitBreakerBrain, CIRCUIT_OPEN_MARKER, } from "./brain/circuit
279
279
  export { createDegradingBrain, readDegradation, DEGRADED_DIAGNOSTIC_TYPE, } from "./brain/degrading.js";
280
280
  export { retryBackoffMs, parseRetryAfter } from "./brain/retry.js";
281
281
  export { WAITING_FIRST_TOKEN_AFTER_MS, WAITING_FIRST_TOKEN_EVERY_MS } from "./brain/stream-engine.js";
282
- export { adjudicateModelRoute, resolveRouteCredential, routeRefusalText, routePairingStatus, normalizeBaseUrl, hasAuthCarrier, } from "./brain/route-adjudicator.js";
282
+ export { adjudicateModelRoute, resolveRouteCredential, routeRefusalText, routePairingStatus, routeKeyOf, displayUrlOf, resolveRequestRoot, hasAuthCarrier, } from "./brain/route-adjudicator.js";
283
283
  export { ROUTE_ADJUDICATION_CONFORMANCE_CORPUS } from "./brain/route-conformance.js";
284
284
  export {} from "./brain/timeout.js";
285
285
  export { createAssistantMessageEventStream } from "./internal/llm.js";
@@ -23,7 +23,7 @@ import { compileOutputSchema } from "../core/runner/strict-output-schema.js";
23
23
  import { WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentBlockedError, WorkflowAgentParkedError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowMaxAgentsError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE } from "./workflow-types.js";
24
24
  import { WorkflowScriptError } from "./workflow-meta.js";
25
25
  import { STALL_CONNECT_MS, STALL_FIRST_TOKEN_MS } from "../brain/timeout.js";
26
- import { asThrownError, thrownText } from "../core/thrown-value.js";
26
+ import { asThrownError, isErrorLike, readMember, thrownIs, thrownName, thrownText } from "../core/thrown-value.js";
27
27
  export * from "./workflow-types.js";
28
28
  const MAX_TRANSCRIPT_CHARS = 4000;
29
29
  const WORKFLOW_RESULT_MAX = 4000;
@@ -176,7 +176,7 @@ export function assertSupportedAgentIsolation(isolation) {
176
176
  }
177
177
  }
178
178
  function rethrowIfMaxAgents(e) {
179
- if (e instanceof WorkflowMaxAgentsError)
179
+ if (thrownIs(e, WorkflowMaxAgentsError))
180
180
  throw e;
181
181
  return null;
182
182
  }
@@ -186,7 +186,7 @@ function foldFanOutSettled(settled, lane, errorsOut, emitRunLog) {
186
186
  const results = settled.map((s) => {
187
187
  if (s.ok)
188
188
  return s.v;
189
- if (s.e instanceof WorkflowBudgetExceededError) {
189
+ if (thrownIs(s.e, WorkflowBudgetExceededError)) {
190
190
  dropped++;
191
191
  droppedSlots.push(s.i);
192
192
  errorsOut?.push(workflowFanOutSlotError(s.i, s.e));
@@ -201,9 +201,19 @@ function foldFanOutSettled(settled, lane, errorsOut, emitRunLog) {
201
201
  emitRunLog(`${lane}: ${dropped} ${dropped === 1 ? "slot" : "slots"} dropped — token budget exceeded (${dropped === 1 ? "slot" : "slots"} ${droppedSlots.join(", ")})`);
202
202
  return results;
203
203
  }
204
+ function thrownMessageOf(err) {
205
+ if (!isErrorLike(err))
206
+ return undefined;
207
+ const message = readMember(err, "message");
208
+ return typeof message === "string" && message !== "" ? message : undefined;
209
+ }
210
+ function thrownNameOf(err) {
211
+ const name = thrownName(err);
212
+ return name !== undefined && name !== "" ? name : undefined;
213
+ }
204
214
  function workflowFanOutSlotError(index, e) {
205
- const code = e?.code;
206
- const kind = typeof code === "string" && code.length > 0 ? code : e instanceof Error ? e.name : "Error";
215
+ const code = readMember(e, "code");
216
+ const kind = typeof code === "string" && code.length > 0 ? code : thrownNameOf(e) ?? "Error";
207
217
  return { index, kind, message: boundedRedactedSummary(thrownText(e), 300) };
208
218
  }
209
219
  export class WorkflowResultTooLargeError extends Error {
@@ -247,12 +257,20 @@ export const MAX_WORKFLOW_ITEMS = 4096;
247
257
  function describeFanOutValue(v) {
248
258
  if (v === null)
249
259
  return "null";
250
- if (Array.isArray(v))
260
+ if (isFanOutArray(v))
251
261
  return "array";
252
- if (v instanceof Promise)
262
+ if (thrownIs(v, Promise))
253
263
  return "a Promise (an already-CALLED agent(...) — wrap it: () => agent(...))";
254
264
  return typeof v;
255
265
  }
266
+ function isFanOutArray(v) {
267
+ try {
268
+ return Array.isArray(v);
269
+ }
270
+ catch {
271
+ return false;
272
+ }
273
+ }
256
274
  function assertFanOutCallables(lane, member, list) {
257
275
  for (let i = 0; i < list.length; i++) {
258
276
  if (typeof list[i] !== "function") {
@@ -263,7 +281,7 @@ function assertFanOutCallables(lane, member, list) {
263
281
  }
264
282
  }
265
283
  function assertFanOutArray(lane, what, v) {
266
- if (!Array.isArray(v)) {
284
+ if (!isFanOutArray(v)) {
267
285
  throw new WorkflowScriptError(`${lane}(): ${what} must be an ARRAY (got ${describeFanOutValue(v)}). Nothing was run.`);
268
286
  }
269
287
  }
@@ -1740,14 +1758,14 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1740
1758
  }
1741
1759
  }
1742
1760
  catch (err) {
1743
- if (drive !== undefined && !finalized && !(err instanceof WorkflowAgentParkedError)) {
1761
+ if (drive !== undefined && !finalized && !thrownIs(err, WorkflowAgentParkedError)) {
1744
1762
  emitRunLog(`[park] the parked leg "${label}" could not be driven — the resume was refused before the checkpoint was consumed (${withoutToken(errText(err), drive.token)}); the park stands.`);
1745
1763
  return await parkAgentLeg(rec, drive.paused, activityTail, false);
1746
1764
  }
1747
1765
  if (!finalized) {
1748
1766
  rec.status = "failed";
1749
1767
  rec.endedAt = now();
1750
- const salvaged = err instanceof WorkflowAgentStalledError ? err.lastResult : undefined;
1768
+ const salvaged = thrownIs(err, WorkflowAgentStalledError) ? err.lastResult : undefined;
1751
1769
  if (salvaged?.runId === undefined)
1752
1770
  delete rec.taskRunId;
1753
1771
  if (salvaged !== undefined) {
@@ -1763,17 +1781,18 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1763
1781
  rec.output = boundedRedactedSummary(salvaged.structuredOutput ?? salvaged.result, MAX_TRANSCRIPT_CHARS) || (rec.errorMessage ?? "");
1764
1782
  rec.stats = { tokens: salvaged.stats.tokens ?? 0, turns: salvaged.stats.turns ?? 0, costMicroUsd: salvaged.stats.costMicroUsd };
1765
1783
  }
1766
- if (err instanceof WorkflowAgentBlockedError) {
1784
+ if (thrownIs(err, WorkflowAgentBlockedError)) {
1767
1785
  rec.errorCode = WORKFLOW_SPAWN_BLOCKED_ERROR_CODE;
1768
1786
  rec.errorMessage = boundedRedactedSummary(err.message, MAX_TRANSCRIPT_CHARS);
1769
1787
  }
1770
- if (err instanceof WorkflowBudgetExceededError) {
1788
+ if (thrownIs(err, WorkflowBudgetExceededError)) {
1771
1789
  rec.errorCode = err.code;
1772
1790
  rec.errorMessage = boundedRedactedSummary(err.message, MAX_TRANSCRIPT_CHARS);
1773
1791
  }
1774
- if (rec.errorMessage === undefined && err instanceof Error && err.message !== "")
1775
- rec.errorMessage = boundedRedactedSummary(err.message, MAX_TRANSCRIPT_CHARS);
1776
- if (err instanceof WorkflowAgentStalledError && err.attempts > 1) {
1792
+ const preLaunchMessage = thrownMessageOf(err);
1793
+ if (rec.errorMessage === undefined && preLaunchMessage !== undefined)
1794
+ rec.errorMessage = boundedRedactedSummary(preLaunchMessage, MAX_TRANSCRIPT_CHARS);
1795
+ if (thrownIs(err, WorkflowAgentStalledError) && err.attempts > 1) {
1777
1796
  rec.attempts = err.attempts;
1778
1797
  rec.lastAttemptReason = "stalled";
1779
1798
  }
@@ -1785,12 +1804,12 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1785
1804
  result: boundedRedactedSummary(thrownText(err), 500),
1786
1805
  terminal: {
1787
1806
  kind: "failed",
1788
- ...(err instanceof WorkflowAgentBlockedError ? { code: WORKFLOW_SPAWN_BLOCKED_ERROR_CODE } : err instanceof WorkflowBudgetExceededError ? { code: err.code } : {}),
1807
+ ...(thrownIs(err, WorkflowAgentBlockedError) ? { code: WORKFLOW_SPAWN_BLOCKED_ERROR_CODE } : thrownIs(err, WorkflowBudgetExceededError) ? { code: err.code } : {}),
1789
1808
  message: boundedRedactedSummary(thrownText(err), 500),
1790
1809
  },
1791
1810
  stats: { turns: 0, tokens: 0, costMicroUsd: 0, usageMissing: true },
1792
1811
  }, label).catch(() => undefined);
1793
- if (!((err instanceof WorkflowBudgetExceededError || err instanceof WorkflowAgentParkedError) && rec.startedAt === undefined))
1812
+ if (!((thrownIs(err, WorkflowBudgetExceededError) || thrownIs(err, WorkflowAgentParkedError)) && rec.startedAt === undefined))
1794
1813
  await journaled;
1795
1814
  bceTerminal(callKey, "failed", rec.output ?? (thrownText(err)), rec.sessionId, rec.stats);
1796
1815
  }
@@ -1955,9 +1974,10 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1955
1974
  releaseLeg();
1956
1975
  return await parkAgentLeg(rec, drive.paused, [], false);
1957
1976
  }
1958
- if (!finalized && rec.errorMessage === undefined && err instanceof Error && err.message !== "")
1959
- rec.errorMessage = boundedRedactedSummary(err.message, MAX_TRANSCRIPT_CHARS);
1960
- if (!finalized && err instanceof WorkflowAgentBlockedError) {
1977
+ const spawnMessage = thrownMessageOf(err);
1978
+ if (!finalized && rec.errorMessage === undefined && spawnMessage !== undefined)
1979
+ rec.errorMessage = boundedRedactedSummary(spawnMessage, MAX_TRANSCRIPT_CHARS);
1980
+ if (!finalized && thrownIs(err, WorkflowAgentBlockedError)) {
1961
1981
  rec.errorCode = WORKFLOW_SPAWN_BLOCKED_ERROR_CODE;
1962
1982
  rec.errorMessage = boundedRedactedSummary(err.message, MAX_TRANSCRIPT_CHARS);
1963
1983
  void journalAppend(callKey, {
@@ -2210,8 +2230,9 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
2210
2230
  prior = await opts.store.get(opts.resumeFromRunId);
2211
2231
  }
2212
2232
  catch (err) {
2213
- const errCode = err instanceof Error ? err.code : undefined;
2214
- const errClass = err instanceof Error ? `${err.name}${typeof errCode === "string" ? `/${errCode}` : ""}` : typeof err;
2233
+ const errName = thrownNameOf(err);
2234
+ const errCode = errName !== undefined ? readMember(err, "code") : undefined;
2235
+ const errClass = errName !== undefined ? `${errName}${typeof errCode === "string" ? `/${errCode}` : ""}` : typeof err;
2215
2236
  throw new WorkflowParkRefusal("workflow.park_truth_unreadable", `startWorkflow: resume from "${fromRunId}" was REFUSED — the prior run's record could not be read from the run store (${errClass}), so its parks cannot be proven.`, { reason: "store_threw" });
2216
2237
  }
2217
2238
  if (prior !== null && prior.scope !== scope)
@@ -2376,7 +2397,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
2376
2397
  run.status = "failed";
2377
2398
  run.completionId ??= uuidv7();
2378
2399
  run.endedAt = now();
2379
- if (parkedLeg !== undefined && !(err instanceof WorkflowAgentParkedError))
2400
+ if (parkedLeg !== undefined && !thrownIs(err, WorkflowAgentParkedError))
2380
2401
  err = parkedError();
2381
2402
  run.error = thrownText(err);
2382
2403
  closeAbandonedAgents(run.endedAt);
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
2
  import { appendFileSync, closeSync, existsSync, fsyncSync, openSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { thrownText } from "../../core/thrown-value.js";
4
5
  import { applyFileRestoreViaEnv, boundaryPublishVerdict, worldStillMatchesMint, captureFileBackupViaEnv, modeProvenDifferent, previewFileDelta, describeTrackKey, fileHistoryExportDigest, resolveTrackKey, trackKeyOf, validateFileHistoryExport, } from "../../core/file-history-store.js";
5
6
  import { fileHistoryBoundariesToKeep, resolveFileHistoryRetention } from "../../core/file-history-retention.js";
6
7
  import { atomicWriteFile, ensureDir, sanitizePathComponent, sanitizeScope, writeThenLink } from "./fs-atomic.js";
@@ -456,7 +457,7 @@ export class FileFileHistoryStore {
456
457
  return { ok: true, minted: true };
457
458
  }
458
459
  catch (err) {
459
- return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} could not be persisted: ${err.message}` } };
460
+ return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} could not be persisted: ${thrownText(err)}` } };
460
461
  }
461
462
  }
462
463
  async annulTrack(scope, absPath, root, verify) {
@@ -511,7 +512,7 @@ export class FileFileHistoryStore {
511
512
  return { ok: true };
512
513
  }
513
514
  catch (err) {
514
- return { ok: false, error: { code: "track_failed", message: `annulling the first-touch record for ${absPath} failed: ${err.message}` } };
515
+ return { ok: false, error: { code: "track_failed", message: `annulling the first-touch record for ${absPath} failed: ${thrownText(err)}` } };
515
516
  }
516
517
  }
517
518
  scopeIsEmptySync(scope) {
@@ -677,7 +678,7 @@ export class FileFileHistoryStore {
677
678
  }
678
679
  catch (err) {
679
680
  nestedSetAdd(this.spentAttempts, scope, entryId);
680
- return { ok: false, error: { code: "snapshot_failed", message: `boundary for entry "${entryId}" could not be committed: ${err.message}` } };
681
+ return { ok: false, error: { code: "snapshot_failed", message: `boundary for entry "${entryId}" could not be committed: ${thrownText(err)}` } };
681
682
  }
682
683
  finally {
683
684
  this.activeCaptures.get(scope)?.delete(entryId);
@@ -754,7 +755,7 @@ export class FileFileHistoryStore {
754
755
  });
755
756
  }
756
757
  catch (err) {
757
- applied = { outcome: "failed", reason: err.message };
758
+ applied = { outcome: "failed", reason: thrownText(err) };
758
759
  }
759
760
  if (applied.outcome === "applied")
760
761
  ledger.filesChanged.push(key);
@@ -771,7 +772,7 @@ export class FileFileHistoryStore {
771
772
  }
772
773
  catch (err) {
773
774
  const touched = ledger.filesChanged.length > 0 || ledger.identicalSkipped.length > 0 || ledger.refused.length > 0 || ledger.failed.length > 0;
774
- return { outcome: touched ? "partial" : "failed", ...ledger, error: { code: "restore_failed", message: err.message } };
775
+ return { outcome: touched ? "partial" : "failed", ...ledger, error: { code: "restore_failed", message: thrownText(err) } };
775
776
  }
776
777
  }
777
778
  async canRestore(scope, entryId) {
@@ -889,7 +890,7 @@ export class FileFileHistoryStore {
889
890
  this.writeTracked(scope, tracked);
890
891
  }
891
892
  catch (err) {
892
- this.disclose(this.trackedPath(scope), `version-chain trim could not be committed (${err.message}) — trim skipped this pass; untrimmed versions keep their blobs live`);
893
+ this.disclose(this.trackedPath(scope), `version-chain trim could not be committed (${thrownText(err)}) — trim skipped this pass; untrimmed versions keep their blobs live`);
893
894
  }
894
895
  }
895
896
  this.collectBlobs();
@@ -1091,7 +1092,7 @@ export class FileFileHistoryStore {
1091
1092
  return { ok: true };
1092
1093
  }
1093
1094
  catch (err) {
1094
- return { ok: false, error: { code: "adopt_failed", message: `adoptScope failed: ${err.message}` } };
1095
+ return { ok: false, error: { code: "adopt_failed", message: `adoptScope failed: ${thrownText(err)}` } };
1095
1096
  }
1096
1097
  }
1097
1098
  async exportHistory(scope) {
@@ -1151,7 +1152,7 @@ export class FileFileHistoryStore {
1151
1152
  return await this.runImport(scope, data, srcGetBlob, importJournal, identicalRepublish);
1152
1153
  }
1153
1154
  catch (err) {
1154
- return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${err.message}` } };
1155
+ return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}` } };
1155
1156
  }
1156
1157
  }
1157
1158
  async runImport(scope, data, srcGetBlob, importJournal, identicalRepublish) {
@@ -1178,7 +1179,7 @@ export class FileFileHistoryStore {
1178
1179
  bytes = await srcGetBlob(v.blobHash);
1179
1180
  }
1180
1181
  catch (err) {
1181
- return { ok: false, error: { code: "unsupported_format", message: `source blob ${v.blobHash} fetch failed: ${err.message}` } };
1182
+ return { ok: false, error: { code: "unsupported_format", message: `source blob ${v.blobHash} fetch failed: ${thrownText(err)}` } };
1182
1183
  }
1183
1184
  if (bytes === undefined)
1184
1185
  return { ok: false, error: { code: "not_found", message: `source blob ${v.blobHash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
@@ -1222,7 +1223,7 @@ export class FileFileHistoryStore {
1222
1223
  }
1223
1224
  catch (err) {
1224
1225
  safeRm(staging);
1225
- return { ok: false, error: { code: "adopt_failed", message: `importHistory could not publish the staged graph for "${scope}": ${err.message} — nothing was published (a local storage fault, not a malformed envelope)` } };
1226
+ return { ok: false, error: { code: "adopt_failed", message: `importHistory could not publish the staged graph for "${scope}": ${thrownText(err)} — nothing was published (a local storage fault, not a malformed envelope)` } };
1226
1227
  }
1227
1228
  if (published !== "published") {
1228
1229
  safeRm(staging);
@@ -1238,7 +1239,7 @@ export class FileFileHistoryStore {
1238
1239
  return { ok: true };
1239
1240
  }
1240
1241
  catch (err) {
1241
- return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${err.message}` } };
1242
+ return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}` } };
1242
1243
  }
1243
1244
  finally {
1244
1245
  safeRm(importJournal);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.23.5",
3
+ "version": "7.24.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": 2395,
4
+ "count": 2398,
5
5
  "exports": {
6
6
  "A2ATaskState": "type",
7
7
  "A2ATaskStateReversal": "type",
@@ -679,6 +679,7 @@
679
679
  "MEMORY_SAFETY": "variable",
680
680
  "MEMORY_SEARCH_SUPERSEDED_TAG": "variable",
681
681
  "MEMORY_SEARCH_TOOL_NAME": "variable",
682
+ "MODEL_ROLES": "variable",
682
683
  "MONITOR_BATCH_WINDOW_MS": "variable",
683
684
  "MONITOR_DEFAULT_TIMEOUT_MS": "variable",
684
685
  "MONITOR_MAX_BATCHES_PER_MINUTE": "variable",
@@ -1867,6 +1868,7 @@
1867
1868
  "devWorkflowScriptRunner": "variable",
1868
1869
  "diffToolRoster": "function",
1869
1870
  "directoryRuleAdmits": "function",
1871
+ "displayUrlOf": "function",
1870
1872
  "distilledEquals": "function",
1871
1873
  "dotAtOrBelowFrontier": "function",
1872
1874
  "drainMemoryAnnouncements": "function",
@@ -2076,7 +2078,6 @@
2076
2078
  "namespacedRuleNameCovers": "function",
2077
2079
  "nextSyncBaseline": "function",
2078
2080
  "normalizeAgentName": "function",
2079
- "normalizeBaseUrl": "function",
2080
2081
  "normalizeForExactMatch": "function",
2081
2082
  "normalizeMemorySpec": "function",
2082
2083
  "normalizePeerName": "function",
@@ -2242,6 +2243,7 @@
2242
2243
  "resolveReadDenyBuiltins": "function",
2243
2244
  "resolveReasoning": "function",
2244
2245
  "resolveReasoningProfile": "function",
2246
+ "resolveRequestRoot": "function",
2245
2247
  "resolveRouteCredential": "function",
2246
2248
  "resolveSubagentTranscriptTier": "function",
2247
2249
  "resolveTaskLimits": "function",
@@ -2255,6 +2257,7 @@
2255
2257
  "retryBackoffMs": "function",
2256
2258
  "riskSeverity": "function",
2257
2259
  "rosterMemberBoundProblem": "function",
2260
+ "routeKeyOf": "function",
2258
2261
  "routePairingStatus": "function",
2259
2262
  "routePeerDeliveryReceipt": "function",
2260
2263
  "routePeerDroppedReceipt": "function",
@@ -3076,6 +3079,7 @@
3076
3079
  "MEMORY_SAFETY": "advanced",
3077
3080
  "MEMORY_SEARCH_SUPERSEDED_TAG": "advanced",
3078
3081
  "MEMORY_SEARCH_TOOL_NAME": "advanced",
3082
+ "MODEL_ROLES": "advanced",
3079
3083
  "MONITOR_BATCH_WINDOW_MS": "advanced",
3080
3084
  "MONITOR_DEFAULT_TIMEOUT_MS": "advanced",
3081
3085
  "MONITOR_MAX_BATCHES_PER_MINUTE": "advanced",
@@ -4264,6 +4268,7 @@
4264
4268
  "devWorkflowScriptRunner": "stable",
4265
4269
  "diffToolRoster": "advanced",
4266
4270
  "directoryRuleAdmits": "advanced",
4271
+ "displayUrlOf": "advanced",
4267
4272
  "distilledEquals": "stable",
4268
4273
  "dotAtOrBelowFrontier": "advanced",
4269
4274
  "drainMemoryAnnouncements": "advanced",
@@ -4473,7 +4478,6 @@
4473
4478
  "namespacedRuleNameCovers": "advanced",
4474
4479
  "nextSyncBaseline": "advanced",
4475
4480
  "normalizeAgentName": "advanced",
4476
- "normalizeBaseUrl": "advanced",
4477
4481
  "normalizeForExactMatch": "internal",
4478
4482
  "normalizeMemorySpec": "advanced",
4479
4483
  "normalizePeerName": "advanced",
@@ -4639,6 +4643,7 @@
4639
4643
  "resolveReadDenyBuiltins": "advanced",
4640
4644
  "resolveReasoning": "advanced",
4641
4645
  "resolveReasoningProfile": "advanced",
4646
+ "resolveRequestRoot": "stable",
4642
4647
  "resolveRouteCredential": "advanced",
4643
4648
  "resolveSubagentTranscriptTier": "advanced",
4644
4649
  "resolveTaskLimits": "internal",
@@ -4652,6 +4657,7 @@
4652
4657
  "retryBackoffMs": "advanced",
4653
4658
  "riskSeverity": "advanced",
4654
4659
  "rosterMemberBoundProblem": "advanced",
4660
+ "routeKeyOf": "advanced",
4655
4661
  "routePairingStatus": "advanced",
4656
4662
  "routePeerDeliveryReceipt": "advanced",
4657
4663
  "routePeerDroppedReceipt": "advanced",