@sema-agent/core 2.3.0 → 2.4.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 (61) hide show
  1. package/dist/agents/send-message-tool.d.ts +4 -0
  2. package/dist/agents/send-message-tool.js +37 -24
  3. package/dist/agents/subagent.js +275 -127
  4. package/dist/brain/errors.d.ts +1 -0
  5. package/dist/brain/errors.js +14 -0
  6. package/dist/brain/stream-engine.js +3 -3
  7. package/dist/core/context-edit.js +2 -1
  8. package/dist/core/runner/prepare-task.js +20 -1
  9. package/dist/core/runner/tool-output-projection.js +2 -1
  10. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +37 -0
  11. package/dist/core/store-contracts/checkpoint-store-contract.js +195 -0
  12. package/dist/core/store-contracts/contract-harness.d.ts +6 -0
  13. package/dist/core/store-contracts/contract-harness.js +16 -0
  14. package/dist/core/store-contracts/contract-kit-version.d.ts +1 -0
  15. package/dist/core/store-contracts/contract-kit-version.js +2 -0
  16. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +3 -0
  17. package/dist/core/store-contracts/file-snapshot-store-contract.js +126 -0
  18. package/dist/core/store-contracts/mailbox-store-contract.d.ts +6 -0
  19. package/dist/core/store-contracts/mailbox-store-contract.js +193 -0
  20. package/dist/core/store-contracts/session-repo-contract.d.ts +3 -0
  21. package/dist/core/store-contracts/session-repo-contract.js +36 -0
  22. package/dist/core/store-contracts/tool-result-store-contract.d.ts +3 -0
  23. package/dist/core/store-contracts/tool-result-store-contract.js +35 -0
  24. package/dist/core/task-notification.d.ts +2 -0
  25. package/dist/core/task-registry-agent.d.ts +4 -0
  26. package/dist/core/task-registry-agent.js +13 -0
  27. package/dist/core/task-registry-monitor.js +6 -6
  28. package/dist/core/task-registry-shared.d.ts +8 -2
  29. package/dist/core/task-registry-shared.js +1 -1
  30. package/dist/core/task-registry.d.ts +6 -0
  31. package/dist/core/task-registry.js +48 -4
  32. package/dist/core/tool-result-store.d.ts +3 -2
  33. package/dist/core/tool-result-store.js +12 -4
  34. package/dist/core/trace.d.ts +7 -0
  35. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
  36. package/dist/engine/lsp/node-lsp-manager.js +16 -0
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.js +1 -0
  39. package/dist/orchestration/builtin-workflows.d.ts +1 -1
  40. package/dist/orchestration/builtin-workflows.js +11 -2
  41. package/dist/orchestration/workflow-governance.d.ts +6 -1
  42. package/dist/orchestration/workflow-governance.js +24 -4
  43. package/dist/orchestration/workflow-primitives.js +7 -1
  44. package/dist/orchestration/workflow.d.ts +1 -0
  45. package/dist/orchestration/workflow.js +31 -2
  46. package/dist/tools/fs/fs-bash.d.ts +7 -1
  47. package/dist/tools/fs/fs-bash.js +51 -20
  48. package/dist/tools/fs/fs-read.js +22 -11
  49. package/dist/tools/fs/fs-search-tools.js +3 -3
  50. package/dist/tools/fs/fs-shared.d.ts +20 -7
  51. package/dist/tools/fs/fs-shared.js +17 -3
  52. package/dist/tools/fs/fs-write.js +4 -4
  53. package/dist/tools/fs/index.d.ts +2 -0
  54. package/dist/tools/fs/index.js +7 -1
  55. package/dist/tools/fs/repo-map.js +2 -2
  56. package/dist/tools/fs/safety.d.ts +10 -0
  57. package/dist/tools/fs/safety.js +15 -1
  58. package/dist/tools/monitor.js +18 -4
  59. package/dist/tools/web.js +6 -2
  60. package/dist/tools/worktree.js +46 -25
  61. package/package.json +1 -1
