@sema-agent/core 7.17.7 → 7.19.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 (201) hide show
  1. package/CHANGELOG.md +233 -13
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +1 -0
  4. package/dist/agents/subagent.js +5 -0
  5. package/dist/agents/teacher.js +1 -0
  6. package/dist/agents/verify.js +3 -0
  7. package/dist/brain/anthropic.js +6 -3
  8. package/dist/brain/open-responses.js +10 -4
  9. package/dist/brain/openai.js +5 -2
  10. package/dist/brain/reasoning.d.ts +13 -13
  11. package/dist/brain/reasoning.js +2 -2
  12. package/dist/brain/route-adjudicator.d.ts +18 -0
  13. package/dist/brain/route-adjudicator.js +16 -0
  14. package/dist/brain/stream-engine.js +2 -1
  15. package/dist/brain/stream-shared.d.ts +3 -0
  16. package/dist/brain/stream-shared.js +1 -0
  17. package/dist/core/agent-definition.d.ts +10 -0
  18. package/dist/core/artifact-host.d.ts +41 -4
  19. package/dist/core/artifact-host.js +18 -2
  20. package/dist/core/ask-origin.d.ts +16 -0
  21. package/dist/core/ask-origin.js +8 -0
  22. package/dist/core/auto-mode-arming.d.ts +6 -0
  23. package/dist/core/auto-mode-arming.js +5 -5
  24. package/dist/core/auto-mode-defaults.d.ts +43 -15
  25. package/dist/core/auto-mode-defaults.js +4 -1
  26. package/dist/core/auto-mode.d.ts +184 -59
  27. package/dist/core/auto-mode.js +188 -13
  28. package/dist/core/checkpoint-store.d.ts +23 -2
  29. package/dist/core/checkpoint-store.js +2 -2
  30. package/dist/core/engine-notice.d.ts +77 -1
  31. package/dist/core/engine-notice.js +7 -0
  32. package/dist/core/gate-fold.js +5 -1
  33. package/dist/core/gate-lanes.js +9 -8
  34. package/dist/core/gate-outcome.d.ts +42 -2
  35. package/dist/core/gate-outcome.js +23 -0
  36. package/dist/core/governance-codes.d.ts +1 -1
  37. package/dist/core/governance-codes.js +14 -0
  38. package/dist/core/hook-seats.d.ts +66 -0
  39. package/dist/core/hook-seats.js +37 -0
  40. package/dist/core/hooks.d.ts +1 -1
  41. package/dist/core/read-root-candidate.d.ts +12 -0
  42. package/dist/core/read-root-candidate.js +24 -0
  43. package/dist/core/roles.d.ts +9 -1
  44. package/dist/core/roles.js +4 -0
  45. package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
  46. package/dist/core/runner/assemble-result.d.ts +2 -1
  47. package/dist/core/runner/assemble-result.js +1 -1
  48. package/dist/core/runner/classifier-ladder.d.ts +227 -0
  49. package/dist/core/runner/classifier-ladder.js +214 -0
  50. package/dist/core/runner/contracts.d.ts +71 -13
  51. package/dist/core/runner/decide-continuation.d.ts +1 -3
  52. package/dist/core/runner/decide-continuation.js +1 -7
  53. package/dist/core/runner/deferral-census.d.ts +52 -0
  54. package/dist/core/runner/deferral-census.js +44 -0
  55. package/dist/core/runner/denial-limit-arms.js +2 -2
  56. package/dist/core/runner/derived-route-fallback.d.ts +8 -4
  57. package/dist/core/runner/derived-route-fallback.js +8 -3
  58. package/dist/core/runner/gate-exit.d.ts +12 -0
  59. package/dist/core/runner/gate-exit.js +6 -3
  60. package/dist/core/runner/prepare-artifact.d.ts +10 -2
  61. package/dist/core/runner/prepare-artifact.js +5 -4
  62. package/dist/core/runner/prepare-ask-lane.js +1 -0
  63. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  64. package/dist/core/runner/prepare-caps-and-workflow.js +43 -55
  65. package/dist/core/runner/prepare-config-doors.d.ts +7 -1
  66. package/dist/core/runner/prepare-config-doors.js +13 -13
  67. package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
  68. package/dist/core/runner/prepare-defer-classify.js +8 -3
  69. package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
  70. package/dist/core/runner/prepare-gate-stations.js +1 -1
  71. package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
  72. package/dist/core/runner/prepare-hands-readface.js +60 -24
  73. package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
  74. package/dist/core/runner/prepare-inherited-gate.js +10 -2
  75. package/dist/core/runner/prepare-lsp.d.ts +2 -2
  76. package/dist/core/runner/prepare-lsp.js +1 -1
  77. package/dist/core/runner/prepare-policy-chain.js +2 -0
  78. package/dist/core/runner/prepare-project-context.d.ts +5 -1
  79. package/dist/core/runner/prepare-project-context.js +7 -4
  80. package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
  81. package/dist/core/runner/prepare-prompt-inputs.js +4 -1
  82. package/dist/core/runner/prepare-run-refs.d.ts +9 -1
  83. package/dist/core/runner/prepare-run-refs.js +3 -2
  84. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
  85. package/dist/core/runner/prepare-suspend-saga.js +5 -2
  86. package/dist/core/runner/prepare-task.js +19 -14
  87. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
  88. package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
  89. package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
  90. package/dist/core/runner/prepare-turn-wiring.js +3 -2
  91. package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
  92. package/dist/core/runner/prepare-wiring-manifest.js +5 -2
  93. package/dist/core/runner/resume-apply.js +3 -3
  94. package/dist/core/runner/resume-checkpoint-screen.js +7 -0
  95. package/dist/core/runner/run-harness-handlers.js +4 -1
  96. package/dist/core/runner/run-identity-wiring.js +6 -2
  97. package/dist/core/runner/run-leg.d.ts +2 -0
  98. package/dist/core/runner/run-leg.js +45 -45
  99. package/dist/core/runner/run-notification-lane.js +55 -27
  100. package/dist/core/runner/run-settle-and-teardown.js +1 -0
  101. package/dist/core/runner/run-terminal-adoption.js +16 -13
  102. package/dist/core/runner/run-turn-boundary.js +15 -5
  103. package/dist/core/runner/runtask.d.ts +6 -5
  104. package/dist/core/runner/runtask.js +26 -14
  105. package/dist/core/runner/skill-base-dir.d.ts +54 -0
  106. package/dist/core/runner/skill-base-dir.js +42 -0
  107. package/dist/core/runner/synthetic-tools.d.ts +83 -48
  108. package/dist/core/runner/synthetic-tools.js +69 -32
  109. package/dist/core/runner/tool-defer-gate.d.ts +22 -9
  110. package/dist/core/runner/tool-defer-gate.js +15 -5
  111. package/dist/core/runner/tool-disclosure.d.ts +5 -0
  112. package/dist/core/runner/tool-disclosure.js +2 -0
  113. package/dist/core/runner/tool-progress-lane.d.ts +42 -0
  114. package/dist/core/runner/tool-progress-lane.js +61 -0
  115. package/dist/core/runner/turn-attachments.d.ts +43 -12
  116. package/dist/core/runner/turn-attachments.js +58 -14
  117. package/dist/core/runner-deps.d.ts +48 -7
  118. package/dist/core/sensitive-path-policy.d.ts +58 -17
  119. package/dist/core/sensitive-path-policy.js +33 -5
  120. package/dist/core/skill-listing-budget.d.ts +112 -0
  121. package/dist/core/skill-listing-budget.js +58 -0
  122. package/dist/core/swappable-deps.d.ts +9 -7
  123. package/dist/core/swappable-deps.js +7 -3
  124. package/dist/core/task-event.d.ts +96 -29
  125. package/dist/core/task-notification.d.ts +142 -16
  126. package/dist/core/task-notification.js +106 -0
  127. package/dist/core/task-result.d.ts +9 -1
  128. package/dist/core/task-spec.d.ts +46 -3
  129. package/dist/core/tighten-task-spec.js +7 -5
  130. package/dist/core/tool-model-gate.d.ts +1 -1
  131. package/dist/core/tool-policy.d.ts +19 -4
  132. package/dist/core/tool-policy.js +6 -7
  133. package/dist/core/tool-search-setting.d.ts +78 -0
  134. package/dist/core/tool-search-setting.js +90 -0
  135. package/dist/core/tool-spec.d.ts +45 -4
  136. package/dist/core/types.d.ts +2 -2
  137. package/dist/core/types.js +1 -1
  138. package/dist/core/wiring-manifest.d.ts +46 -0
  139. package/dist/core/wiring-manifest.js +9 -1
  140. package/dist/core/write-protect.d.ts +8 -3
  141. package/dist/core/write-protect.js +8 -5
  142. package/dist/engine/execution-env/node-execution-env.js +10 -4
  143. package/dist/engine/harness/agent-harness.d.ts +42 -0
  144. package/dist/engine/harness/agent-harness.js +61 -29
  145. package/dist/engine/loop/agent-loop.d.ts +68 -3
  146. package/dist/engine/loop/agent-loop.js +109 -16
  147. package/dist/index.d.ts +13 -8
  148. package/dist/index.js +11 -7
  149. package/dist/internal/harness.d.ts +1 -1
  150. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  151. package/dist/orchestration/run-workflow-tool.js +6 -1
  152. package/dist/orchestration/workflow-governance.d.ts +7 -1
  153. package/dist/orchestration/workflow-governance.js +9 -5
  154. package/dist/orchestration/workflow-primitives.d.ts +3 -0
  155. package/dist/orchestration/workflow-primitives.js +6 -1
  156. package/dist/orchestration/workflow-types.d.ts +48 -13
  157. package/dist/orchestration/workflow-types.js +1 -1
  158. package/dist/orchestration/workflow.js +45 -17
  159. package/dist/prompt-assembly/epoch.js +2 -0
  160. package/dist/prompt-assembly/packs/sema-default.js +2 -2
  161. package/dist/prompt-assembly/section-usage.d.ts +26 -0
  162. package/dist/prompt-assembly/section-usage.js +10 -0
  163. package/dist/prompt-assembly/types.d.ts +8 -6
  164. package/dist/prompts/default.d.ts +13 -0
  165. package/dist/prompts/default.js +16 -8
  166. package/dist/tools/artifact/artifact-text.d.ts +10 -1
  167. package/dist/tools/artifact/artifact-text.js +6 -3
  168. package/dist/tools/artifact/artifact-tool.d.ts +12 -2
  169. package/dist/tools/artifact/artifact-tool.js +203 -70
  170. package/dist/tools/artifact/conformance.d.ts +56 -0
  171. package/dist/tools/artifact/conformance.js +349 -0
  172. package/dist/tools/artifact/local-stub.js +1 -1
  173. package/dist/tools/artifact/reject-breaker.d.ts +28 -0
  174. package/dist/tools/artifact/reject-breaker.js +42 -0
  175. package/dist/tools/fs/bash-lexer.d.ts +132 -0
  176. package/dist/tools/fs/bash-lexer.js +563 -0
  177. package/dist/tools/fs/bash-program-position.d.ts +142 -0
  178. package/dist/tools/fs/bash-program-position.js +125 -0
  179. package/dist/tools/fs/bash-readonly-classifier.d.ts +81 -16
  180. package/dist/tools/fs/bash-readonly-classifier.js +180 -558
  181. package/dist/tools/fs/containment-face.d.ts +89 -0
  182. package/dist/tools/fs/containment-face.js +81 -0
  183. package/dist/tools/fs/fs-bash.d.ts +46 -1
  184. package/dist/tools/fs/fs-bash.js +165 -25
  185. package/dist/tools/fs/fs-read.d.ts +1 -1
  186. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  187. package/dist/tools/fs/fs-write.d.ts +4 -3
  188. package/dist/tools/fs/fs-write.js +9 -9
  189. package/dist/tools/fs/index.d.ts +24 -5
  190. package/dist/tools/fs/index.js +18 -6
  191. package/dist/tools/fs/read-deny.d.ts +4 -3
  192. package/dist/tools/fs/repo-map.d.ts +2 -2
  193. package/dist/tools/fs/safety.d.ts +18 -3
  194. package/dist/tools/fs/safety.js +11 -4
  195. package/package.json +1 -1
  196. package/test/export-surface.snapshot.json +99 -13
  197. package/wire-changes/core@7.17.7.json +49 -0
  198. package/wire-changes/core@7.18.0.json +1273 -0
  199. package/wire-changes/core@7.19.0.json +457 -0
  200. package/dist/tools/fs/read-face.d.ts +0 -68
  201. package/dist/tools/fs/read-face.js +0 -47
