@sema-agent/core 5.17.0-pre.0 → 5.18.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 (81) hide show
  1. package/CHANGELOG.md +186 -1
  2. package/dist/agents/subagent.d.ts +8 -0
  3. package/dist/agents/subagent.js +93 -2
  4. package/dist/core/ask-question.js +6 -1
  5. package/dist/core/auto-compaction.d.ts +6 -0
  6. package/dist/core/auto-compaction.js +15 -1
  7. package/dist/core/canonical-json.js +176 -14
  8. package/dist/core/checkpoint-store.d.ts +15 -0
  9. package/dist/core/checkpoint-store.js +73 -0
  10. package/dist/core/governance-codes.js +1 -0
  11. package/dist/core/hooks.d.ts +12 -1
  12. package/dist/core/hooks.js +41 -6
  13. package/dist/core/mcp.d.ts +1 -0
  14. package/dist/core/mcp.js +18 -3
  15. package/dist/core/memory-engine/content-origin.d.ts +27 -0
  16. package/dist/core/memory-engine/content-origin.js +38 -0
  17. package/dist/core/memory-engine/engine.d.ts +12 -2
  18. package/dist/core/memory-engine/engine.js +172 -12
  19. package/dist/core/memory-engine/file-backend.d.ts +4 -0
  20. package/dist/core/memory-engine/file-backend.js +25 -3
  21. package/dist/core/memory-engine/index.d.ts +2 -1
  22. package/dist/core/memory-engine/index.js +2 -1
  23. package/dist/core/memory-engine/layout.d.ts +16 -0
  24. package/dist/core/memory-engine/layout.js +90 -2
  25. package/dist/core/memory-engine/sync-client.d.ts +1 -0
  26. package/dist/core/memory-engine/sync-client.js +23 -5
  27. package/dist/core/memory-engine/tools.d.ts +55 -0
  28. package/dist/core/memory-engine/tools.js +307 -0
  29. package/dist/core/memory-engine/types.d.ts +1 -1
  30. package/dist/core/memory.d.ts +4 -0
  31. package/dist/core/memory.js +15 -2
  32. package/dist/core/permission-rule-consent.d.ts +131 -0
  33. package/dist/core/permission-rule-consent.js +307 -0
  34. package/dist/core/permission-rule-model.d.ts +66 -0
  35. package/dist/core/permission-rule-model.js +135 -0
  36. package/dist/core/permission-rule-store.d.ts +89 -0
  37. package/dist/core/permission-rule-store.js +145 -0
  38. package/dist/core/permission-rules.d.ts +3 -2
  39. package/dist/core/permission-rules.js +9 -4
  40. package/dist/core/runner/prepare-memory.d.ts +3 -1
  41. package/dist/core/runner/prepare-memory.js +54 -14
  42. package/dist/core/runner/prepare-task.d.ts +12 -0
  43. package/dist/core/runner/prepare-task.js +270 -32
  44. package/dist/core/runner/runtask.js +27 -1
  45. package/dist/core/runner/tool-output-projection.js +1 -1
  46. package/dist/core/runner/turn-attachments.d.ts +2 -1
  47. package/dist/core/runner/turn-attachments.js +9 -6
  48. package/dist/core/session-reconcile.js +19 -1
  49. package/dist/core/task-registry-agent.js +1 -0
  50. package/dist/core/tool-policy.d.ts +17 -1
  51. package/dist/core/tool-policy.js +64 -8
  52. package/dist/core/tools.js +1 -0
  53. package/dist/core/trace.d.ts +20 -0
  54. package/dist/core/types.d.ts +15 -0
  55. package/dist/core/wiring-manifest.d.ts +5 -1
  56. package/dist/core/wiring-manifest.js +4 -2
  57. package/dist/engine/llm/validation.js +121 -5
  58. package/dist/engine/loop/agent-loop.d.ts +2 -0
  59. package/dist/engine/loop/agent-loop.js +17 -4
  60. package/dist/index.d.ts +7 -2
  61. package/dist/index.js +6 -1
  62. package/dist/prompts/supervisor.d.ts +1 -1
  63. package/dist/prompts/supervisor.js +1 -1
  64. package/dist/stores/file/checkpoint-store.d.ts +1 -0
  65. package/dist/stores/file/checkpoint-store.js +1 -0
  66. package/dist/stores/file/permission-rule-store.d.ts +32 -0
  67. package/dist/stores/file/permission-rule-store.js +213 -0
  68. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
  69. package/dist/tools/fs/bash-readonly-classifier.js +11 -10
  70. package/dist/tools/fs/fs-bash.js +17 -10
  71. package/dist/tools/fs/fs-read.d.ts +1 -1
  72. package/dist/tools/fs/fs-read.js +4 -3
  73. package/dist/tools/fs/fs-shared.d.ts +15 -0
  74. package/dist/tools/fs/fs-shared.js +73 -2
  75. package/dist/tools/fs/fs-write.js +8 -8
  76. package/dist/tools/fs/index.d.ts +1 -0
  77. package/dist/tools/fs/index.js +1 -1
  78. package/dist/tools/fs/safety.d.ts +1 -0
  79. package/dist/tools/fs/safety.js +9 -2
  80. package/dist/tools/web.js +2 -0
  81. package/package.json +1 -1
