@sema-agent/core 7.18.0 → 7.20.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 (137) hide show
  1. package/CHANGELOG.md +276 -2
  2. package/dist/agents/subagent.js +12 -20
  3. package/dist/agents/teacher.js +2 -5
  4. package/dist/agents/verify.js +21 -10
  5. package/dist/brain/anthropic.js +8 -3
  6. package/dist/brain/open-responses.js +12 -4
  7. package/dist/brain/openai.js +7 -2
  8. package/dist/brain/reasoning.d.ts +13 -13
  9. package/dist/brain/reasoning.js +2 -2
  10. package/dist/brain/stream-engine.d.ts +4 -0
  11. package/dist/brain/stream-engine.js +6 -1
  12. package/dist/brain/stream-shared.d.ts +3 -0
  13. package/dist/brain/stream-shared.js +1 -0
  14. package/dist/core/ask-origin.d.ts +16 -0
  15. package/dist/core/ask-origin.js +8 -0
  16. package/dist/core/auto-mode-arming.d.ts +6 -0
  17. package/dist/core/auto-mode-arming.js +5 -5
  18. package/dist/core/auto-mode-defaults.d.ts +43 -15
  19. package/dist/core/auto-mode-defaults.js +4 -1
  20. package/dist/core/auto-mode.d.ts +74 -26
  21. package/dist/core/auto-mode.js +80 -11
  22. package/dist/core/checkpoint-store.d.ts +7 -0
  23. package/dist/core/checkpoint-store.js +2 -2
  24. package/dist/core/child-clamp-seats.d.ts +79 -0
  25. package/dist/core/child-clamp-seats.js +29 -0
  26. package/dist/core/engine-notice.d.ts +89 -41
  27. package/dist/core/fs-write-gate-policy.d.ts +17 -9
  28. package/dist/core/fs-write-gate-policy.js +51 -4
  29. package/dist/core/gate-bounded-await.d.ts +38 -0
  30. package/dist/core/gate-bounded-await.js +53 -0
  31. package/dist/core/gate-fold.js +20 -48
  32. package/dist/core/gate-lanes.js +17 -9
  33. package/dist/core/gate-outcome.d.ts +34 -6
  34. package/dist/core/gate-outcome.js +7 -0
  35. package/dist/core/governance-codes.d.ts +16 -1
  36. package/dist/core/governance-codes.js +18 -0
  37. package/dist/core/hooks.d.ts +8 -8
  38. package/dist/core/memory-engine/engine.d.ts +14 -9
  39. package/dist/core/memory-engine/engine.js +21 -8
  40. package/dist/core/memory-engine/layout.d.ts +31 -0
  41. package/dist/core/memory-engine/layout.js +68 -3
  42. package/dist/core/memory-engine/types.d.ts +24 -0
  43. package/dist/core/memory.d.ts +19 -0
  44. package/dist/core/memory.js +12 -0
  45. package/dist/core/read-root-candidate.d.ts +12 -0
  46. package/dist/core/read-root-candidate.js +24 -0
  47. package/dist/core/remote-env.d.ts +32 -0
  48. package/dist/core/remote-env.js +4 -1
  49. package/dist/core/roles.d.ts +9 -1
  50. package/dist/core/roles.js +4 -0
  51. package/dist/core/runner/advertised-writable-dirs.d.ts +59 -1
  52. package/dist/core/runner/advertised-writable-dirs.js +51 -3
  53. package/dist/core/runner/announce-once-ledger.d.ts +15 -0
  54. package/dist/core/runner/announce-once-ledger.js +8 -0
  55. package/dist/core/runner/classifier-ladder.d.ts +154 -11
  56. package/dist/core/runner/classifier-ladder.js +170 -10
  57. package/dist/core/runner/contracts.d.ts +46 -1
  58. package/dist/core/runner/denial-limit-arms.d.ts +14 -1
  59. package/dist/core/runner/denial-limit-arms.js +7 -6
  60. package/dist/core/runner/env-capability-census.d.ts +30 -0
  61. package/dist/core/runner/env-capability-census.js +38 -0
  62. package/dist/core/runner/forward-sink.d.ts +32 -0
  63. package/dist/core/runner/forward-sink.js +41 -0
  64. package/dist/core/runner/gate-exit.d.ts +4 -0
  65. package/dist/core/runner/gate-exit.js +4 -1
  66. package/dist/core/runner/git-status-frame.d.ts +0 -9
  67. package/dist/core/runner/listing-fold.d.ts +57 -0
  68. package/dist/core/runner/listing-fold.js +75 -0
  69. package/dist/core/runner/prepare-ask-lane.js +1 -0
  70. package/dist/core/runner/prepare-caps-and-workflow.js +20 -31
  71. package/dist/core/runner/prepare-config-doors.d.ts +6 -0
  72. package/dist/core/runner/prepare-config-doors.js +8 -10
  73. package/dist/core/runner/prepare-execution-env.d.ts +1 -1
  74. package/dist/core/runner/prepare-execution-env.js +2 -0
  75. package/dist/core/runner/prepare-gate-stations.d.ts +8 -0
  76. package/dist/core/runner/prepare-gate-stations.js +6 -2
  77. package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
  78. package/dist/core/runner/prepare-hands-readface.js +3 -5
  79. package/dist/core/runner/prepare-listings.d.ts +3 -0
  80. package/dist/core/runner/prepare-listings.js +4 -2
  81. package/dist/core/runner/prepare-memory.js +7 -1
  82. package/dist/core/runner/prepare-policy-chain.js +5 -5
  83. package/dist/core/runner/prepare-project-context.d.ts +5 -1
  84. package/dist/core/runner/prepare-project-context.js +3 -2
  85. package/dist/core/runner/prepare-prompt-inputs.d.ts +2 -2
  86. package/dist/core/runner/prepare-run-refs.d.ts +4 -2
  87. package/dist/core/runner/prepare-run-refs.js +5 -4
  88. package/dist/core/runner/prepare-task.js +8 -6
  89. package/dist/core/runner/resume-apply.js +3 -3
  90. package/dist/core/runner/run-clock-and-content.js +3 -7
  91. package/dist/core/runner/run-harness-handlers.js +10 -10
  92. package/dist/core/runner/run-leg.js +31 -16
  93. package/dist/core/runner/run-settle-and-teardown.js +2 -5
  94. package/dist/core/runner/run-turn-boundary.js +44 -25
  95. package/dist/core/runner/runtask.js +2 -1
  96. package/dist/core/runner/synthetic-tools.d.ts +41 -29
  97. package/dist/core/runner/synthetic-tools.js +25 -40
  98. package/dist/core/runner/task-root-canonical.d.ts +45 -0
  99. package/dist/core/runner/task-root-canonical.js +18 -0
  100. package/dist/core/runner/turn-attachments.d.ts +160 -53
  101. package/dist/core/runner/turn-attachments.js +153 -51
  102. package/dist/core/safe-notify.d.ts +5 -0
  103. package/dist/core/safe-notify.js +24 -2
  104. package/dist/core/sensitive-path-policy.d.ts +84 -17
  105. package/dist/core/sensitive-path-policy.js +37 -19
  106. package/dist/core/skill-listing-budget.d.ts +106 -0
  107. package/dist/core/skill-listing-budget.js +57 -0
  108. package/dist/core/skills-directory.js +17 -10
  109. package/dist/core/task-registry.js +2 -1
  110. package/dist/core/task-spec.d.ts +29 -2
  111. package/dist/core/tool-policy.d.ts +31 -5
  112. package/dist/core/tool-policy.js +45 -32
  113. package/dist/core/tool-spec.d.ts +37 -4
  114. package/dist/core/types.d.ts +1 -1
  115. package/dist/core/write-protect.d.ts +105 -20
  116. package/dist/core/write-protect.js +89 -32
  117. package/dist/index.d.ts +8 -7
  118. package/dist/index.js +6 -5
  119. package/dist/orchestration/workflow-primitives.js +10 -20
  120. package/dist/stores/file/memory-store.d.ts +5 -2
  121. package/dist/stores/file/memory-store.js +8 -4
  122. package/dist/tools/fs/bash-readonly-classifier.d.ts +40 -2
  123. package/dist/tools/fs/bash-readonly-classifier.js +376 -41
  124. package/dist/tools/fs/fs-bash.d.ts +1 -1
  125. package/dist/tools/fs/fs-bash.js +19 -28
  126. package/dist/tools/fs/fs-shared.d.ts +22 -0
  127. package/dist/tools/fs/fs-shared.js +30 -0
  128. package/dist/tools/fs/index.d.ts +1 -1
  129. package/dist/tools/fs/index.js +1 -1
  130. package/dist/tools/fs/read-deny.d.ts +4 -3
  131. package/dist/tools/fs/safety.d.ts +15 -0
  132. package/dist/tools/fs/safety.js +7 -0
  133. package/package.json +2 -1
  134. package/test/export-surface.snapshot.json +31 -5
  135. package/wire-changes/core@7.18.0.json +1273 -0
  136. package/wire-changes/core@7.19.0.json +457 -0
  137. package/wire-changes/core@7.20.0.json +568 -0
