@sema-agent/core 5.12.0 → 5.14.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 (101) hide show
  1. package/CHANGELOG.md +320 -0
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +4 -0
  4. package/dist/agents/subagent.js +137 -43
  5. package/dist/brain/anthropic.js +33 -10
  6. package/dist/brain/context-overflow.d.ts +20 -0
  7. package/dist/brain/context-overflow.js +58 -0
  8. package/dist/brain/open-responses.js +24 -10
  9. package/dist/brain/openai.js +29 -11
  10. package/dist/brain/request-params.d.ts +2 -0
  11. package/dist/brain/request-params.js +16 -0
  12. package/dist/brain/stream-engine.d.ts +9 -1
  13. package/dist/brain/stream-engine.js +256 -27
  14. package/dist/brain/timeout.d.ts +1 -0
  15. package/dist/brain/timeout.js +1 -0
  16. package/dist/core/a2a.d.ts +2 -2
  17. package/dist/core/a2a.js +3 -3
  18. package/dist/core/ask-question.d.ts +47 -2
  19. package/dist/core/ask-question.js +209 -28
  20. package/dist/core/background-agent-store.d.ts +2 -0
  21. package/dist/core/checkpoint-store.d.ts +49 -18
  22. package/dist/core/checkpoint-store.js +117 -4
  23. package/dist/core/compliance.d.ts +11 -0
  24. package/dist/core/compliance.js +34 -0
  25. package/dist/core/governance-codes.d.ts +12 -0
  26. package/dist/core/governance-codes.js +24 -0
  27. package/dist/core/hooks.d.ts +24 -2
  28. package/dist/core/hooks.js +97 -10
  29. package/dist/core/human-input-projection.d.ts +12 -0
  30. package/dist/core/human-input-projection.js +27 -0
  31. package/dist/core/locked-config.d.ts +27 -0
  32. package/dist/core/locked-config.js +42 -0
  33. package/dist/core/mcp.d.ts +7 -2
  34. package/dist/core/mcp.js +7 -7
  35. package/dist/core/memory-admission.d.ts +51 -0
  36. package/dist/core/memory-admission.js +159 -0
  37. package/dist/core/memory.d.ts +2 -0
  38. package/dist/core/memory.js +3 -2
  39. package/dist/core/retention.d.ts +36 -0
  40. package/dist/core/retention.js +31 -0
  41. package/dist/core/runner/assemble-result.d.ts +1 -0
  42. package/dist/core/runner/assemble-result.js +2 -2
  43. package/dist/core/runner/prepare-memory.d.ts +9 -0
  44. package/dist/core/runner/prepare-memory.js +28 -2
  45. package/dist/core/runner/prepare-task.d.ts +18 -6
  46. package/dist/core/runner/prepare-task.js +428 -38
  47. package/dist/core/runner/runtask.d.ts +3 -6
  48. package/dist/core/runner/runtask.js +258 -77
  49. package/dist/core/runner/tool-output-projection.js +1 -0
  50. package/dist/core/session-store.d.ts +4 -0
  51. package/dist/core/session-store.js +5 -0
  52. package/dist/core/session.d.ts +1 -0
  53. package/dist/core/store-contracts/background-agent-store-contract.js +19 -0
  54. package/dist/core/store-contracts/checkpoint-store-contract.js +62 -3
  55. package/dist/core/task-notification.d.ts +2 -0
  56. package/dist/core/task-notification.js +5 -3
  57. package/dist/core/task-registry-agent.d.ts +1 -0
  58. package/dist/core/task-registry-agent.js +6 -0
  59. package/dist/core/task-registry.d.ts +1 -0
  60. package/dist/core/task-registry.js +4 -1
  61. package/dist/core/tool-policy.d.ts +5 -0
  62. package/dist/core/tool-policy.js +2 -1
  63. package/dist/core/tool-result-store.d.ts +2 -0
  64. package/dist/core/tool-result-store.js +1 -0
  65. package/dist/core/types.d.ts +38 -2
  66. package/dist/core/wiring-manifest.d.ts +97 -0
  67. package/dist/core/wiring-manifest.js +186 -0
  68. package/dist/engine/compaction/compaction.js +2 -2
  69. package/dist/engine/harness/agent-harness.d.ts +2 -1
  70. package/dist/engine/harness/agent-harness.js +19 -2
  71. package/dist/engine/harness/types.d.ts +3 -1
  72. package/dist/engine/llm/types.d.ts +7 -0
  73. package/dist/engine/llm/types.js +8 -1
  74. package/dist/engine/llm/validation.js +11 -1
  75. package/dist/engine/session/import-validate.d.ts +6 -1
  76. package/dist/engine/session/import-validate.js +29 -6
  77. package/dist/engine/session/memory-repo.d.ts +3 -1
  78. package/dist/engine/session/memory-repo.js +2 -2
  79. package/dist/index.d.ts +12 -4
  80. package/dist/index.js +12 -4
  81. package/dist/internal/harness-types.d.ts +1 -1
  82. package/dist/internal/llm.d.ts +2 -2
  83. package/dist/internal/llm.js +1 -1
  84. package/dist/orchestration/run-workflow-tool.d.ts +4 -0
  85. package/dist/orchestration/run-workflow-tool.js +3 -0
  86. package/dist/orchestration/workflow-types.d.ts +8 -0
  87. package/dist/orchestration/workflow-types.js +14 -0
  88. package/dist/orchestration/workflow.d.ts +4 -0
  89. package/dist/orchestration/workflow.js +134 -5
  90. package/dist/prompts/default.js +1 -1
  91. package/dist/stores/file/checkpoint-store.d.ts +3 -5
  92. package/dist/stores/file/checkpoint-store.js +31 -2
  93. package/dist/stores/file/index.js +1 -1
  94. package/dist/stores/file/session-store.d.ts +3 -1
  95. package/dist/stores/file/session-store.js +2 -2
  96. package/dist/stores/file/shared-ledger.js +8 -1
  97. package/dist/tools/fs/bash-readonly-classifier.d.ts +3 -0
  98. package/dist/tools/fs/bash-readonly-classifier.js +94 -0
  99. package/dist/tools/fs/fs-bash.js +31 -12
  100. package/dist/tools/fs/safety.js +34 -10
  101. package/package.json +1 -1