@@ -14,6 +14,9 @@ export declare function decodeEditBytes(bytes: Uint8Array, path: string): {
14
14
  ok: false;
15
15
  message: string;
16
16
  };
17
+ export declare function tooLargeToEditMessage(path: string, bytes: number): string;
18
+ export declare function truncatedUtf16BodyMessage(tool: string, path: string): string;
19
+ export declare const BASH_READONLY_CONFINEMENT_NOTE = "bash_readonly is confined to the workspace roots; it has no approval path, so the call is refused rather than escalated.";
17
20
  export declare function persistedTextOf(encoded: string | Uint8Array): string;
18
21
  export declare function notReadRefusalText(env: ExecutionEnv, toolName: string, key: string, v: Pick<FsViolation, "code" | "message" | "partialView">, signal?: AbortSignal, fallbackHint?: string): Promise<string>;
19
22
  export declare const MAX_IMAGE_READ_BYTES: number;
@@ -27,6 +30,17 @@ export declare const BASH_DEFAULT_TIMEOUT_SEC = 120;
27
30
  export declare const BASH_MAX_TIMEOUT_SEC = 600;
28
31
  export declare const BASH_DEFAULT_TIMEOUT_MS: number;
29
32
  export declare const BASH_MAX_TIMEOUT_MS: number;