@@ -1,3 +1,5 @@
1
+ import type { ExecutionEnv } from "../internal/harness-types.js";
2
+ import type { PermissionResult } from "./tool-policy.js";
1
3
  import { type ToolCallLike } from "./tool-registry.js";
2
4
  /**
3
5
  * How one table row matches the judged path:
@@ -45,41 +47,124 @@ export declare function foldWriteProtectCase(s: string): string;
45
47
  * candidate configuration with the engine's own rules.
46
48
  */
47
49
  export declare function resolveWriteProtectedTable(entries?: readonly WriteProtectedEntry[]): readonly WriteProtectedRow[];
48
- /** The compiled judge over one resolved table. Pure and synchronous — literal folded comparisons,
49
- * no filesystem access (see the module header for the declared lexical scope). */
50
+ /**
51
+ * The judge's answer for ONE covered write ({@link WriteProtectionMatcher.judge}):
52
+ * · `clear` — no row on either view: the write is not this layer's business;
53
+ * · `hit` — a row matched; `via` says which view named it: `"spelling"` (the anchored spelling, no
54
+ * filesystem consulted) or `"target"` (the resolved real target, the spelling being innocent);
55
+ * · `unresolvable` — the real target could not be resolved (an unfollowable symlink, an errored probe, a
56
+ * namespace spelling the tree refuses); `cause` is the canonicalizer's own sentence and the consumer
57
+ * REFUSES, an unjudged target never being vouched.
58
+ */
59
+ export type WriteProtectionVerdict = {
60
+ readonly kind: "clear";
61
+ } | {
62
+ readonly kind: "hit";
63
+ readonly hit: WriteProtectedHit;
64
+ readonly via: "spelling" | "target";
65
+ } | {
66
+ readonly kind: "unresolvable";
67
+ readonly cause: string;
68
+ readonly unresolvedSymlink: boolean;
69
+ };
70
+ /** What one judgment knows about the call, all optional — every member has an absence reading. */
71
+ export interface WriteProtectionJudgeContext {
72
+ /** The call's LIVE tracked working directory (see {@link WriteProtectionMatcher.matchPath}). */
73
+ readonly cwd?: string;
74
+ /** The base a RELATIVE spelling resolves against on the TARGET view — the tool's own declared base
75
+ * ({@link pathTargetBaseOf}). Absent ⇒ `cwd`, then the compile-time `rootPath`, then the environment's
76
+ * own working directory: the same fallback chain the write tool itself walks. */
77
+ readonly base?: string;
78
+ /** Bounds the filesystem probes of the target view. */
79
+ readonly signal?: AbortSignal;
80
+ }
81
+ /** The compiled judge over one resolved table. `matchPath` is the SPELLING view alone (literal folded
82
+ * comparisons, no filesystem access — the admin preview face); `judge` is the whole judgment (see the
83
+ * module header for the two views). */
50
84
  export interface WriteProtectionMatcher {
51
85
  /** The resolved rows this judge was compiled from (canonical names — the disclosure face). */
52
86
  readonly rows: readonly WriteProtectedRow[];
53
- /** Judge ONE path spelling. Returns the first matching row (basename rows first, then segment,
54
- * then segment-run — deterministic), or null. */
55
- matchPath(path: string): WriteProtectedHit | null;
87
+ /** Judge ONE path SPELLING. Returns the first matching row (basename rows first, then segment,
88
+ * then segment-run — deterministic), or null.
89
+ *
90
+ * `cwd` = the call's LIVE tracked working directory, read at judgment time off the ONE ref the fs
91
+ * hand tools resolve with — the law every path guard here follows: judge the file the tool will write.
92
+ * Absent ⇒ judged exactly as given — the historical reading, and the honest answer for a host
93
+ * that tracks no cwd. */
94
+ matchPath(path: string, cwd?: string): WriteProtectedHit | null;
95
+ /**
96
+ * Judge ONE covered write by the file the tool will write: the spelling view first (`matchPath`; a hit
97
+ * there is the answer and costs no I/O), then — on a miss, and only when the judge was compiled with an
98
+ * execution environment — the resolved real target through the shared `canonicalizeTarget`.
99
+ *
100
+ * @contract write_protection.judges_the_real_target — the always-on write-protection table and the
101
+ * opt-in sensitive-path deny set differ in VOCABULARY (literal rows / segment globs) and SEVERITY
102
+ * (ask / deny), never in REACH: both judge the file the tool will write — the spelling anchored at the
103
+ * live cwd AND its resolved real target, through one canonicalizer — and a target neither can resolve is
104
+ * refused, never vouched. An environment-less judge has no target view and answers by the spelling alone —
105
+ * the same absence law the cwd follows (absent ⇒ the spelling is judged exactly as given). REACH is what
106
+ * this sentence promises, not a LOCK: the identity is resolved at judgment time and re-resolved by the
107
+ * tool at execute time (the module header states that window and the seat that would close it).
108
+ */
109
+ judge(path: string, ctx?: WriteProtectionJudgeContext): Promise<WriteProtectionVerdict>;
110
+ }
111
+ /** Compile-time seats of {@link compileWriteProtection}. Every member is optional and has an absence reading. */
112
+ export interface WriteProtectionCompileOptions {
113
+ /** The engine data root for the passthrough in the module header (same seat and same meaning as
114
+ * `createSensitivePathPolicy`'s): omitted ⇒ the knob its consumers read (`$AGENT_DATA_DIR ?? ~/.ai-agent`). */
115
+ readonly dataRoot?: string;
116
+ /** The execution environment the hands write against — the TARGET view resolves in ITS filesystem
117
+ * (a remote/sandboxed task resolves in its container). Omitted ⇒ no target view: the spelling is the
118
+ * judgment (a host with no hands has no filesystem to resolve against). */
119
+ readonly env?: ExecutionEnv;
120
+ /** The task root — the base a `base: "root"` write tool's relative spelling resolves against, and the
121
+ * path-family source when the call carries no cwd. Omitted ⇒ relative spellings resolve against the
122
+ * cwd, then the environment's own working directory. */
123
+ readonly rootPath?: string;
56
124
  }