@@ -7,10 +7,10 @@ import { hasBackgroundShell } from "../../core/background-shell.js";
7
7
  import { delimitUntrusted } from "../../core/untrusted-text.js";
8
8
  import { MCP_IMAGE_MAX_BASE64 } from "../../core/mcp.js";
9
9
  import { imageMagicMatches, withinAnyRoot } from "./safety.js";
10
- import { isRemoteExecutionEnv } from "../../core/remote-env.js";
10
+ import { isRemoteExecutionEnv, hasDestroy, isIsolated } from "../../core/remote-env.js";
11
11
  import { ghRateLimitHint } from "./gh-rate-limit.js";
12
12
  import { resolveBashTimeoutCaps, bashTimeoutCapsSec, bashMaxOutputChars, clipShellOutput, writeShellOverflowFile, createShellOverflowSpoolFence, shellRecoveryHint, CWD_SENTINEL, } from "./fs-shared.js";
13
- import { BASH_READONLY_DEFAULT_ALLOW, coarseReadonlyCheck, classifyCompoundReadonly, classifySimpleCommandReadBoundary, } from "./bash-readonly-classifier.js";
13
+ import { BASH_READONLY_DEFAULT_ALLOW, coarseReadonlyCheck, classifyBoundedReadonlyPollLoop, classifyCompoundReadonly, classifySimpleCommandReadBoundary, } from "./bash-readonly-classifier.js";
14
14
  export function bashReversibilityProbe(allow, boundary) {
15
15
  const allowSet = new Set(allow ?? BASH_READONLY_DEFAULT_ALLOW);
16
16
  return (args) => {
@@ -18,7 +18,9 @@ export function bashReversibilityProbe(allow, boundary) {
18
18
  if (typeof command !== "string")
19
19
  return { reversible: false };
20
20
  const resolved = typeof boundary === "function" ? boundary() : boundary;
21
- return { reversible: classifyCompoundReadonly(command, allowSet, resolved) === undefined };
21
+ if (classifyCompoundReadonly(command, allowSet, resolved) === undefined)
22
+ return { reversible: true };
23
+ return { reversible: classifyBoundedReadonlyPollLoop(command, allowSet, resolved) === undefined };
22
24
  };
23
25
  }
24
26
  const EXIT1_INTERPRETATION = {
@@ -399,7 +401,22 @@ async function runShell(env, cwd, toolName, command, timeoutSec, caps, signal, c
399
401
  },
400
402
  };
401
403
  }