33
+ export declare const MIN_BASH_TIMEOUT_MS = 1000;
34
+ export declare const BASH_TIMEOUT_PLATEAU_END_MS = 1500;
35
+ export declare function envErrorDetail(message: string | undefined): string;
36
+ export declare function bashTimeoutParamDescription(caps: {
37
+ defaultMs: number;
38
+ maxMs: number;
39
+ }, withDefault: boolean): string;
40
+ export declare function bashTimeoutArgRefusal(timeoutMs: number | undefined, caps: {
41
+ defaultMs: number;
42
+ maxMs: number;
43
+ }): string | undefined;
30
44
  export declare function resolveBashTimeoutCaps(opts?: {
31
45
  bashDefaultTimeoutMs?: number;
32
46
  bashMaxTimeoutMs?: number;
@@ -34,6 +48,7 @@ export declare function resolveBashTimeoutCaps(opts?: {
34
48
  defaultMs: number;
35
49
  maxMs: number;
36
50
  };
51
+ export declare function __resetBashTimeoutAnnouncements(): void;
37
52
  export declare function bashTimeoutCapsSec(caps: {
38
53
  defaultMs: number;
39
54
  maxMs: number;
@@ -24,9 +24,16 @@ export function decodeEditBytes(bytes, path) {
24
24
  return { ok: true, value: decodeTextBytes(bytes) };
25
25
  }
26
26
  catch {
27
- return { ok: false, message: `Error (Edit): "${path}" is too large to edit (${formatByteSize(bytes.byteLength)}). Maximum editable file size is ${formatByteSize(MAX_EDIT_BYTES)}.` };
27
+ return { ok: false, message: tooLargeToEditMessage(path, bytes.byteLength) };
28
28
  }
29
29
  }
30
+ export function tooLargeToEditMessage(path, bytes) {
31
+ return `Error (Edit): "${path}" is too large to edit (${formatByteSize(bytes)}). Maximum editable file size is ${formatByteSize(MAX_EDIT_BYTES)}.`;
32
+ }
33
+ export function truncatedUtf16BodyMessage(tool, path) {
34
+ return `Error (${tool}): "${path}" has a truncated UTF-16 body; repair/convert it with bash first.`;
35
+ }
36
+ export const BASH_READONLY_CONFINEMENT_NOTE = "bash_readonly is confined to the workspace roots; it has no approval path, so the call is refused rather than escalated.";
30
37
  export function persistedTextOf(encoded) {
31
38
  return decodeTextBytes(typeof encoded === "string" ? Buffer.from(encoded, "utf8") : encoded).text;
32
39
  }
@@ -52,19 +59,83 @@ export const BASH_DEFAULT_TIMEOUT_SEC = 120;
52
59
  export const BASH_MAX_TIMEOUT_SEC = 600;
53
60
  export const BASH_DEFAULT_TIMEOUT_MS = BASH_DEFAULT_TIMEOUT_SEC * 1000;
54
61
  export const BASH_MAX_TIMEOUT_MS = BASH_MAX_TIMEOUT_SEC * 1000;
62
+ export const MIN_BASH_TIMEOUT_MS = 1000;
63
+ export const BASH_TIMEOUT_PLATEAU_END_MS = 1500;
64
+ function bashTimeoutDefect(ms) {
65
+ if (!Number.isFinite(ms))
66
+ return "must be a finite number of milliseconds";
67
+ if (ms <= 0)
68
+ return `must be a positive number of milliseconds (received ${ms})`;
69
+ if (ms < MIN_BASH_TIMEOUT_MS) {
70
+ return (`is in MILLISECONDS and ${ms} is under one second — if ${ms} seconds was meant, the value is ` +
71
+ `${ms * 1000}. Values below ${MIN_BASH_TIMEOUT_MS}ms all collapse to the same one-second budget`);
72
+ }
73
+ if (ms > MIN_BASH_TIMEOUT_MS && ms < BASH_TIMEOUT_PLATEAU_END_MS) {
74
+ return (`is ${ms}ms, which rounds down to the same one-second budget as ${MIN_BASH_TIMEOUT_MS}ms — the ` +
75
+ `shell's granularity is whole seconds, so write ${MIN_BASH_TIMEOUT_MS} for one second or at least ` +
76
+ `${BASH_TIMEOUT_PLATEAU_END_MS} for two`);
77
+ }
78
+ return undefined;
79
+ }
55
80
  function validTimeoutMs(n) {
56
81
  if (n === undefined || !Number.isFinite(n))
57
82
  return undefined;
58
83
  const floored = Math.floor(n);
59
- return floored >= 1 ? floored : undefined;
84
+ return bashTimeoutDefect(floored) === undefined ? floored : undefined;
85
+ }
86
+ export function envErrorDetail(message) {
87
+ const trimmed = (message ?? "").trim();
88
+ return trimmed === "" ? "the execution environment reported no reason" : trimmed;
89
+ }
90
+ const announcedTimeoutConfig = new Set();
91
+ function announceDiscardedTimeout(knob, raw, usedMs) {
92
+ if (!Number.isFinite(raw) || raw <= 0)
93
+ return;
94
+ const defect = bashTimeoutDefect(Math.floor(raw));
95
+ if (defect === undefined)
96
+ return;
97
+ const line = `${knob}=${raw} was ignored — it ${defect}. Using ${usedMs}ms instead.`;
98
+ if (announcedTimeoutConfig.has(line))
99
+ return;
100
+ announcedTimeoutConfig.add(line);
101
+ console.warn(line);
102
+ }
103
+ export function bashTimeoutParamDescription(caps, withDefault) {
104
+ const bounds = `${withDefault ? `default ${caps.defaultMs}, ` : ""}min ${MIN_BASH_TIMEOUT_MS}, max ${caps.maxMs}`;
105
+ const escape = caps.maxMs >= BASH_TIMEOUT_PLATEAU_END_MS
106
+ ? ` — pass exactly ${MIN_BASH_TIMEOUT_MS} for one second, or ${BASH_TIMEOUT_PLATEAU_END_MS}+ for longer`
107
+ : ` — ${MIN_BASH_TIMEOUT_MS} is the only value this deployment accepts below its ${caps.maxMs}ms ceiling`;
108
+ return (`Timeout in milliseconds (${bounds}). The shell's granularity is whole seconds, so ${MIN_BASH_TIMEOUT_MS} is the ` +
109
+ `smallest accepted value and anything between ${MIN_BASH_TIMEOUT_MS} and ${BASH_TIMEOUT_PLATEAU_END_MS} is REJECTED ` +
110
+ `(it would round back down to ${MIN_BASH_TIMEOUT_MS})${escape}. Requests above the max are capped to it.`);
111
+ }
112
+ export function bashTimeoutArgRefusal(timeoutMs, caps) {
113
+ if (timeoutMs === undefined)
114
+ return undefined;
115
+ const defect = bashTimeoutDefect(timeoutMs);
116
+ if (defect === undefined)
117
+ return undefined;
118
+ return (`Error (Bash): \`timeout\` ${defect}. The command was NOT run. Omit \`timeout\` to use the default ` +
119
+ `budget of ${caps.defaultMs}ms (maximum ${caps.maxMs}ms).`);
60
120
  }
61
121
  export function resolveBashTimeoutCaps(opts) {
62
122
  const defaultMs = validTimeoutMs(opts?.bashDefaultTimeoutMs) ??
63
123
  validTimeoutMs(Number(process.env.BASH_DEFAULT_TIMEOUT_MS)) ??
64
124
  BASH_DEFAULT_TIMEOUT_MS;
65
125
  const maxMs = Math.max(validTimeoutMs(opts?.bashMaxTimeoutMs) ?? validTimeoutMs(Number(process.env.BASH_MAX_TIMEOUT_MS)) ?? BASH_MAX_TIMEOUT_MS, defaultMs);
126
+ if (opts?.bashDefaultTimeoutMs !== undefined)
127
+ announceDiscardedTimeout("bashDefaultTimeoutMs", opts.bashDefaultTimeoutMs, defaultMs);
128
+ else if (process.env.BASH_DEFAULT_TIMEOUT_MS !== undefined)
129
+ announceDiscardedTimeout("BASH_DEFAULT_TIMEOUT_MS", Number(process.env.BASH_DEFAULT_TIMEOUT_MS), defaultMs);
130
+ if (opts?.bashMaxTimeoutMs !== undefined)
131
+ announceDiscardedTimeout("bashMaxTimeoutMs", opts.bashMaxTimeoutMs, maxMs);
132
+ else if (process.env.BASH_MAX_TIMEOUT_MS !== undefined)
133
+ announceDiscardedTimeout("BASH_MAX_TIMEOUT_MS", Number(process.env.BASH_MAX_TIMEOUT_MS), maxMs);
66
134
  return { defaultMs, maxMs };
67
135
  }
136
+ export function __resetBashTimeoutAnnouncements() {
137
+ announcedTimeoutConfig.clear();
138
+ }
68
139
  export function bashTimeoutCapsSec(caps) {
69
140
  return { defaultSec: Math.max(1, Math.round(caps.defaultMs / 1000)), maxSec: Math.max(1, Math.round(caps.maxMs / 1000)) };
70
141
  }
@@ -3,7 +3,7 @@ import { Type } from "typebox";
3
3
  import { defineTool, errorResult } from "../../core/tools.js";
4
4
  import { sha256, resolveKey, violationText, violationDetails, requireRead, checkStale, checkEditMatch, checkNoChange, fileArgPath, resolveQuoteMatch, adaptNewStringQuotes, resolveEscapeMatch, adaptNewStringEscapes, escapeMatchWasAttempted, ESCAPE_MATCH_MISS_NOTE, deletionOldString, countOccurrences, READ_REFUSED_ESCAPE_HINT, } from "./safety.js";
5
5
  import { decodeTextBytes, encodeTextForFile, normalizeEditText, normalizeFileText } from "./encoding.js";
6
- import { MAX_EDIT_BYTES, formatByteSize, decodeEditBytes, persistedTextOf, notReadRefusalText, enoentMessage, FILE_STATE_TRAILER, FILE_PATH_PARAMS, ipynbRedirect, countLines, } from "./fs-shared.js";
6
+ import { MAX_EDIT_BYTES, decodeEditBytes, tooLargeToEditMessage, truncatedUtf16BodyMessage, persistedTextOf, notReadRefusalText, enoentMessage, FILE_STATE_TRAILER, FILE_PATH_PARAMS, ipynbRedirect, countLines, } from "./fs-shared.js";
7
7
  async function gateToolWrite(hook, tool, path, key, content) {
8
8
  if (hook === undefined)
9
9
  return undefined;
@@ -91,21 +91,21 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
91
91
  }
92
92
  const editInfo = await env.fileInfo(r.key, ctx.signal);
93
93
  if (editInfo.ok && editInfo.value.size > MAX_EDIT_BYTES) {
94
- return errorResult(`Error (Edit): "${path}" is too large to edit (${formatByteSize(editInfo.value.size)}). Maximum editable file size is ${formatByteSize(MAX_EDIT_BYTES)}.`);
94
+ return errorResult(tooLargeToEditMessage(path, editInfo.value.size));
95
95
  }
96
96
  if (singleOld === "") {
97
97
  const preBin = await env.readBinaryFile(r.key, ctx.signal);
98
98
  if (!preBin.ok)
99
99
  return errorResult(`Error (Edit): cannot read "${path}": ${preBin.error.message}`);
100
100
  if (preBin.value.byteLength > MAX_EDIT_BYTES) {
101
- return errorResult(`Error (Edit): "${path}" is too large to edit (${formatByteSize(preBin.value.byteLength)}). Maximum editable file size is ${formatByteSize(MAX_EDIT_BYTES)}.`);
101
+ return errorResult(tooLargeToEditMessage(path, preBin.value.byteLength));
102
102
  }
103
103
  const preDecResult = decodeEditBytes(preBin.value, path);
104
104
  if (!preDecResult.ok)
105
105
  return errorResult(preDecResult.message);
106
106
  const preDec = preDecResult.value;
107
107
  if (preDec.malformed)
108
- return errorResult(`Error (Edit): "${path}" has a truncated UTF-16 body; repair/convert it with bash first.`);
108
+ return errorResult(truncatedUtf16BodyMessage("Edit", path));
109
109
  if (preDec.text.trim() !== "")
110
110
  return errorResult(`Error (Edit): Cannot create new file - file already exists.`);
111
111
  const priorRead = state.get(r.key);
@@ -141,14 +141,14 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
141
141
  if (!readBin.ok)
142
142
  return errorResult(`Error (Edit): cannot read "${path}": ${readBin.error.message}`);
143
143
  if (readBin.value.byteLength > MAX_EDIT_BYTES) {
144
- return errorResult(`Error (Edit): "${path}" is too large to edit (${formatByteSize(readBin.value.byteLength)}). Maximum editable file size is ${formatByteSize(MAX_EDIT_BYTES)}.`);
144
+ return errorResult(tooLargeToEditMessage(path, readBin.value.byteLength));
145
145
  }
146
146
  const decodedResult = decodeEditBytes(readBin.value, path);
147
147
  if (!decodedResult.ok)
148
148
  return errorResult(decodedResult.message);
149
149
  const decoded = decodedResult.value;
150
150
  if (decoded.malformed)
151
- return errorResult(`Error (Edit): "${path}" has a truncated UTF-16 body; repair/convert it with bash first.`);
151
+ return errorResult(truncatedUtf16BodyMessage("Edit", path));
152
152
  const original = decoded.text;
153
153
  const entry = state.get(r.key);
154
154
  const readWasTruncated = entry?.truncated === true;
@@ -256,7 +256,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
256
256
  return errorResult(`Error (Write): cannot re-read "${path}" to verify it is unchanged: ${readBin.error.message}`);
257
257
  const decodedPrev = decodeTextBytes(readBin.value);
258
258
  if (decodedPrev.malformed)
259
- return errorResult(`Error (Write): "${path}" has a truncated UTF-16 body; repair/convert it with bash first.`);
259
+ return errorResult(truncatedUtf16BodyMessage("Write", path));
260
260
  const stale = checkStale(state.get(r.key), sha256(decodedPrev.text));
261
261
  if (stale)
262
262
  return errorResult(violationText("Write", stale));
@@ -340,7 +340,7 @@ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additi
340
340
  return errorResult(`Error (NotebookEdit): cannot read "${notebook_path}": ${readBin.error.message}`);
341
341
  const decodedNb = decodeTextBytes(readBin.value);
342
342
  if (decodedNb.malformed)
343
- return errorResult(`Error (NotebookEdit): "${notebook_path}" has a truncated UTF-16 body; repair/convert it with bash first.`);
343
+ return errorResult(truncatedUtf16BodyMessage("NotebookEdit", notebook_path));
344
344
  const nbText = decodedNb.text;
345
345
  const stale = checkStale(state.get(r.key), sha256(nbText));
346
346
  if (stale)
@@ -34,6 +34,7 @@ export interface HandsToolkitOptions {
34
34
  autoBackgroundOnTimeout?: boolean;
35
35
  readImageDownsampler?: ReadImageDownsamplerOption;
36
36
  pdfModelCapabilities?: PdfModelCapabilities;
37
+ readCyberReminder?: boolean;
37
38
  beforeWrite?: BeforeWriteHook;
38
39
  monitorToolActive?: boolean;
39
40
  }
@@ -30,7 +30,7 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
30
30
  ...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}),
31
31
  }, env);
32
32
  const tools = [
33
- createReadFileTool(env, readFileState, rootCanonical, readOnly ? undefined : cwdRef, readFaceRoots, opts.readImageDownsampler, opts.pdfModelCapabilities, bgOutputReadExemption),
33
+ createReadFileTool(env, readFileState, rootCanonical, readOnly ? undefined : cwdRef, readFaceRoots, opts.readImageDownsampler, opts.pdfModelCapabilities, bgOutputReadExemption, opts.readCyberReminder),
34
34
  ];
35
35
  if (!readOnly) {
36
36
  tools.push(createEditFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite), createWriteFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite), createNotebookEditTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite));