57
125
  /**
58
126
  * Compile the write-protection judge for a deployment configuration. Returns `undefined` when the
59
127
  * resolved table is EMPTY (`[]` replacement) — the caller then mounts no tighten at all, keeping an
60
128
  * opted-out deployment's decision path byte-identical to a build without this layer.
61
129
  *
62
- * `opts.dataRoot` names the engine data root for the passthrough in the module header (same seat and
63
- * same meaning as `createSensitivePathPolicy`'s): omitted ⇒ the knob its consumers read
64
- * (`$AGENT_DATA_DIR ?? ~/.ai-agent`), read ONCE here at compile so the judge stays pure and
65
- * synchronous. The passthrough is a property of the JUDGE, not of the default table — a deployment
130
+ * The data-root spellings are read ONCE here at compile (a judge that re-read `process.env` per call
131
+ * would answer two calls of one run differently); their CANONICAL keys are resolved per judgment, on the
132
+ * target view only, and only once a row has matched there (see {@link canonicalDataRootKeys} for why they
133
+ * are never cached). The passthrough is a property of the JUDGE, not of the default table — a deployment
66
134
  * that replaces the table wholesale gets the same law over its own rows.
67
135
  */
68
- export declare function compileWriteProtection(entries?: readonly WriteProtectedEntry[], opts?: {
69
- dataRoot?: string;
70
- }): WriteProtectionMatcher | undefined;
136
+ export declare function compileWriteProtection(entries?: readonly WriteProtectedEntry[], opts?: WriteProtectionCompileOptions): WriteProtectionMatcher | undefined;
137
+ /**
138
+ * The gate tighten's DECISION for one judgment — the words the fold writes on the pass, minted beside the
139
+ * verdict they read so the two cannot drift:
140
+ * · `hit` ⇒ `ask` (`safety`; no `requiresRealApproval` — one explicit look, clearable by an approver, a
141
+ * blanket `onAsk:"allow"`, the classifier or a matching persisted rule). The matched row NAME goes on the
142
+ * message (table names are deployment-validated vocabulary); the model-authored target path deliberately
143
+ * does not — `message` is the classifier's `askMessage`, and the approval card already shows the args. The
144
+ * spelling form's sentence is the historical one byte for byte; the target form says the target RESOLVED.
145
+ * · `unresolvable` ⇒ `deny` naming the cause: an unjudged target is never vouched, and an ask would be a
146
+ * question about a file nobody can name — the rule the canonicalizing deny set follows for the same reason.
147
+ * · `clear` ⇒ `undefined`: the surviving decision stands.
148
+ */
149
+ export declare function writeProtectionTightenDecision(toolName: string, verdict: WriteProtectionVerdict): PermissionResult | undefined;
150
+ /**
151
+ * The ENGINE-FILLED gate input (`ToolGateInput.writeProtectionCheck`). Returns `null` SYNCHRONOUSLY when the
152
+ * call is not this layer's business — not a covered write, or no resolvable target string — and otherwise
153
+ * the judgment of {@link WriteProtectionMatcher.judge}, a promise. The split is load-bearing for the gate:
154
+ * a call the judge does not cover must not cost the fold a suspension point.
155
+ */
156
+ export type WriteProtectionCheck = (toolName: string, args: unknown, face?: ToolCallLike["face"], cwd?: string, signal?: AbortSignal) => Promise<WriteProtectionVerdict> | null;
71
157
  /**
72
158
  * Build the ENGINE-FILLED gate input (`ToolGateInput.writeProtectionCheck`): covered tools are the
73
159
  * path-confinable write set (Write/Edit/NotebookEdit — the shared spelling), the judged target is
74
160
  * the SAME string the tool itself will resolve (`writeTargetPath`, the shared single source with
75
161
  * the sensitive-path guard and the fs-write gate), and the verdict is the table judge's. A covered
76
- * write with NO resolvable target returns null here — this layer is ADDITIVE friction on a named
77
- * set of targets, not a containment boundary (a target-less call cannot land on a named row, and
78
- * the tool's own schema validation refuses it before any write); the fail-closed treatment of the
79
- * unresolvable case belongs to the containment gates (fs-write-gate-policy documents that split).
162
+ * write with NO resolvable target string returns null here — this layer is ADDITIVE friction on a named
163
+ * set of targets, not a containment boundary (a target-less call cannot land on a named row, and the
164
+ * tool's own schema validation refuses it before any write); the fail-closed treatment of that case
165
+ * belongs to the containment gates (fs-write-gate-policy documents that split). A target string the
166
+ * environment cannot RESOLVE is the judge's own `unresolvable` verdict, not this null.
80
167
  * Returns `undefined` when the resolved table is empty — nothing to judge, mount nothing.
81
- * `opts.dataRoot` is the judge's own seat, passed straight through ({@link compileWriteProtection}).
168
+ * `opts` are the judge's own seats, passed straight through ({@link compileWriteProtection}).
82
169
  */
83
- export declare function createWriteProtectionCheck(entries?: readonly WriteProtectedEntry[], opts?: {
84
- dataRoot?: string;
85
- }): ((toolName: string, args: unknown, face?: ToolCallLike["face"]) => WriteProtectedHit | null) | undefined;
170
+ export declare function createWriteProtectionCheck(entries?: readonly WriteProtectedEntry[], opts?: WriteProtectionCompileOptions): WriteProtectionCheck | undefined;
@@ -1,6 +1,7 @@
1
- import { isWinFormPath, writeTargetPath, isUncPath } from "../tools/fs/safety.js";
2
- import { isProtectedWrite } from "./tool-registry.js";
3
- import { dataRootSpellings } from "./sensitive-path-policy.js";
1
+ import { canonicalizeTarget, isWinFormPath, writeTargetPath, isUncPath, isAbsoluteForFamily, joinForFamily, pathFamilyOf } from "../tools/fs/safety.js";
2
+ import { protectivePathTargetOf } from "./tool-registry.js";
3
+ import { pathTargetBaseOf } from "./effective-path-target.js";
4
+ import { canonicalDataRootKeys, dataRootSpellings, relativeUnderRoot } from "./sensitive-path-policy.js";
4
5
  const freezeTable = (rows) => Object.freeze(rows.map((r) => Object.freeze(r)));