402
- function bashDescription(coAuthor, caps, bgNotifies = false, bgRetained = false) {
404
+ function bgReapClause(sessionScoped, envIsolatedOwned) {
405
+ if (!sessionScoped)
406
+ return "when this task ends";
407
+ return envIsolatedOwned
408
+ ? "at session end, not when this task ends — UNLESS this task's own isolated sandbox is torn down, which reaps it right then instead"
409
+ : "at session end, not when this task ends: it keeps running across tasks in the same session, and a later task can still read its output or stop it";
410
+ }
411
+ function bgNohupCaveat(envIsolatedOwned) {
412
+ return envIsolatedOwned
413
+ ? " — on a per-task isolated sandbox this only helps if the sandbox itself survives; if the deployment tears it down at task end, host the deliverable outside the sandbox instead"
414
+ : "";
415
+ }
416
+ function bgRetainedIsolatedNote() {
417
+ return " This environment declares retention for background processes — normally one keeps running past this task and the session. The one exception: a per-task isolated sandbox that gets torn down still reaps it right then despite the retention declaration; if the deliverable must survive that too, host it outside the sandbox instead.";
418
+ }
419
+ function bashDescription(coAuthor, caps, bgNotifies = false, bgRetained = false, bgSessionScoped = false, bgEnvIsolatedOwned = false) {
403
420
  const coAuthorLines = coAuthor === false
404
421
  ? "- Follow the deployment's commit-message conventions."
405
422
  : `- End git commit messages with:\nCo-Authored-By: ${coAuthor}`;
@@ -409,7 +426,7 @@ function bashDescription(coAuthor, caps, bgNotifies = false, bgRetained = false)
409
426
  - IMPORTANT: Avoid using this tool to run \`cat\`, \`head\`, \`tail\`, \`sed\`, \`awk\`, or \`echo\` commands, unless explicitly instructed or after you have verified that a dedicated tool cannot accomplish your task. Instead, use the appropriate dedicated tool as this will provide a much better experience for the user.
410
427
  - File search by name: use the Glob tool (NOT \`find\` or \`ls\`); content search: use the Grep tool (NOT \`grep\`/\`rg\` in the shell).
411
428
  - \`timeout\` is in milliseconds: default ${caps.defaultMs}, max ${caps.maxMs}.
412
- - \`run_in_background\` runs the command detached: it keeps running across turns and ${bgNotifies ? "re-invokes you when it exits" : "you read its output later with TaskOutput(task_id)"}. No \`&\` needed.${bgRetained ? "" : " Background processes do NOT survive the session — they are reaped when the task ends. A deliverable that must stay alive afterwards (a server, a daemon) needs a self-detaching FOREGROUND start instead: run `nohup cmd >log 2>&1 &` as a normal foreground command (portable; setsid does not exist on macOS), or use a service manager."}
429
+ - \`run_in_background\` runs the command detached: it keeps running across turns and ${bgNotifies ? "re-invokes you when it exits" : "you read its output later with TaskOutput(task_id)"}. No \`&\` needed.${bgRetained ? (bgEnvIsolatedOwned ? bgRetainedIsolatedNote() : "") : ` Background processes do NOT survive the session — they are reaped ${bgReapClause(bgSessionScoped, bgEnvIsolatedOwned)}. A deliverable that must stay alive afterwards (a server, a daemon) needs a self-detaching FOREGROUND start instead: run \`nohup cmd >log 2>&1 &\` as a normal foreground command (portable; setsid does not exist on macOS), or use a service manager${bgNohupCaveat(bgEnvIsolatedOwned)}.`}
413
430
 
414
431
  # Git
415
432
  - Interactive flags (\`-i\`, e.g. \`git rebase -i\`, \`git add -i\`) are not supported in this environment.
@@ -463,20 +480,22 @@ async function adoptBackgroundShell(req) {
463
480
  return { ok: false, reason, killed: false, killMsg: kill !== undefined && !kill.ok ? kill.error.message : "killBackground threw" };
464
481
  }
465
482
  }
466
- function backgroundLifetimeNote(caps) {
467
- return caps.retainBackgroundProcesses === true
468
- ? ""
469
- : ` NOTE: background processes do NOT survive the session (reaped at task end). If this is a deliverable service that must stay alive afterwards, host it with a FOREGROUND command instead: \`nohup cmd >log 2>&1 &\` (self-detaching — survives the session).`;
483
+ function backgroundLifetimeNote(caps, sessionScoped, envIsolatedOwned) {
484
+ if (caps.retainBackgroundProcesses === true)
485
+ return envIsolatedOwned ? bgRetainedIsolatedNote() : "";
486
+ return ` NOTE: background processes do NOT survive the session (reaped ${bgReapClause(sessionScoped, envIsolatedOwned)}). If this is a deliverable service that must stay alive afterwards, host it with a FOREGROUND command instead: \`nohup cmd >log 2>&1 &\` (self-detaching — survives the session)${bgNohupCaveat(envIsolatedOwned)}.`;
470
487
  }
471
488
  export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = { current: rootCanonical }, taskOpts = {}) {
472
489
  const timeoutCaps = resolveBashTimeoutCaps(taskOpts);
473
490
  const timeoutCapsSecView = bashTimeoutCapsSec(timeoutCaps);
474
491
  const bgNotifies = taskOpts.taskRegistry !== undefined && taskOpts.onTaskNotification !== undefined;
475
492
  const bgRetained = hasBackgroundShell(env) && env.backgroundCapabilities.retainBackgroundProcesses === true;
493
+ const bgSessionScoped = taskOpts.sessionId !== undefined && taskOpts.taskRegistry !== undefined;
494
+ const bgEnvIsolatedOwned = hasDestroy(env) && isIsolated(env);
476
495
  return defineTool({
477
496
  name: "Bash",
478
497
  contract: { contractId: "core.bash@1", implementationRevision: "1" },
479
- description: bashDescription(coAuthor, timeoutCaps, bgNotifies, bgRetained),
498
+ description: bashDescription(coAuthor, timeoutCaps, bgNotifies, bgRetained, bgSessionScoped, bgEnvIsolatedOwned),
480
499
  parameters: Type.Object({
481
500
  command: Type.String({ description: "The command to execute" }),
482
501
  timeout: Type.Optional(Type.Number({ description: `Optional timeout in milliseconds (max ${timeoutCaps.maxMs}; requests above the max are capped to it)` })),
@@ -535,7 +554,7 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
535
554
  ? ` Time budget: requested ${requestedTimeoutSec}s, capped at ${bgCaps.maxBgTimeoutSec}s (env ceiling: requests above ${bgCaps.maxBgTimeoutSec}s are reduced to it) — auto-terminates if still running after ${appliedTimeoutSec}s.`
536
555
  : ` Time budget: auto-terminates if still running after ${appliedTimeoutSec}s (hard cap ${bgCaps.maxBgTimeoutSec}s).`
537
556
  : "";
538
- const lifetimeNote = backgroundLifetimeNote(bgCaps);
557
+ const lifetimeNote = backgroundLifetimeNote(bgCaps, bgSessionScoped, bgEnvIsolatedOwned);
539
558
  const registry = taskOpts.taskRegistry;
540
559
  if (registry !== undefined) {
541
560
  const onNotify = taskOpts.onTaskNotification;
@@ -649,7 +668,7 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
649
668
  ? `You will be notified when it completes — do not poll.`
650
669
  : `Poll TaskOutput until its status is no longer "running".`) +
651
670
  adoptBudgetNote +
652
- backgroundLifetimeNote(adoptCaps) +
671
+ backgroundLifetimeNote(adoptCaps, bgSessionScoped, bgEnvIsolatedOwned) +
653
672
  (tail ? `\n--- output so far (tail) ---\n${tail}` : ""),
654
673
  details: { type: "bash", detached: true, task_id: taskId, ...(description !== undefined ? { description } : {}), ...(outputFile !== undefined ? { output_file: outputFile } : {}), ...(cause === "timeout" ? { autoBackgrounded: true } : {}) },
655
674
  };
@@ -309,15 +309,29 @@ export async function canonicalizeTarget(env, path, signal, baseCwd) {
309
309
  let abs = absR.value;
310
310
  for (let hop = 0; hop < 16; hop++) {
311
311
  const exists = await env.exists(abs, signal);
312
- if (!(exists.ok && exists.value)) {
313
- return { ok: true, key: await canonicalizeNewPath(env, abs, signal) };
312
+ if (!exists.ok) {
313
+ return { ok: false, message: `cannot determine whether "${path}" exists: ${exists.error.message}`, unresolvedSymlink: true };
314
+ }
315
+ if (!exists.value) {
316
+ const newPath = await canonicalizeNewPath(env, abs, signal);
317
+ if (!newPath.ok) {
318
+ return { ok: false, message: newPath.message, unresolvedSymlink: true };
319
+ }
320
+ return { ok: true, key: newPath.key };
314
321
  }
315
322
  const canon = await env.canonicalPath(abs, signal);
316
323
  if (canon.ok)
317
324
  return { ok: true, key: canon.value };
318
325
  const info = await env.fileInfo(abs, signal);
319
- if (!(info.ok && info.value.kind === "symlink")) {
320
- return { ok: true, key: abs };
326
+ if (!info.ok) {
327
+ return { ok: false, message: `cannot determine whether "${path}" is a symlink: ${info.error.message}`, unresolvedSymlink: true };
328
+ }
329
+ if (info.value.kind !== "symlink") {
330
+ const viaAncestors = await canonicalizeNewPath(env, abs, signal, { skipLeaf: true });
331
+ if (!viaAncestors.ok) {
332
+ return { ok: false, message: viaAncestors.message, unresolvedSymlink: true };
333
+ }
334
+ return { ok: true, key: viaAncestors.key };
321
335
  }
322
336
  if (!env.readLink) {
323
337
  return { ok: false, message: `cannot resolve symlink "${path}" (env has no readLink)`, unresolvedSymlink: true };
@@ -328,7 +342,10 @@ export async function canonicalizeTarget(env, path, signal, baseCwd) {
328
342
  }
329
343
  const linkTarget = isAbsolutePathForm(link.value) ? link.value : `${parentDir(abs)}${nativeSepOf(abs)}${link.value}`;
330
344
  const reabs = await env.absolutePath(linkTarget, signal);
331
- abs = reabs.ok ? reabs.value : linkTarget;
345
+ if (!reabs.ok) {
346
+ return { ok: false, message: `cannot resolve symlink target "${linkTarget}" for "${path}": ${reabs.error.message}`, unresolvedSymlink: true };
347
+ }
348
+ abs = reabs.value;
332
349
  }
333
350
  return { ok: false, message: `symlink chain for "${path}" exceeded the hop cap; target unresolved`, unresolvedSymlink: true };
334
351
  }
@@ -339,21 +356,28 @@ function parentDir(abs) {
339
356
  const head = abs.slice(0, i);
340
357
  return /^[A-Za-z]:$/.test(head) ? head + abs[i] : head;
341
358
  }
342
- async function canonicalizeNewPath(env, abs, signal) {
359
+ async function canonicalizeNewPath(env, abs, signal, opts) {
343
360
  const sep = nativeSepOf(abs);
344
361
  const parts = isWinFormPath(abs) ? abs.split(/[\\/]/) : abs.split("/");
345
362
  const tail = [];
363
+ if (opts?.skipLeaf === true && parts.length > 1)
364
+ tail.push(parts.pop());
346
365
  while (parts.length > 1) {
347
366
  const candidate = parts.join(sep) || sep;
348
367
  const ex = await env.exists(candidate, signal);
349
- if (ex.ok && ex.value) {
368
+ if (!ex.ok) {
369
+ return { ok: false, message: `cannot determine whether ancestor "${candidate}" exists: ${ex.error.message}` };
370
+ }
371
+ if (ex.value) {
350
372
  const canon = await env.canonicalPath(candidate, signal);
351
- const baseCanon = canon.ok ? canon.value : candidate;
352
- return [baseCanon, ...tail.reverse()].join(nativeSepOf(baseCanon));
373
+ if (!canon.ok) {
374
+ return { ok: false, message: `cannot canonicalize existing ancestor "${candidate}": ${canon.error.message}` };
375
+ }
376
+ return { ok: true, key: [canon.value, ...tail.reverse()].join(nativeSepOf(canon.value)) };
353
377
  }
354
378
  tail.push(parts.pop());
355
379
  }
356
- return abs;
380
+ return { ok: true, key: abs };
357
381
  }
358
382
  export function violationText(toolName, v) {
359
383
  return `Error (${toolName}): ${v.message}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "5.12.0",
3
+ "version": "5.14.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",