@@ -82,4 +82,5 @@ export declare function escapeMatchWasAttempted(oldString: string): boolean;
82
82
  export declare const ESCAPE_MATCH_MISS_NOTE = "\n(note: Edit also tried swapping \\uXXXX escapes and their characters; neither form matched, so the mismatch is likely elsewhere in old_string. Re-read the file and copy the exact surrounding text.)";
83
83
  export declare function adaptNewStringQuotes(matchedOld: string, newString: string): string;
84
84
  export declare function deletionOldString(content: string, oldString: string, newString: string): string;
85
+ export declare const EDIT_ECHO_MAX_CHARS = 200;
85
86
  export declare function checkEditMatch(content: string, oldString: string, replaceAll: boolean, truncated: boolean): FsViolation | undefined;
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { sliceHeadSafe } from "../../core/surrogate-safe-slice.js";
2
3
  export function fileArgPath(args) {
3
4
  const a = args;
4
5
  const fp = a?.file_path;
@@ -577,6 +578,12 @@ export function deletionOldString(content, oldString, newString) {
577
578
  return oldString + "\n";
578
579
  return oldString;
579
580
  }
581
+ export const EDIT_ECHO_MAX_CHARS = 200;
582
+ function boundEditEcho(oldString) {
583
+ if (oldString.length <= EDIT_ECHO_MAX_CHARS)
584
+ return oldString;
585
+ return `${sliceHeadSafe(oldString, EDIT_ECHO_MAX_CHARS)}… (truncated)`;
586
+ }
580
587
  export function checkEditMatch(content, oldString, replaceAll, truncated) {
581
588
  if (oldString === "") {
582
589
  return { code: "invalid", message: "old_string must not be empty." };
@@ -585,13 +592,13 @@ export function checkEditMatch(content, oldString, replaceAll, truncated) {
585
592
  if (n === 0) {
586
593
  return {
587
594
  code: "ambiguous_edit",
588
- message: `String to replace not found in file.${truncated ? " (note: the read was truncated — it may be in the unshown portion)" : ""}\nString: ${oldString}`,
595
+ message: `String to replace not found in file.${truncated ? " (note: the read was truncated — it may be in the unshown portion)" : ""}\nString: ${boundEditEcho(oldString)}`,
589
596
  };
590
597
  }
591
598
  if (n > 1 && !replaceAll) {
592
599
  return {
593
600
  code: "ambiguous_edit",
594
- message: `Found ${n} matches of the string to replace, but replace_all is false. To replace all occurrences, set replace_all to true. To replace only one occurrence, please provide more context to uniquely identify the instance.${truncated ? " (the read was truncated; some matches may be in the unshown portion)" : ""}\nString: ${oldString}`,
601
+ message: `Found ${n} matches of the string to replace, but replace_all is false. To replace all occurrences, set replace_all to true. To replace only one occurrence, please provide more context to uniquely identify the instance.${truncated ? " (the read was truncated; some matches may be in the unshown portion)" : ""}\nString: ${boundEditEcho(oldString)}`,
595
602
  };
596
603
  }
597
604
  return undefined;
package/dist/tools/web.js CHANGED
@@ -251,6 +251,7 @@ export function webFetchToolSpec(config = {}) {
251
251
  : {}),
252
252
  }),
253
253
  effect: "read",
254
+ contentOrigin: "external",
254
255
  execute: async (args, ctx) => {
255
256
  const { url, prompt } = args;
256
257
  const startedAt = Date.now();
@@ -811,6 +812,7 @@ export function createWebSearchTool(config) {
811
812
  blocked_domains: Type.Optional(Type.Array(Type.String(), { description: "Never include search results from these domains" })),
812
813
  }),
813
814
  effect: "read",
815
+ contentOrigin: "external",
814
816
  execute: async (args, ctx) => {
815
817
  const { query, allowed_domains, blocked_domains } = args;
816
818
  const startedAt = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "5.17.0-pre.0",
3
+ "version": "5.18.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",