5
6
  export const WRITE_PROTECTED_DEFAULT_TABLE = freezeTable([
6
7
  { name: ".gitconfig", kind: "basename" },
@@ -159,7 +160,8 @@ export function compileWriteProtection(entries, opts) {
159
160
  const rows = resolveWriteProtectedTable(entries);
160
161
  if (rows.length === 0)
161
162
  return undefined;
162
- const rootViews = dataRootSpellings(opts?.dataRoot).map((r) => {
163
+ const dataRootPaths = dataRootSpellings(opts?.dataRoot);
164
+ const rootViews = dataRootPaths.map((r) => {
163
165
  const win = isWinFormPath(r);
164
166
  return { win, segments: pathViewSegments(r, false, win ? BOTH_SEPARATOR_FAMILIES : POSIX_SEPARATOR_FAMILY, uncAnchorSegments(r)) };
165
167
  });
@@ -180,46 +182,101 @@ export function compileWriteProtection(entries, opts) {
180
182
  runs.push({ name: row.name, parts: folded.split("/").filter((s) => s.length > 0) });
181
183
  }
182
184
  }
185
+ const judgeSegments = (raw) => {
186
+ if (raw.length === 0)
187
+ return null;
188
+ const segs = raw.map(foldWriteProtectCase);
189
+ const base = basenames.get(segs[segs.length - 1] ?? "");
190
+ if (base !== undefined)
191
+ return { name: base, kind: "basename" };
192
+ for (const s of segs) {
193
+ const hit = segments.get(s);
194
+ if (hit !== undefined)
195
+ return { name: hit, kind: "segment" };
196
+ }
197
+ for (const run of runs) {
198
+ const n = run.parts.length;
199
+ for (let i = 0; i + n <= segs.length; i++) {
200
+ if (run.parts.every((p, j) => segs[i + j] === p))
201
+ return { name: run.name, kind: "segment-run" };
202
+ }
203
+ }
204
+ return null;
205
+ };
206
+ const judgeViews = (spelling) => judgeSegments(pathViewSegments(spelling, false, POSIX_SEPARATOR_FAMILY, uncAnchorSegments(spelling))) ??
207
+ judgeSegments(pathViewSegments(spelling, false)) ??
208
+ judgeSegments(pathViewSegments(spelling, true));
209
+ const judgeSpelling = (path, cwd) => {
210
+ const base = typeof cwd === "string" && cwd.length > 0 ? cwd : undefined;
211
+ const family = pathFamilyOf({ cwd: base });
212
+ const spelled = base !== undefined && family !== undefined && !isAbsoluteForFamily(family, path) ? joinForFamily(family, base, path) : path;
213
+ return judgeViews(relativeUnderDataRoot(spelled, rootViews) ?? spelled);
214
+ };
215
+ const env = opts?.env;
216
+ const rootPath = opts?.rootPath;
183
217
  return {
184
218
  rows,
185
- matchPath(path) {
186
- const judged = relativeUnderDataRoot(path, rootViews) ?? path;
187
- const judge = (raw) => {
188
- if (raw.length === 0)
189
- return null;
190
- const segs = raw.map(foldWriteProtectCase);
191
- const base = basenames.get(segs[segs.length - 1] ?? "");
192
- if (base !== undefined)
193
- return { name: base, kind: "basename" };
194
- for (const s of segs) {
195
- const hit = segments.get(s);
196
- if (hit !== undefined)
197
- return { name: hit, kind: "segment" };
198
- }
199
- for (const run of runs) {
200
- const n = run.parts.length;
201
- for (let i = 0; i + n <= segs.length; i++) {
202
- if (run.parts.every((p, j) => segs[i + j] === p))
203
- return { name: run.name, kind: "segment-run" };
204
- }
205
- }
206
- return null;
207
- };
208
- return judge(pathViewSegments(judged, false)) ?? judge(pathViewSegments(judged, true));
219
+ matchPath(path, cwd) {
220
+ return judgeSpelling(path, cwd);
221
+ },
222
+ async judge(path, ctx = {}) {
223
+ const bySpelling = judgeSpelling(path, ctx.cwd);
224
+ if (bySpelling !== null)
225
+ return { kind: "hit", hit: bySpelling, via: "spelling" };
226
+ if (env === undefined)
227
+ return { kind: "clear" };
228
+ const canon = await canonicalizeTarget(env, path, ctx.signal, pathFamilyOf({ root: rootPath, cwd: ctx.cwd ?? env.cwd }), ctx.base ?? ctx.cwd ?? rootPath);
229
+ if (!canon.ok)
230
+ return { kind: "unresolvable", cause: canon.message, unresolvedSymlink: canon.unresolvedSymlink === true };
231
+ const byTarget = judgeViews(canon.key);
232
+ if (byTarget === null)
233
+ return { kind: "clear" };
234
+ for (const rootKey of await canonicalDataRootKeys(env, dataRootPaths, ctx.signal)) {
235
+ const relative = relativeUnderRoot(canon.key, rootKey);
236
+ if (relative === null)
237
+ continue;
238
+ if (relative === "")
239
+ return { kind: "clear" };
240
+ const remainder = judgeViews(relative);
241
+ return remainder === null ? { kind: "clear" } : { kind: "hit", hit: remainder, via: "target" };
242
+ }
243
+ return { kind: "hit", hit: byTarget, via: "target" };
209
244
  },
210
245
  };
211
246
  }
247
+ export function writeProtectionTightenDecision(toolName, verdict) {
248
+ if (verdict.kind === "hit") {
249
+ return {
250
+ action: "ask",
251
+ message: verdict.via === "target"
252
+ ? `tool "${toolName}" writes to a write-protected path — its real target resolves onto table entry "${verdict.hit.name}" — explicit approval required`
253
+ : `tool "${toolName}" writes to a write-protected path (table entry "${verdict.hit.name}") — explicit approval required`,
254
+ decisionReason: "safety",
255
+ };
256
+ }
257
+ if (verdict.kind === "unresolvable") {
258
+ return {
259
+ action: "deny",
260
+ message: verdict.unresolvedSymlink
261
+ ? `tool "${toolName}" is blocked: its target is a symlink whose real target could not be resolved, so the write-protection judge cannot tell whether it lands on a protected path (${verdict.cause})`
262
+ : `tool "${toolName}" is blocked: its target could not be resolved on this tree, so the write-protection judge cannot tell whether it lands on a protected path (${verdict.cause})`,
263
+ decisionReason: "safety",
264
+ };
265
+ }
266
+ return undefined;
267
+ }
212
268
  export function createWriteProtectionCheck(entries, opts) {
213
269
  const matcher = compileWriteProtection(entries, opts);
214
270
  if (matcher === undefined)
215
271
  return undefined;
216
- return (toolName, args, face) => {
272
+ return (toolName, args, face, cwd, signal) => {
217
273
  const call = { toolName, args, ...(face !== undefined ? { face } : {}) };
218
- if (!isProtectedWrite(call))
274
+ const protective = protectivePathTargetOf(call);
275
+ if (protective === undefined || protective.access === "read")
219
276
  return null;
220
- const path = writeTargetPath(call);
277
+ const path = writeTargetPath(call, protective);
221
278
  if (typeof path !== "string" || path.length === 0)
222
279
  return null;
223
- return matcher.matchPath(path);
280
+ return matcher.judge(path, { ...(cwd !== undefined ? { cwd } : {}), ...(signal !== undefined ? { signal } : {}), ...(() => { const b = pathTargetBaseOf(protective, { root: opts?.rootPath, cwd }); return b !== undefined ? { base: b } : {}; })() });
224
281
  };
225
282
  }
package/dist/index.d.ts CHANGED
@@ -89,7 +89,7 @@ export { MAX_EXEC_OUTPUT_BYTES, RollingTailBuffer, markTruncated } from "./core/
89
89
  export { physicalPathOf, physicalPathOfExisting } from "./core/physical-path.js";
90
90
  export type { ExecutionEnv, FileInfo, Result, FileErrorCode, ExecutionErrorCode, WriteExpectation, WriteReceipt } from "./internal/harness.js";
91
91
  export type { ExecResult } from "./internal/harness.js";
92
- export { RemoteExecutionError, hasDestroy, isRemoteExecutionEnv, isSuspendable, isIsolated, missingRestoreSurface, isRetryableRemoteErrorCode, RETRYABLE_REMOTE_ERROR_CODES, } from "./core/remote-env.js";
92
+ export { RemoteExecutionError, hasDestroy, isRemoteExecutionEnv, isSuspendable, isIsolated, canonicalPathIsAuthoritative, missingRestoreSurface, isRetryableRemoteErrorCode, RETRYABLE_REMOTE_ERROR_CODES, } from "./core/remote-env.js";
93
93
  export { withRetry } from "./core/with-retry.js";
94
94
  export type { RetryPolicy, RetryResult } from "./core/with-retry.js";
95
95
  export type { RemoteExecutionEnv, WorkspaceHandle, SnapshotId, SessionToken, SandboxTier, OutputChunk, ExecStreamOptions, RemoteConnectConfig, VmLifecycleOptions, SecretRef, RemoteExecutionErrorCode, ExecutionEnvFactory, ExecutionEnvFactoryContext, } from "./core/remote-env.js";
@@ -105,7 +105,7 @@ export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, ty
105
105
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
106
106
  export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, type ReadDenyEntry, type ReadDenyMatcher, type NormalizedReadDenyEntry, type ReadDenyBuiltinTier, type ReadDenyBuiltinRow, type ReadDenyBuiltinConfig, } from "./tools/fs/index.js";
107
107
  export { deploymentFaceClampNotice, resolveContainmentFace, type ContainmentFace, type ContainmentFaceInputs, type ContainmentFaceKind, type ContainmentFaceLayer, } from "./tools/fs/index.js";
108
- export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, type WriteProtectedEntry, type WriteProtectedRow, type WriteProtectedKind, type WriteProtectedHit, type WriteProtectionMatcher, } from "./core/write-protect.js";
108
+ export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, type WriteProtectedEntry, type WriteProtectedRow, type WriteProtectedKind, type WriteProtectedHit, type WriteProtectionMatcher, type WriteProtectionVerdict, type WriteProtectionJudgeContext, type WriteProtectionCompileOptions, type WriteProtectionCheck, } from "./core/write-protect.js";
109
109
  export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, type ToolResultProvenance, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, type ToolResultStore, type ToolResultSlice, type ToolResultDeletionReport, } from "./core/tool-result-store.js";
110
110
  export { InMemoryCheckpointStore, CheckpointError, mintCheckpointId, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type ProbeCause, type ProbeCauseOperands, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type TerminalClaimIntent, type TerminalClaimOutcome, type SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, type CheckpointRow, realApprovalOrgFact, resolveCheckpointStore, checkpointStoreExecutionUndeclared, } from "./core/checkpoint-store.js";
111
111
  export { EXECUTION_OUTCOME_RECORD_WORDS, EXECUTION_RECORD_LEAVES_ROW_UNRECORDED, type ExecutionOutcomeRecordWord, type ExecutionRecordRowFacts, type ExecutionVerdict, ExecutionOutcomeConflictError, checkpointExecutionRecorded, executionRecordDisposition, sameExecutionRecord, executionVerdict, } from "./core/checkpoint-execution-record.js";
@@ -129,7 +129,7 @@ export { CacheBreakDetector, type CacheBreakFinding, type ToolFingerprintInput }
129
129
  export { maybeCompact, type MaybeCompactOptions, type CompactionWindowSafetyInfo } from "./core/auto-compaction.js";
130
130
  export { brainToRuntime } from "./core/runtime.js";
131
131
  export { createSensitivePathPolicy, RECOMMENDED_SENSITIVE_PATTERNS } from "./core/sensitive-path-policy.js";
132
- export { createFsWriteGatePolicy, type FsWriteGatePolicyOptions } from "./core/fs-write-gate-policy.js";
132
+ export { createFsWriteGatePolicy, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE, type FsWriteGatePolicyOptions } from "./core/fs-write-gate-policy.js";
133
133
  export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
134
134
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
135
135
  export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, type TaskNotificationPayload, type TaskNotificationStatus, type ExternalNotificationInput, type SystemInjection, type SystemInjectionPriority, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, type SemaProvenance, } from "./core/task-notification.js";