@@ -86,7 +86,7 @@ async function gateToolWrite(hook, tool, path, key, content) {
86
86
  return `Error (${tool}): write rejected by the write gate (${res.code}): ${clipHookText(res.reason)}`;
87
87
  return undefined;
88
88
  }
89
- export function createEditFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited) {
89
+ export function createEditFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited, writeFace) {
90
90
  return defineTool({
91
91
  name: "Edit",
92
92
  ...toolFace("edit"),
@@ -111,7 +111,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
111
111
  const path = fileArgPath(args);
112
112
  if (path === undefined)
113
113
  return { ok: true };
114
- const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots);
114
+ const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
115
115
  if (!r.ok)
116
116
  return { ok: true };
117
117
  if (a.old_string === a.new_string)
@@ -149,7 +149,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
149
149
  const path = fileArgPath(args);
150
150
  if (path === undefined)
151
151
  return errorResult(`Error (Edit): file_path is required.`);
152
- const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots);
152
+ const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
153
153
  if (!r.ok)
154
154
  return errorResult(violationText("Edit", r.violation), violationDetails(r.violation));
155
155
  if (!batch && a.old_string === a.new_string) {
@@ -324,7 +324,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
324
324
  },
325
325
  });
326
326
  }
327
- export function createWriteFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited) {
327
+ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited, writeFace) {
328
328
  return defineTool({
329
329
  name: "Write",
330
330
  ...toolFace("write"),
@@ -343,7 +343,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
343
343
  return { ok: true };
344
344
  if (ipynbRedirect("Write", path))
345
345
  return { ok: true };
346
- const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots);
346
+ const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
347
347
  if (!r.ok)
348
348
  return { ok: true };
349
349
  const exists = await env.exists(r.key, ctx.signal);
@@ -362,7 +362,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
362
362
  const ipynb = ipynbRedirect("Write", path);
363
363
  if (ipynb)
364
364
  return errorResult(ipynb);
365
- const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots);
365
+ const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
366
366
  if (!r.ok)
367
367
  return errorResult(violationText("Write", r.violation), violationDetails(r.violation));
368
368
  const exists = await env.exists(r.key, ctx.signal);
@@ -423,7 +423,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
423
423
  const notebookNotIpynbMessage = (notebookPath) => `Error (NotebookEdit): "${notebookPath}" is not a .ipynb file; use Edit for other file types.`;
424
424
  const NOTEBOOK_CELL_TYPE_REQUIRED_MESSAGE = `Error (NotebookEdit): Cell type is required when using edit_mode=insert.`;
425
425
  const NOTEBOOK_CELL_ID_REQUIRED_MESSAGE = `Error (NotebookEdit): cell_id is required for replace/delete.`;
426
- export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited) {
426
+ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited, writeFace) {
427
427
  return defineTool({
428
428
  name: "NotebookEdit",
429
429
  ...toolFace("notebook-edit"),
@@ -457,7 +457,7 @@ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additi
457
457
  return { ok: false, code: "invalid", message: NOTEBOOK_CELL_TYPE_REQUIRED_MESSAGE };
458
458
  if (mode !== "insert" && !a.cell_id)
459
459
  return { ok: false, code: "invalid", message: NOTEBOOK_CELL_ID_REQUIRED_MESSAGE };
460
- const r = await resolveKey(env, rootCanonical, a.notebook_path, ctx.signal, cwdRef?.current, additionalRoots);
460
+ const r = await resolveKey(env, rootCanonical, a.notebook_path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
461
461
  if (!r.ok)
462
462
  return { ok: true };
463
463
  const notRead = requireRead(state, r.key);
@@ -477,7 +477,7 @@ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additi
477
477
  return errorResult(NOTEBOOK_CELL_TYPE_REQUIRED_MESSAGE);
478
478
  if (mode !== "insert" && !cell_id)
479
479
  return errorResult(NOTEBOOK_CELL_ID_REQUIRED_MESSAGE);
480
- const r = await resolveKey(env, rootCanonical, notebook_path, ctx.signal, cwdRef?.current, additionalRoots);
480
+ const r = await resolveKey(env, rootCanonical, notebook_path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
481
481
  if (!r.ok)
482
482
  return errorResult(violationText("NotebookEdit", r.violation), violationDetails(r.violation));
483
483
  const notRead = requireRead(state, r.key);
@@ -14,7 +14,8 @@ export * from "./read-deny.js";
14
14
  import { type BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
15
15
  export { BASH_CLASSIFY_DEFAULT_ALLOW } from "./bash-readonly-classifier.js";
16
16
  import { type ReadDenyEntry } from "./read-deny.js";
17
- export * from "./read-face.js";
17
+ import { type ContainmentFace } from "./containment-face.js";
18
+ export * from "./containment-face.js";
18
19
  import { type CwdRef, type ReadImageDownsamplerOption } from "./fs-shared.js";
19
20
  /** Options for {@link createHandsToolkit}. */
20
21
  export interface HandsToolkitOptions {
@@ -108,6 +109,14 @@ export interface HandsToolkitOptions {
108
109
  * by the Runner for every task and so cannot double as an opt-in signal. Default undefined ⇒ `false`
109
110
  * downstream. */
110
111
  autoBackgroundOnTimeout?: boolean;
112
+ /** The run's tool-progress sink — see createBashTool's taskOpts field of the same name. Runner-wired
113
+ * (a late-bound cell behind the closure), absent for a standalone toolkit build ⇒ the shell's execute
114
+ * leg opens no streaming callbacks at all and its `env.exec` options are byte-identical to before. */
115
+ onToolProgress?: (body: import("../../core/runner/contracts.js").ToolProgressBody) => void;
116
+ /** Is a PERSON's input waiting for this run's next turn boundary — see createBashTool's taskOpts field
117
+ * of the same name. Runner-wired (reads the harness's own accepted-but-unserved account); absent ⇒ the
118
+ * shell never auto-backgrounds a running command, exactly as before. */
119
+ queuedHumanInput?: () => boolean;
111
120
  /** Read-image downsampler seam ({@link ReadImageDownsamplerOption}). Default undefined = auto-detect
112
121
  * the optional sharp dependency once per process; `false` disables it (deterministic no-sharp behavior). */
113
122
  readImageDownsampler?: ReadImageDownsamplerOption;
@@ -164,16 +173,26 @@ export interface HandsToolkitOptions {
164
173
  * time (never silently ignored). Deployment seat ONLY, same clamp argument as the tiers key. */
165
174
  readDenyBuiltinExclude?: readonly string[];
166
175
  /** The READ-face containment state for the structured read faces
167
- * ({@link import("./read-face.js").ReadFace}; resolved through the SAME
168
- * {@link import("./read-face.js").resolveReadFace} order prepare-task uses). Absent ⇒ the
176
+ * ({@link import("./containment-face.js").ContainmentFace}; resolved through the SAME
177
+ * {@link import("./containment-face.js").resolveContainmentFace} order prepare-task uses). Absent ⇒ the
169
178
  * resolution order's default = "roots" (the engine never opens implicitly). "open" skips ONLY the
170
179
  * roots containment judgment — the deny set, the UNC out-of-set refusal and the special-file type
171
180
  * gates run in both faces. Beside `readOnly: true` the mount wins: this seat is deps-shaped (a
172
181
  * deployment default, not a per-call assertion), so its "open" CLAMPS to "roots" and the clamp is
173
182
  * ANNOUNCED (`config.read_face_deployment_clamped`, through `onNotice` / `console.warn`) — the loud
174
183
  * refusal belongs to the TASK seat (`TaskSpec.readFace`), the one genuine per-call contradiction.
175
- * Never affects the write faces. */
176
- readFace?: "open" | "roots";
184
+ * Never affects the write faces — {@link writeFace} is a separate seat with its own default. */
185
+ readFace?: ContainmentFace;
186
+ /** The WRITE-face containment state for the structured write faces (Edit / Write / NotebookEdit)
187
+ * ({@link import("./containment-face.js").ContainmentFace}; resolved through the SAME
188
+ * {@link import("./containment-face.js").resolveContainmentFace} order — one body, one default,
189
+ * per seat). Absent ⇒ "roots" (today's behavior, byte-identical). "open" skips ONLY the roots
190
+ * containment judgment on the write band — the write-side sensitive-path policy, the blocked-device
191
+ * gate, the UNC out-of-set refusal and the read-before-edit requirement all still run, and the full
192
+ * `bash` face is unaffected either way (it was never fenced). Beside `readOnly: true` the mount
193
+ * wins the same way the read seat's does (that mount carries no write faces at all), announced as
194
+ * `config.write_face_deployment_clamped`. Never affects the read faces. */
195
+ writeFace?: ContainmentFace;
177
196
  /** The session's reminder provenance mark: the band's engine-minted
178
197
  * `<system-reminder>` open tags (the Read cyber/dedup/offset/empty reminders and the gh
179
198
  * rate-limit hint) carry it as `mark="…"`, matching the system-prompt declaration the Runner
@@ -14,8 +14,8 @@ export * from "./read-deny.js";
14
14
  import { BASH_READONLY_DEFAULT_ALLOW, } from "./bash-readonly-classifier.js";
15
15
  export { BASH_CLASSIFY_DEFAULT_ALLOW } from "./bash-readonly-classifier.js";
16
16
  import { compileReadDeny } from "./read-deny.js";
17
- import { deploymentReadFaceClampNotice, resolveReadFace } from "./read-face.js";
18
- export * from "./read-face.js";
17
+ import { deploymentFaceClampNotice, resolveContainmentFace } from "./containment-face.js";
18
+ export * from "./containment-face.js";
19
19
  import {} from "./fs-shared.js";
20
20
  import { createReadFileTool } from "./fs-read.js";
21
21
  import { createEditFileTool, createWriteFileTool, createNotebookEditTool } from "./fs-write.js";
@@ -42,18 +42,27 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
42
42
  ...(opts.readDenyBuiltinTiers !== undefined ? { tiers: opts.readDenyBuiltinTiers } : {}),
43
43
  ...(opts.readDenyBuiltinExclude !== undefined ? { exclude: opts.readDenyBuiltinExclude } : {}),
44
44
  });
45
- const readFace = resolveReadFace({
46
- depsReadFace: opts.readFace,
45
+ const readFace = resolveContainmentFace({
46
+ kind: "read",
47
+ depsFace: opts.readFace,
47
48
  readOnlyMount: readOnly,
48
49
  orgGoverned: false,
49
50
  fullShellReachable: includeShell && !readOnly,
50
- onDeploymentClamp: () => deliverEngineNotice(opts.onNotice, deploymentReadFaceClampNotice()),
51
+ onDeploymentClamp: () => deliverEngineNotice(opts.onNotice, deploymentFaceClampNotice("read")),
52
+ });
53
+ const writeFace = resolveContainmentFace({
54
+ kind: "write",
55
+ depsFace: opts.writeFace,
56
+ readOnlyMount: readOnly,
57
+ orgGoverned: false,
58
+ fullShellReachable: includeShell && !readOnly,
59
+ onDeploymentClamp: () => deliverEngineNotice(opts.onNotice, deploymentFaceClampNotice("write")),
51
60
  });
52
61
  const tools = [
53
62
  createReadFileTool(env, readFileState, rootCanonical, readOnly ? undefined : cwdRef, readFaceRoots, opts.readImageDownsampler, opts.pdfModelCapabilities, bgOutputReadExemption, opts.readCyberReminder, readDeny, readFace, opts.reminderMark, opts.reminderDisclosureCounts),
54
63
  ];
55
64
  if (handsWriteHalfMounts({ readOnly })) {
56
- tools.push(createEditFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited), createWriteFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited), createNotebookEditTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited));
65
+ tools.push(createEditFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFace), createWriteFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFace), createNotebookEditTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFace));
57
66
  }
58
67
  tools.push(createGrepTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef), createGlobTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef), createRepoMapTool(env, rootCanonical, readFaceRoots, readDeny, readFace));
59
68
  if (includeShell) {
@@ -63,6 +72,7 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
63
72
  ...(opts.bashMaxTimeoutMs !== undefined ? { bashMaxTimeoutMs: opts.bashMaxTimeoutMs } : {}),
64
73
  ...(opts.onNotice !== undefined ? { onNotice: opts.onNotice } : {}),
65
74
  ...(readFaceRoots !== undefined ? { additionalRoots: readFaceRoots } : {}),
75
+ ...(opts.onToolProgress !== undefined ? { onToolProgress: opts.onToolProgress } : {}),
66
76
  })
67
77
  : createBashTool(env, rootCanonical, commitCoAuthor, cwdRef, {
68
78
  taskRegistry: opts.taskRegistry,
@@ -72,6 +82,8 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
72
82
  onTaskNotification: opts.taskNotification,
73
83
  detachHub: opts.detachHub,
74
84
  ...(opts.autoBackgroundOnTimeout !== undefined ? { autoBackgroundOnTimeout: opts.autoBackgroundOnTimeout } : {}),
85
+ ...(opts.onToolProgress !== undefined ? { onToolProgress: opts.onToolProgress } : {}),
86
+ ...(opts.queuedHumanInput !== undefined ? { queuedHumanInput: opts.queuedHumanInput } : {}),
75
87
  ...(opts.oneShot !== undefined ? { oneShot: opts.oneShot } : {}),
76
88
  ...(additionalRoots !== undefined ? { additionalRoots } : {}),
77
89
  ...(opts.shellReadBoundary !== undefined ? { readBoundary: opts.shellReadBoundary } : {}),
@@ -104,11 +104,12 @@ export interface ReadDenyBuiltinRow {
104
104
  * `.sema`/`.claude` also hold agents, skills, hooks and (under the shell's layout,
105
105
  * `~/.sema/engine-data`) the engine data root with the memory library — material the model reads
106
106
  * and edits as ordinary work, and a directory-wide row would need a data-root passthrough (one more
107
- * rule) to keep it readable. The write face guards the directories (`sensitive-path-policy.ts`): a
108
- * write there is self-authorization; a read of a non-credential file there is debugging. Token
107
+ * rule) to keep it readable. Token
109
108
  * STORES of the same tooling (`.credentials.json`, an agent CLI's `auth.json`, …) are credentials rows, not
110
109
  * agent-config rows: a deployment that switches this tier off to debug its agent configuration must
111
- * not also expose the OAuth stores beside it.
110
+ * not also expose the OAuth stores beside it. These rows are ALSO the opt-in write deny set's
111
+ * agent-configuration block ({@link import("../../core/sensitive-path-policy.js").agentConfigDenyPatterns},
112
+ * which reads them from here; contract sentence there), so a row added here is judged by both faces.
112
113
  */
113
114
  export declare const READ_FACE_BUILTIN_DENY_TABLE: readonly ReadDenyBuiltinRow[];
114
115
  /**
@@ -1,7 +1,7 @@
1
1
  import type { AgentTool } from "../../internal/harness-types.js";
2
2
  import type { ExecutionEnv } from "../../internal/harness-types.js";
3
3
  import type { ReadDenyMatcher } from "./read-deny.js";
4
- import type { ReadFace } from "./read-face.js";
4
+ import type { ContainmentFace } from "./containment-face.js";
5
5
  /** Extract top-level symbol names (deduped, in first-seen order) from source text. A lightweight
6
6
  * comment/docstring state machine skips column-0 declaration-like lines that are really inside a
7
7
  * `/* … *​/` block comment or a Python `"""`/`'''` docstring — otherwise e.g. a module docstring
@@ -13,4 +13,4 @@ import type { ReadFace } from "./read-face.js";
13
13
  * (src/core/auto-promote.ts) consumes it in ESCALATE-ONLY mode — a clean scan contributes nothing;
14
14
  * it can only ever raise an escalation, never clear one. */
15
15
  export declare function extractSymbols(text: string): string[];
16
- export declare function createRepoMapTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ReadFace): AgentTool;
16
+ export declare function createRepoMapTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ContainmentFace): AgentTool;
@@ -258,6 +258,21 @@ export declare function isBinaryContent(sample: string): boolean;
258
258
  * test with no verdict of its own.
259
259
  */
260
260
  export declare function isUncPath(path: string): boolean;
261
+ /**
262
+ * True when a WIN-FORM spelling carries a component Win32's own normalization would trim — a trailing dot
263
+ * or space on anything but the `.`/`..` specials (the same client-side strip {@link uncCanonicalKey}
264
+ * folds, stated once and read here rather than re-derived).
265
+ *
266
+ * Such a spelling is an ALIAS: `x. ` and `x` name ONE directory to the OS and two different strings to a
267
+ * lexical comparator. A caller that must reason about a spelling byte-wise — a directory offered to a
268
+ * person as the thing to declare, a common-prefix cut between two operands — cannot pick one of the two
269
+ * readings on the person's behalf, so it withholds. POSIX paths are never aliased this way: a trailing dot
270
+ * or space there is an ordinary filename character.
271
+ *
272
+ * The UNC ANCHOR is exempt for the reason the key minter states: a host's trailing dot is DNS (the FQDN
273
+ * root) and share-name normalization is the server's business; neither is the client-side strip.
274
+ */
275
+ export declare function hasWin32AliasedComponent(p: string): boolean;
261
276
  /** The two spelling families a tree can be in. Read from the ROOT's spelling (the cwd's when a deployment
262
277
  * supplies no root) — never from `process.platform`: the exec env may be remote and of the other family. */
263
278
  export type PathFamily = "win" | "posix";
@@ -392,7 +407,7 @@ export declare function resolveKey(env: ExecutionEnv, rootCanonical: string, pat
392
407
  pattern: string;
393
408
  matchedView?: string;
394
409
  } | null;
395
- }, readFace?: "open" | "roots"): Promise<{
410
+ }, containmentFace?: "open" | "roots"): Promise<{
396
411
  ok: true;
397
412
  key: string;
398
413
  } | {
@@ -458,7 +473,7 @@ export declare function withinAnyRoot(rootsCanonical: readonly string[], p: stri
458
473
  *
459
474
  * The card names the SANCTIONED way out and nothing else: widening the declaration
460
475
  * (`additionalDirectories` / read-only `additionalReadDirectories`), or the deployment-level
461
- * `readFace: "open"` for reads.
476
+ * `readFace: "open"` (reads) / `writeFace: "open"` (writes).
462
477
  *
463
478
  * It deliberately does NOT name the shell as unconfined by this fence, even though that is a true
464
479
  * statement rather than a recommendation. A live retest settled the question: a refused agent read
@@ -469,7 +484,7 @@ export declare function withinAnyRoot(rootsCanonical: readonly string[], p: stri
469
484
  * shell is deliberately unconfined by this fence, and every shell call still passes the deployment's
470
485
  * approval policy); it is simply not handed to the model inside a denial.
471
486
  */
472
- export declare const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories \u2014 or additionalReadDirectories for read-only access; a deployment that wants reads open everywhere can declare readFace: \"open\" instead of listing directories.)";
487
+ export declare const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories \u2014 or additionalReadDirectories for read-only access; a deployment that wants reads or writes open everywhere can declare readFace / writeFace: \"open\" instead of listing directories.)";
473
488
  /** inv 1 (read-before-edit): a file must have been read this task before it can be edited/overwritten.
474
489
  * Message is CC 2.1.198 live-verbatim (all-tools-live-probe 2026-07-08 §2.1/§3.1/§5.1 — one message for
475
490
  * Edit/Write/NotebookEdit: "before writing to it", not the old sema "before editing").
@@ -314,6 +314,13 @@ function foldUncLexically(p) {
314
314
  const anchor = `\\\\${host}${share !== undefined ? `\\${share}` : ""}`;
315
315
  return { key: out.length > 0 ? `${anchor}\\${out.join("\\")}` : anchor, climbedAboveShare: climbed };
316
316
  }
317
+ export function hasWin32AliasedComponent(p) {
318
+ if (!isWinFormPath(p))
319
+ return false;
320
+ const parts = p.split(/[\\/]+/).filter((seg) => seg.length > 0);
321
+ const tail = p.startsWith("\\\\") || p.startsWith("//") ? parts.slice(2) : parts;
322
+ return tail.some((seg) => seg !== "." && seg !== ".." && /[. ]$/.test(seg));
323
+ }
317
324
  export function pathFamilyOf(bases) {
318
325
  const tree = bases.root ?? bases.cwd;
319
326
  return tree === undefined ? undefined : isWinFormPath(tree) ? "win" : "posix";
@@ -443,7 +450,7 @@ function within(rootCanonical, p) {
443
450
  return true;
444
451
  return pN.identity.startsWith(rootN.identity.endsWith(SEP) ? rootN.identity : rootN.identity + SEP);
445
452
  }
446
- export async function resolveKey(env, rootCanonical, path, signal, baseCwd, additionalRootsCanonical, exactFileReadExemption, readDeny, readFace) {
453
+ export async function resolveKey(env, rootCanonical, path, signal, baseCwd, additionalRootsCanonical, exactFileReadExemption, readDeny, containmentFace) {
447
454
  const canon = await canonicalizeTarget(env, path, signal, pathFamilyOf({ root: rootCanonical }), baseCwd);
448
455
  if (!canon.ok)
449
456
  return { ok: false, violation: { code: "invalid", message: canon.message } };
@@ -471,13 +478,13 @@ export async function resolveKey(env, rootCanonical, path, signal, baseCwd, addi
471
478
  };
472
479
  }
473
480
  }
474
- if (readFace === "open") {
481
+ if (containmentFace === "open") {
475
482
  if (isUncPath(key) && isWinFormPath(key) && !within(rootCanonical, key) && !additionalRootsCanonical?.some((r) => within(r, key))) {
476
483
  return {
477
484
  ok: false,
478
485
  violation: {
479
486
  code: "invalid",
480
- message: `path "${path}" names a network (UNC) location outside the declared roots; refused even under the open read face — a read against an undeclared share can hang on the SMB mount timeout and offers credentials to the remote host.`,
487
+ message: `path "${path}" names a network (UNC) location outside the declared roots; refused even under an open containment face — an access against an undeclared share can hang on the SMB mount timeout and offers credentials to the remote host.`,
481
488
  },
482
489
  };
483
490
  }
@@ -604,7 +611,7 @@ export function violationDetails(v) {
604
611
  export function withinAnyRoot(rootsCanonical, p) {
605
612
  return rootsCanonical.some((r) => within(r, p));
606
613
  }
607
- export const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories — or additionalReadDirectories for read-only access; a deployment that wants reads open everywhere can declare readFace: \"open\" instead of listing directories.)";
614
+ export const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories — or additionalReadDirectories for read-only access; a deployment that wants reads or writes open everywhere can declare readFace / writeFace: \"open\" instead of listing directories.)";
608
615
  export function requireRead(state, key) {
609
616
  const entry = state.get(key);
610
617
  if (entry === undefined || entry.isPartialView) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.17.7",
3
+ "version": "7.19.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",