package/dist/tools/web.js CHANGED
@@ -594,7 +594,7 @@ function classifySearchFailure(message) {
594
594
  if (code === 408 || /\btimed? ?out\b|\betimedout\b/.test(m)) {
595
595
  return { retryable: true, hint: "This looks like a timeout — a retry may succeed; a narrower query tends to return faster." };
596
596
  }
597
- if (/\becconnrefused\b|\benotfound\b|\beai_again\b|\becconnreset\b|\bepipe\b|\bfetch failed\b|socket hang up|\bnetwork\b|\bdns\b/.test(m)) {
597
+ if (/\beconnrefused\b|\benotfound\b|\beai_again\b|\beconnreset\b|\bepipe\b|\bfetch failed\b|socket hang up|\bnetwork\b|\bdns\b/.test(m)) {
598
598
  return { retryable: true, hint: "This is a network/transport fault, not a rejection of the query — one retry is reasonable; if it repeats, treat the search backend as unavailable and continue without it." };
599
599
  }
600
600
  if (code !== undefined && code >= 500 && code <= 599) {
@@ -751,6 +751,7 @@ export function createWebSearchTool(config) {
751
751
  const domainFiltered = usable.filter((r) => webSearchResultAllowed(r.url, allowed_domains, blocked_domains));
752
752
  const droppedByDomain = usable.length - domainFiltered.length;
753
753
  const kept = domainFiltered.slice(0, max);
754
+ const droppedByMax = domainFiltered.length - kept.length;
754
755
  const schemeAssumed = kept.filter((r) => r.schemeAssumed).length;
755
756
  let clipped = false;
756
757
  const shown = kept.map((r) => {
@@ -771,6 +772,9 @@ export function createWebSearchTool(config) {
771
772
  const domainNote = droppedByDomain > 0
772
773
  ? `\n\n[WebSearch: ${droppedByDomain} result(s) were dropped by the ${allowed_domains?.length ? "allowed_domains" : "blocked_domains"} filter passed with this call — the backend returned them, this tool removed them. Relax or drop the filter to see them.]`
773
774
  : "";
775
+ const maxResultsNote = droppedByMax > 0
776
+ ? `\n\n[WebSearch: showing ${kept.length} of ${domainFiltered.length} results — the rest were cut off by this deployment's maxResults limit (${max}).]`
777
+ : "";
774
778
  const clipNote = clipped
775
779
  ? "\n\n[WebSearch: one or more result fields exceeded the display limits and were truncated — treat the text above as an excerpt, not the backend's full result.]"
776
780
  : "";
@@ -780,7 +784,7 @@ export function createWebSearchTool(config) {
780
784
  const tailReminder = shown.length > 0
781
785
  ? "REMINDER: You MUST include the sources above in your response to the user using markdown hyperlinks."
782
786
  : "REMINDER: this search returned ZERO usable sources — there is nothing above to cite. Do not fabricate sources, URLs or citations; tell the user the search returned no usable results (the notes above say why, when there is a why) and answer from what you already know, or try a different query.";
783
- const modelText = `${fenced}${dropNote}${domainNote}${schemeNote}${clipNote}\n\n${tailReminder}`;
787
+ const modelText = `${fenced}${dropNote}${domainNote}${maxResultsNote}${schemeNote}${clipNote}\n\n${tailReminder}`;
784
788
  return {
785
789
  content: modelText,
786
790
  details: {
@@ -164,11 +164,14 @@ export function createWorktreeTools(env, opts) {
164
164
  const originalCwd = previous ? previous.originalCwd : cwdRef.current;
165
165
  session.current = { worktreeDir: target, originalCwd, baseSha: head.value.stdout.trim(), entered: true };
166
166
  cwdRef.current = target;
167
- return (`Switched into the existing worktree at ${target}${previous ? ` (the previous worktree at ${previous.worktreeDir} was left on disk, untouched)` : ""}. ` +
168
- "The session's working directory is now this worktree subsequent commands and relative paths " +
169
- "operate there. Note this is a cwd switch, not a sandbox: file containment stays anchored at the " +
170
- "task root. ExitWorktree will restore the original directory but will NOT remove a worktree " +
171
- "entered via `path`.");
167
+ return {
168
+ content: `Switched into the existing worktree at ${target}${previous ? ` (the previous worktree at ${previous.worktreeDir} was left on disk, untouched)` : ""}. ` +
169
+ "The session's working directory is now this worktree subsequent commands and relative paths " +
170
+ "operate there. Note this is a cwd switch, not a sandbox: file containment stays anchored at the " +
171
+ "task root. ExitWorktree will restore the original directory but will NOT remove a worktree " +
172
+ "entered via `path`.",
173
+ details: { type: "worktree", action: "enter", worktreeDir: target },
174
+ };
172
175
  }
173
176
  const base = await resolveBaseCommit(env, repoRoot, baseRef ?? "fresh");
174
177
  if ("error" in base)
@@ -195,11 +198,14 @@ export function createWorktreeTools(env, opts) {
195
198
  baseSha: base.sha,
196
199
  };
197
200
  cwdRef.current = createdDir;
198
- return (`Created worktree at ${createdDir} (base: ${base.label}${base.note ?? ""}). ` +
199
- "The session's working directory is now the worktree subsequent commands and relative paths " +
200
- "operate there. Note this is a cwd switch, not a sandbox: file containment stays anchored at the " +
201
- "task root, so the rest of the repository remains reachable. " +
202
- "Use ExitWorktree to leave (an unchanged worktree is removed; a changed one is kept).");
201
+ return {
202
+ content: `Created worktree at ${createdDir} (base: ${base.label}${base.note ?? ""}). ` +
203
+ "The session's working directory is now the worktree subsequent commands and relative paths " +
204
+ "operate there. Note this is a cwd switch, not a sandbox: file containment stays anchored at the " +
205
+ "task root, so the rest of the repository remains reachable. " +
206
+ "Use ExitWorktree to leave (an unchanged worktree is removed; a changed one is kept).",
207
+ details: { type: "worktree", action: "create", worktreeDir: createdDir, base: base.label },
208
+ };
203
209
  },
204
210
  });
205
211
  const exit = defineTool({
@@ -245,16 +251,22 @@ export function createWorktreeTools(env, opts) {
245
251
  cwdRef.current = restoredTo;
246
252
  if (active.entered) {
247
253
  const discardNote = discardChanges === true ? " (`discardChanges` does not apply to a worktree this session did not create)" : "";
248
- return (`Exited the worktree session; working directory restored to ${restoredTo}${restoreNote}. ` +
249
- `The worktree at ${active.worktreeDir} was entered via \`path\` (not created by this session) and ` +
250
- `was left on disk, untouched${discardNote}.`);
254
+ return {
255
+ content: `Exited the worktree session; working directory restored to ${restoredTo}${restoreNote}. ` +
256
+ `The worktree at ${active.worktreeDir} was entered via \`path\` (not created by this session) and ` +
257
+ `was left on disk, untouched${discardNote}.`,
258
+ details: { type: "worktree", action: "exit", worktreeDir: active.worktreeDir, kept: true },
259
+ };
251
260
  }
252
261
  const wtThere = await env.exists(active.worktreeDir);
253
262
  if (wtThere.ok && !wtThere.value) {
254
263
  await pruneWorktrees(env, repoRoot).catch(() => undefined);
255
- return (`Exited the worktree session; working directory restored to ${restoredTo}${restoreNote}. ` +
256
- `The worktree at ${active.worktreeDir} was already gone from disk (removed externally, not by this ` +
257
- "session any uncommitted work in it is lost); stale git worktree metadata was pruned.");
264
+ return {
265
+ content: `Exited the worktree session; working directory restored to ${restoredTo}${restoreNote}. ` +
266
+ `The worktree at ${active.worktreeDir} was already gone from disk (removed externally, not by this ` +
267
+ "session — any uncommitted work in it is lost); stale git worktree metadata was pruned.",
268
+ details: { type: "worktree", action: "exit", worktreeDir: active.worktreeDir, kept: false },
269
+ };
258
270
  }
259
271
  let changed = true;
260
272
  let changeDetail = "could not verify the worktree state — keeping it to be safe";
@@ -278,10 +290,13 @@ export function createWorktreeTools(env, opts) {
278
290
  const removeHint = wantsRemove && keep !== true
279
291
  ? " To remove it anyway and permanently discard these changes, re-invoke with discard_changes: true."
280
292
  : "";
281
- return (`Exited the worktree session; working directory restored to ${restoredTo}${restoreNote}. ` +
282
- `The worktree was KEPT at ${active.worktreeDir} (${why}) — inspect or merge it there, and remove it ` +
283
- "later with `git worktree remove` if no longer needed." +
284
- removeHint);
293
+ return {
294
+ content: `Exited the worktree session; working directory restored to ${restoredTo}${restoreNote}. ` +
295
+ `The worktree was KEPT at ${active.worktreeDir} (${why}) — inspect or merge it there, and remove it ` +
296
+ "later with `git worktree remove` if no longer needed." +
297
+ removeHint,
298
+ details: { type: "worktree", action: "exit", worktreeDir: active.worktreeDir, kept: true },
299
+ };
285
300
  }
286
301
  await env.exec(`git worktree remove --force ${shq(active.worktreeDir)}`, { cwd: repoRoot }).catch(() => undefined);
287
302
  await pruneWorktrees(env, repoRoot).catch(() => undefined);
@@ -291,12 +306,18 @@ export function createWorktreeTools(env, opts) {
291
306
  : changeDetail;
292
307
  const stateClause = changed ? `had ${detailForDiscard} (discarded — discardChanges was set)` : "had no changes";
293
308
  if (stillThere.ok && stillThere.value) {
294
- return (`Exited the worktree session; working directory restored to ${restoredTo}${restoreNote}. ` +
295
- `The worktree at ${active.worktreeDir} ${changed ? "was marked for discard" : "had no changes"} but could NOT be removed — it is still on disk; ` +
296
- "remove it manually with `git worktree remove --force` if no longer needed.");
309
+ return {
310
+ content: `Exited the worktree session; working directory restored to ${restoredTo}${restoreNote}. ` +
311
+ `The worktree at ${active.worktreeDir} ${changed ? "was marked for discard" : "had no changes"} but could NOT be removed — it is still on disk; ` +
312
+ "remove it manually with `git worktree remove --force` if no longer needed.",
313
+ details: { type: "worktree", action: "exit", worktreeDir: active.worktreeDir, kept: true },
314
+ };
297
315
  }
298
- return (`Exited the worktree session; working directory restored to ${restoredTo}${restoreNote}. ` +
299
- `The worktree at ${active.worktreeDir} ${stateClause} and was removed.`);
316
+ return {
317
+ content: `Exited the worktree session; working directory restored to ${restoredTo}${restoreNote}. ` +
318
+ `The worktree at ${active.worktreeDir} ${stateClause} and was removed.`,
319
+ details: { type: "worktree", action: "exit", worktreeDir: active.worktreeDir, kept: false },
320
+ };
300
321
  },
301
322
  });
302
323
  return [enter, exit];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",