@@ -177,8 +177,9 @@ export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource, type AutoModeDenySource }
177
177
  export { SETTLEMENT_KINDS, type SettlementKind, isSettlementKind, type Settlement, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, type DeniedBy, isDeniedBy, DENIED_BY_MAY_VETO, CLASSIFIER_DENY_CAUSES, type ClassifierDenyCause, isClassifierDenyCause, DENIED_BY_MAY_CARRY_CAUSE, type GateDisposition, type GateOutcome, screenGateOutcome } from "./core/gate-outcome.js";
178
178
  export { type AskCarry } from "./core/hooks.js";
179
179
  export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps, type SwappableDepSeat, type SwappableDeps } from "./core/swappable-deps.js";
180
- export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, classifierUnavailableDenyMessage, CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE, type AutoModeUnavailableCause, AUTO_MODE_CLASSIFIER_FALLBACKS, isAutoModeClassifierFallback, AUTO_MODE_FALLBACK_CAUSES, isAutoModeFallbackCause, type AutoModeClassifierFallback, type AutoModeFallbackCause, type AutoModeClassifierRound, type AutoModeLadderRung, type AutoModeVerdict, type AutoModeDecider, type AutoModeDeciderOptions, type AutoModeClassified, type AutoModeClassifyFn, type AutoModeClassifyInput, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, type AutoModeDenialTracker, type AutoModeDenialLimitOptions, type DenialLimitCounts, type DenialLimitFallbackFace, type UnarmedDenialLimitFallback, type DenialLimitFallback, type DenialLimitVerdict, } from "./core/auto-mode.js";
180
+ export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, classifierUnavailableDenyMessage, classifierParseFailureDenyMessage, AUTO_MODE_PARSE_FAILURES, isAutoModeParseFailure, type AutoModeParseFailure, type AutoModeUnavailableCause, AUTO_MODE_CLASSIFIER_FALLBACKS, isAutoModeClassifierFallback, AUTO_MODE_FALLBACK_CAUSES, isAutoModeFallbackCause, type AutoModeClassifierFallback, type AutoModeFallbackCause, type AutoModeClassifierRound, type AutoModeLadderRung, type AutoModeVerdict, type AutoModeDecider, type AutoModeDeciderOptions, type AutoModeClassified, type AutoModeClassifyFn, type AutoModeClassifyInput, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, type AutoModeDenialTracker, type AutoModeDenialLimitOptions, type DenialLimitCounts, type DenialLimitFallbackFace, type UnarmedDenialLimitFallback, type DenialLimitFallback, type DenialLimitVerdict, } from "./core/auto-mode.js";
181
181
  export { AUTO_MODE_DENIAL_LIMIT_DEFAULTS, AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS, AUTO_MODE_CLASSIFIER_MAX_TOKENS } from "./core/auto-mode-defaults.js";
182
+ export { classifierSeatModel, type ClassifierSeat } from "./core/runner/classifier-ladder.js";
182
183
  export { buildAutoModePrompt, renderAutoModeWindow, renderAutoModeAction, AUTO_MODE_DEFAULTS_SENTINEL, type AutoModeRules, type BuildAutoModePromptOptions, type AutoModeWindowOptions, } from "./core/auto-mode-prompt.js";
183
184
  export { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./core/auto-mode-prompt-assets.js";
184
185
  export { AUTO_MODE_ARMING_RECIPE_VERSION, autoModeArmingRecipeOf, sanitizeAutoModeArmingRecipe, foldAutoModeArming, type AutoModeArmingRecipe, type AutoModeArmingFace, type AutoModeArmingFold, type AutoModeRebuildRefusal, } from "./core/auto-mode-arming.js";
@@ -216,7 +217,7 @@ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootA
216
217
  export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, type AdoptionStatus, type AdoptLocalDataRootOptions, type AdoptLocalDataRootResult, type AdoptionCarriageLeg, type AdoptionCarriageLegContext, type AdoptionConfigWitnessReceipt, } from "./stores/file/adoption/adopt.js";
217
218
  export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreToolUseConstraintPolicy, type PersistedRuleHit, type PersistedRuleHitRule, type PersistedRuleUnreadable, type PersistedRuleCoverage, type PersistedRuleAnswer, normalizePersistedRuleHit, type Hooks, type HookToolContext, type HookInvocationIdentity, type UserPromptSubmitContext, type PostToolBatchContext, type HookEnvCapabilities, type HookToolOutput, type PreToolUseResult, type PostToolUseResult, type UserPromptSubmitResult, type HookToolFailure, type PostToolUseFailureResult, type PostToolBatchCall, type PostToolBatchResult, type PreCompactContext, type PreCompactResult, type PostCompactContext, type StopFailureContext, type PermissionDeniedPayload, } from "./core/hooks.js";
218
219
  export { HOOK_FAMILIES, HOOKS_INVALID_CODE, type HookFamily, type WiringManifestHookEntry } from "./core/hook-seats.js";
219
- export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, type NormalizedMemorySpec, type MemorySpecInput, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, type Embedder, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, type UtilityGate, type MemoryStore, type MemoryVectorMode, type ScoredMemory, type MemoryNoteHeader, type MemoryNoteRecord, type MemoryNoteType, type StructuredNoteInput, } from "./core/memory.js";
220
+ export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, type NormalizedMemorySpec, type MemorySpecInput, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, type Embedder, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, MemoryProjectionError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, type UtilityGate, type MemoryStore, type MemoryVectorMode, type ScoredMemory, type MemoryNoteHeader, type MemoryNoteRecord, type MemoryNoteType, type StructuredNoteInput, } from "./core/memory.js";
220
221
  export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, writableRootOf, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_ORIGIN_CAUSES, committedOriginOf, originEquals, consolidationExposedFrontmatter, ambiguousOriginRepresentation, type OriginClearanceRow, type OriginClearanceEvent, type OriginClearanceShadow, committedDistilledOf, distilledEquals, type MemoryEntryDistilled, type MemoryEntryDistilledInput, CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, memoryConsolidationWithheldNotice, type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type MemoryConsolidationOptions, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, type ConsolidationPlanFoldEvidence, type SessionMemoryStatus, DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, MEMORY_DISTILLER_PURITY_CONTRACT_V1, detectCleanArmVerbatimLeak, type CleanArmLeakFinding, type CleanArmLeakVerdict, type MemoryDistillerPurityContract, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, type ConsolidationDistillFn, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationDriveResult, type ConsolidationFoldState, type DistillerCandidate, type DistillerChatAnswer, type DistillerChatFn, type DistillerChatRequest, type FuseSchedule, type LlmConsolidationPlan, type LlmConsolidationPlanArm, type LlmConsolidationPlanProduct, type LlmDistillerContract, type MintLlmConsolidationPlanResult, type PlanParseRepairs, type SanitizedLlmGroups, CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, CONSOLIDATION_RUN_STOP_REASONS, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, type ConsolidationDriverEngine, type ConsolidationDriverRunRow, type ConsolidationRunReceipt, type ConsolidationRunStopReason, type RunMemoryConsolidationOptions, isInstructionEntry, type MemoryEntryOrigin, type MemoryOriginCause, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_CAPTURE_OPTOUT_NOTICE, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, SESSION_CAPTURE_OPTOUT_DIR, markSessionCaptureOptOut, readSessionCaptureOptOut, listSessionCaptureOptOut, fileSessionCaptureRecordStore, type SessionCaptureOptOutRecord, type SessionCaptureOptOutMarkOutcome, type SessionCaptureRecordStore, readV2HeaderHints, type V2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_INDEX_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type CleanMemorySearchHit, type ExposedMemorySearchHit, MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, memoryExposureIndexRow, type MemoryGetDetails, type MemoryIndexDetails, type MemoryIndexRow, type CleanMemoryIndexRow, type ExposedMemoryIndexRow, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, type TransferEvidence, type CommittedBinding, type CommittedEntrySnapshot, type CommittedScopeSnapshots, type EntryCustodyReport, erasureSelectHash, type EraseMemoryEntriesInput, type ErasureSelect, type ErasedBinding, type MemoryErasureAttestation, computeMemoryBundleHash, type MemoryExportBundle, type MemoryImportReport, type MemoryExportSnapshot, type MemoryBundleImportPlan, type BundleChallengeRow, type BundleLineageRow, type BundlePollutedSession, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type MemoryScopeEnumeration, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
221
222
  export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, type SharedMemoryStoreProvider, type SharedMemoryStoreReader, type SharedMemoryPagedList, type SharedMemoryStoreInfo, type SharedMemoryDocumentEntry, type SharedMemorySnapshot, type SharedMemoryRequestContext, type MemoryListDetails, type MemoryReadDetails, } from "./core/shared-memory/types.js";
222
223
  export { sharedMemoryStoreContract, type SharedMemoryFixture, type SharedMemoryStoreContractHooks, } from "./core/shared-memory/contract.js";
@@ -239,7 +240,7 @@ export { stripEngineMetadata } from "./internal/llm.js";
239
240
  export { EVENT_PROMPT_REGISTRY, eventDefaultOn, type CompiledEventPrompt, type EventDedupe, type EventDefaultPolicy } from "./prompt-assembly/event-registry.js";
240
241
  export { contextUsageSections, type ContextUsageSection } from "./prompt-assembly/section-usage.js";
241
242
  export type { CompiledSection, ComposedPrompt, PackSectionDeclaration, PromptCacheClass, PromptCarrier, PromptMutability, PromptOwner, PromptPack, PromptRenderCadence, PromptRuntimeFacts, PromptSlot, PromptTrust, SectionRenderInputs, } from "./prompt-assembly/types.js";
242
- export { type ReasoningIntensity, type ReasoningResolution, type ResolvedReasoning, type ReasoningFormat, DEFAULT_EFFORT_LEVELS, REASONING_BUDGET_SHARE, isThinkingLevel, rankOf, resolveEffort, reasoningEffortLevelsOf, resolveBinary, reasoningBudgetShare, resolveReasoning, thinkingOffExpressible, declaredOffSpelling, type OffCapabilityModel, resolveReasoningProfile, type ReasoningTier, type ReasoningProfileFlags, } from "./brain/reasoning.js";
243
+ export { type ReasoningIntensity, type ReasoningResolution, type ResolvedReasoning, type ReasoningFormat, DEFAULT_EFFORT_LEVELS, REASONING_BUDGET_SHARE, isThinkingLevel, rankOf, resolveEffort, reasoningEffortLevelsOf, resolveBinary, reasoningBudgetShare, resolveReasoning, thinkingOffSpelled, declaredOffSpelling, type OffCapabilityModel, resolveReasoningProfile, type ReasoningTier, type ReasoningProfileFlags, } from "./brain/reasoning.js";
243
244
  export { DESIGN_REVIEW_PROMPTS, CODE_REVIEW_PROMPT, SCENARIO_REGISTRY, runScenario, type ScenarioId, type CodeReviewMode, type ScenarioProfile, type RunScenarioOptions, type RunScenarioResult, } from "./scenarios/scenario-registry.js";
244
245
  export { teacherMode, TEACHER_PROFILE, type TeacherModePair, type TeacherProfile, } from "./scenarios/teacher-quickstart.js";
245
246
  export { loadOrchestrationEnv, DEFAULT_REASONING_INTENSITY, type OrchestrationMode, type OrchestrationEnv, } from "./scenarios/env.js";
@@ -329,7 +330,7 @@ export { ROUTE_ADJUDICATION_CONFORMANCE_CORPUS, type RouteAdjudicationVector } f
329
330
  export { type BrainTimeoutConfig } from "./brain/timeout.js";
330
331
  export { createAssistantMessageEventStream } from "./internal/llm.js";
331
332
  export type { AssistantMessage, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, StopReason, StreamFn, TextContent, ThinkingContent, ToolCall, ToolResultMessage, Usage, UserMessage, } from "./internal/llm.js";
332
- export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, TrackFileEditHook, TrackEditRequest, TrackEditResult, FileEditedHook, FileEditedNotice, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, BrainRetryErrClass, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, ResumePreflightInfo, ResumePreflightVerdict, EngineNotice, RuntimeCaps, BackgroundChildEvent, DelegationLifecycleEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskLimits, StaleToolResultOffloadOptions, TaskResult, TerminalCause, PausedCause, EffectiveMemoryScopes, RemoteEnvFailureNote, TaskSpec, ShellGateDoctrine, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ToolInputVerdict, ToolInputValidationContext, ReversibilityVerdict, ToolEffect, ToolContentOrigin, WorkflowGovernanceBaseline, DelegationTaskType, } from "./core/types.js";
333
+ export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, TrackFileEditHook, TrackEditRequest, TrackEditResult, FileEditedHook, FileEditedNotice, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, BrainRetryErrClass, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, ResumePreflightInfo, ResumePreflightVerdict, EngineNotice, RuntimeCaps, BackgroundChildEvent, DelegationLifecycleEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskLimits, StaleToolResultOffloadOptions, TaskResult, TerminalCause, PausedCause, EffectiveMemoryScopes, RemoteEnvFailureNote, TaskSpec, ShellGateDoctrine, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ToolInputVerdict, ToolInputValidationContext, ReversibilityVerdict, ReadRootGrantCandidate, ToolEffect, ToolContentOrigin, WorkflowGovernanceBaseline, DelegationTaskType, } from "./core/types.js";
333
334
  export { Type } from "typebox";
334
335
  export type { TSchema, Static } from "typebox";
335
336
  export { explainPromptAssembly, describeDefaultPack, type DefaultPackDescription, type ExplainInput } from "./prompt-assembly/explain.js";
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ export { getShellConfig, isWslBashLauncher } from "./engine/execution-env/node-e
71
71
  export { isSecretEnvKey, scrubSecretEnv } from "./core/secret-env.js";
72
72
  export { MAX_EXEC_OUTPUT_BYTES, RollingTailBuffer, markTruncated } from "./core/exec-output-tail.js";
73
73
  export { physicalPathOf, physicalPathOfExisting } from "./core/physical-path.js";
74
- export { RemoteExecutionError, hasDestroy, isRemoteExecutionEnv, isSuspendable, isIsolated, missingRestoreSurface, isRetryableRemoteErrorCode, RETRYABLE_REMOTE_ERROR_CODES, } from "./core/remote-env.js";
74
+ export { RemoteExecutionError, hasDestroy, isRemoteExecutionEnv, isSuspendable, isIsolated, canonicalPathIsAuthoritative, missingRestoreSurface, isRetryableRemoteErrorCode, RETRYABLE_REMOTE_ERROR_CODES, } from "./core/remote-env.js";
75
75
  export { withRetry } from "./core/with-retry.js";
76
76
  export { addWorktree, pruneWorktrees, WORKTREE_PARENT } from "./core/git-worktree-env.js";
77
77
  export { runExecGate } from "./core/exec-gate.js";
@@ -104,7 +104,7 @@ export { CacheBreakDetector } from "./core/cache-break-detector.js";
104
104
  export { maybeCompact } from "./core/auto-compaction.js";
105
105
  export { brainToRuntime } from "./core/runtime.js";
106
106
  export { createSensitivePathPolicy, RECOMMENDED_SENSITIVE_PATTERNS } from "./core/sensitive-path-policy.js";
107
- export { createFsWriteGatePolicy } from "./core/fs-write-gate-policy.js";
107
+ export { createFsWriteGatePolicy, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE } from "./core/fs-write-gate-policy.js";
108
108
  export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
109
109
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
110
110
  export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, } from "./core/task-notification.js";
@@ -150,8 +150,9 @@ export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./core/runtime-cap
150
150
  export { SETTLEMENT_KINDS, isSettlementKind, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, isDeniedBy, DENIED_BY_MAY_VETO, CLASSIFIER_DENY_CAUSES, isClassifierDenyCause, DENIED_BY_MAY_CARRY_CAUSE, screenGateOutcome } from "./core/gate-outcome.js";
151
151
  export {} from "./core/hooks.js";
152
152
  export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps } from "./core/swappable-deps.js";
153
- export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, classifierUnavailableDenyMessage, CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE, AUTO_MODE_CLASSIFIER_FALLBACKS, isAutoModeClassifierFallback, AUTO_MODE_FALLBACK_CAUSES, isAutoModeFallbackCause, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, } from "./core/auto-mode.js";
153
+ export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, classifierUnavailableDenyMessage, classifierParseFailureDenyMessage, AUTO_MODE_PARSE_FAILURES, isAutoModeParseFailure, AUTO_MODE_CLASSIFIER_FALLBACKS, isAutoModeClassifierFallback, AUTO_MODE_FALLBACK_CAUSES, isAutoModeFallbackCause, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, } from "./core/auto-mode.js";
154
154
  export { AUTO_MODE_DENIAL_LIMIT_DEFAULTS, AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS, AUTO_MODE_CLASSIFIER_MAX_TOKENS } from "./core/auto-mode-defaults.js";
155
+ export { classifierSeatModel } from "./core/runner/classifier-ladder.js";
155
156
  export { buildAutoModePrompt, renderAutoModeWindow, renderAutoModeAction, AUTO_MODE_DEFAULTS_SENTINEL, } from "./core/auto-mode-prompt.js";
156
157
  export { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./core/auto-mode-prompt-assets.js";
157
158
  export { AUTO_MODE_ARMING_RECIPE_VERSION, autoModeArmingRecipeOf, sanitizeAutoModeArmingRecipe, foldAutoModeArming, } from "./core/auto-mode-arming.js";
@@ -174,7 +175,7 @@ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootA
174
175
  export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, } from "./stores/file/adoption/adopt.js";
175
176
  export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreToolUseConstraintPolicy, normalizePersistedRuleHit, } from "./core/hooks.js";
176
177
  export { HOOK_FAMILIES, HOOKS_INVALID_CODE } from "./core/hook-seats.js";
177
- export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, } from "./core/memory.js";
178
+ export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, MemoryProjectionError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, } from "./core/memory.js";
178
179
  export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, buildMemoryInstruction, writableRootOf, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_ORIGIN_CAUSES, committedOriginOf, originEquals, consolidationExposedFrontmatter, ambiguousOriginRepresentation, committedDistilledOf, distilledEquals, CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, memoryConsolidationWithheldNotice, DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, MEMORY_DISTILLER_PURITY_CONTRACT_V1, detectCleanArmVerbatimLeak, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, CONSOLIDATION_RUN_STOP_REASONS, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, isInstructionEntry, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_CAPTURE_OPTOUT_NOTICE, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, SESSION_CAPTURE_OPTOUT_DIR, markSessionCaptureOptOut, readSessionCaptureOptOut, listSessionCaptureOptOut, fileSessionCaptureRecordStore, readV2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_INDEX_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, memoryExposureIndexRow, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, erasureSelectHash, computeMemoryBundleHash, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, migrateScope, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, syncMemoryScope, } from "./core/memory-engine/index.js";
179
180
  export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, } from "./core/shared-memory/types.js";
180
181
  export { sharedMemoryStoreContract, } from "./core/shared-memory/contract.js";
@@ -196,7 +197,7 @@ export { CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore
196
197
  export { stripEngineMetadata } from "./internal/llm.js";
197
198
  export { EVENT_PROMPT_REGISTRY, eventDefaultOn } from "./prompt-assembly/event-registry.js";
198
199
  export { contextUsageSections } from "./prompt-assembly/section-usage.js";
199
- export { DEFAULT_EFFORT_LEVELS, REASONING_BUDGET_SHARE, isThinkingLevel, rankOf, resolveEffort, reasoningEffortLevelsOf, resolveBinary, reasoningBudgetShare, resolveReasoning, thinkingOffExpressible, declaredOffSpelling, resolveReasoningProfile, } from "./brain/reasoning.js";
200
+ export { DEFAULT_EFFORT_LEVELS, REASONING_BUDGET_SHARE, isThinkingLevel, rankOf, resolveEffort, reasoningEffortLevelsOf, resolveBinary, reasoningBudgetShare, resolveReasoning, thinkingOffSpelled, declaredOffSpelling, resolveReasoningProfile, } from "./brain/reasoning.js";
200
201
  export { DESIGN_REVIEW_PROMPTS, CODE_REVIEW_PROMPT, SCENARIO_REGISTRY, runScenario, } from "./scenarios/scenario-registry.js";
201
202
  export { teacherMode, TEACHER_PROFILE, } from "./scenarios/teacher-quickstart.js";
202
203
  export { loadOrchestrationEnv, DEFAULT_REASONING_INTENSITY, } from "./scenarios/env.js";
@@ -1,4 +1,5 @@
1
1
  import { assertSupportedAgentIsolation } from "./workflow.js";
2
+ import { childClampSeats } from "../core/child-clamp-seats.js";
2
3
  import { WorkflowScriptError } from "./workflow-meta.js";
3
4
  import { buildGovernedChildSpec, emitIgnoredAgentOptionsNotice, overlayWorktreeBaseline, MAX_ANNOUNCED_KEYS, SPEC_KEY_ALIASES, WHITELIST_KEYS, } from "./workflow-governance.js";
4
5
  const ACCEPTED_AGENT_OPTION_KEYS = ["label", "phase", "schema", "agentType", "isolation"];
@@ -77,27 +78,16 @@ export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentChe
77
78
  if (parentCheckpointStore !== undefined && (parentCheckpointStore === "disabled" || childSpec.checkpointStore === undefined)) {
78
79
  childSpec.checkpointStore = parentCheckpointStore;
79
80
  }
80
- if (parentReadFace) {
81
- const pf = parentReadFace();
82
- if (pf === "roots" && childSpec.readFace !== "roots")
83
- childSpec.readFace = "roots";
84
- }
85
- if (parentWriteFace) {
86
- const pw = parentWriteFace();
87
- if (pw === "roots" && childSpec.writeFace !== "roots")
88
- childSpec.writeFace = "roots";
89
- }
90
- if (parentReadDenyPatterns) {
91
- const pd = parentReadDenyPatterns();
92
- if (pd !== undefined && pd.length > 0) {
93
- childSpec.readDenyPatterns = childSpec.readDenyPatterns !== undefined ? [...childSpec.readDenyPatterns, ...pd] : [...pd];
94
- }
95
- }
96
- if (parentHandsReadOnly === true) {
97
- childSpec.handsReadOnly = true;
98
- }
81
+ const pd = parentReadDenyPatterns?.();
82
+ const workflowChildClamps = childClampSeats({
83
+ handsReadOnly: parentHandsReadOnly,
84
+ interactiveTools: parentInteractiveToolsOff === true ? false : undefined,
85
+ readFace: parentReadFace?.() === "roots" ? "roots" : childSpec.readFace,
86
+ writeFace: parentWriteFace?.() === "roots" ? "roots" : childSpec.writeFace,
87
+ readDenyPatterns: pd !== undefined && pd.length > 0 ? [...(childSpec.readDenyPatterns ?? []), ...pd] : childSpec.readDenyPatterns,
88
+ });
89
+ Object.assign(childSpec, workflowChildClamps);
99
90
  if (parentInteractiveToolsOff === true) {
100
- childSpec.interactiveTools = false;
101
91
  if (childSpec.interactionPosture === "interactive")
102
92
  delete childSpec.interactionPosture;
103
93
  }
@@ -1,7 +1,6 @@
1
1
  import { type Embedder, type MemoryNoteHeader, type MemoryNoteRecord, type MemoryStore, type MemoryVectorMode, type ScoredMemory, type StructuredNoteInput } from "../../core/memory.js";
2
2
  export declare class FileMemoryStore implements MemoryStore {
3
3
  private readonly root;
4
- private readonly tmpDir;
5
4
  /** Optional injected embedder (design/81 Slice 5). When present, note bodies are embedded (in the background,
6
5
  * after the durable note write) into a rebuildable sidecar index and `searchScored` ranks by cosine distance
7
6
  * (portable / in-process, mirrors PgMemoryStore's jsonb mode). Absent ⇒ lexical, byte-identical to before. */
@@ -59,7 +58,11 @@ export declare class FileMemoryStore implements MemoryStore {
59
58
  * Only when an embedder is injected; a failure/timeout leaves the row lexical (fail-open). The durable note
60
59
  * write has ALREADY happened before this is called, so this never gates durability. */
61
60
  private queueEmbed;
62
- /** Rewrite the derived `MEMORY.md` projection atomically (CC markdown parity). */
61
+ /** Rewrite the derived `MEMORY.md` projection atomically (CC markdown parity), STAGED IN THE TARGET'S
62
+ * OWN DIRECTORY: `rename(2)` is defined only within one filesystem, so staging beside the target makes
63
+ * the two share a mount by construction where a fixed scratch dir elsewhere made it deployment luck (a
64
+ * memory tree on its own device took `EXDEV` on every note). EVERY caller is POST-DURABLE, so a failure
65
+ * here cannot mean the note was lost: it is {@link MemoryProjectionError}, that phase's one name. */
63
66
  private writeProjection;
64
67
  read(scope: string): string | null;
65
68
  append(scope: string, note: string): string | void;
@@ -1,6 +1,6 @@
1
1
  import { join } from "node:path";
2
2
  import { uuidv7 } from "../../internal/harness.js";
3
- import { firstSentence, lexicalSearchMatch, parseNoteTimestamp, } from "../../core/memory.js";
3
+ import { firstSentence, lexicalSearchMatch, parseNoteTimestamp, MemoryProjectionError, } from "../../core/memory.js";
4
4
  import { cosineDistance, jaccardDistance, termSet } from "../../core/memory-vector.js";
5
5
  import { canonicalStoreKey, AppendLog, atomicWriteFile, ensureDir, readJsonlRecords, sanitizeScope, } from "./fs-atomic.js";
6
6
  import { assertAdoptionBootGate } from "./adoption/marker.js";
@@ -8,7 +8,6 @@ const MAX_OPEN_MEMORY_SCOPES = 64;
8
8
  const sharedMemoryDirs = new Map();
9
9
  export class FileMemoryStore {
10
10
  root;
11
- tmpDir;
12
11
  embedder;
13
12
  embedTimeoutMs;
14
13
  pending = new Set();
@@ -31,7 +30,6 @@ export class FileMemoryStore {
31
30
  this.sharedScopes = { scopes: new Map(), refs: 1, openLogs: new Set() };
32
31
  sharedMemoryDirs.set(this.scopeKey, this.sharedScopes);
33
32
  }
34
- this.tmpDir = join(root, "tmp");
35
33
  this.embedder = opts.embedder;
36
34
  this.embedTimeoutMs = Math.max(0, opts.embedTimeoutMs ?? 10_000);
37
35
  ensureDir(this.root);
@@ -148,7 +146,13 @@ export class FileMemoryStore {
148
146
  }
149
147
  writeProjection(state) {
150
148
  const body = state.entries.map(renderBullet).join("\n");
151
- atomicWriteFile(this.tmpDir, join(state.dir, "MEMORY.md"), body ? `${body}\n` : "");
149
+ const target = join(state.dir, "MEMORY.md");
150
+ try {
151
+ atomicWriteFile(state.dir, target, body ? `${body}\n` : "");
152
+ }
153
+ catch (err) {
154
+ throw new MemoryProjectionError(target, err);
155
+ }
152
156
  }
153
157
  read(scope) {
154
158
  const { entries } = this.scopeState(scope);