@sema-agent/core 7.30.1 → 7.31.1

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 (80) hide show
  1. package/CHANGELOG.md +284 -0
  2. package/dist/agents/cross-session-judge.d.ts +4 -12
  3. package/dist/agents/cross-session-judge.js +4 -11
  4. package/dist/agents/peer-session-drain.js +0 -5
  5. package/dist/agents/send-message-tool.js +1 -0
  6. package/dist/agents/subagent.d.ts +1 -0
  7. package/dist/agents/subagent.js +1 -0
  8. package/dist/core/ask-origin.d.ts +6 -0
  9. package/dist/core/ask-origin.js +10 -0
  10. package/dist/core/checkpoint-store.d.ts +14 -0
  11. package/dist/core/checkpoint-store.js +1 -0
  12. package/dist/core/engine-notice.d.ts +4 -0
  13. package/dist/core/governance-codes.d.ts +1 -1
  14. package/dist/core/governance-codes.js +4 -0
  15. package/dist/core/memory-engine/data-plane.d.ts +3 -1
  16. package/dist/core/memory-engine/data-plane.js +1 -1
  17. package/dist/core/memory-engine/delegation-settlement.d.ts +44 -3
  18. package/dist/core/memory-engine/delegation-settlement.js +38 -3
  19. package/dist/core/memory-engine/engine.d.ts +133 -27
  20. package/dist/core/memory-engine/engine.js +452 -151
  21. package/dist/core/memory-engine/entry-scan.d.ts +23 -0
  22. package/dist/core/memory-engine/entry-scan.js +55 -0
  23. package/dist/core/memory-engine/file-backend.d.ts +31 -23
  24. package/dist/core/memory-engine/file-backend.js +174 -100
  25. package/dist/core/memory-engine/layer-observations.d.ts +109 -0
  26. package/dist/core/memory-engine/layer-observations.js +148 -0
  27. package/dist/core/memory-engine/layout.d.ts +40 -4
  28. package/dist/core/memory-engine/layout.js +43 -7
  29. package/dist/core/memory-engine/types.d.ts +26 -2
  30. package/dist/core/parent-spec-seat.d.ts +4 -1
  31. package/dist/core/parent-spec-seat.js +2 -0
  32. package/dist/core/park-selfcheck.js +1 -0
  33. package/dist/core/runner/announce-once-ledger.d.ts +26 -0
  34. package/dist/core/runner/announce-once-ledger.js +37 -0
  35. package/dist/core/runner/contracts.d.ts +22 -5
  36. package/dist/core/runner/denial-limit-arms.d.ts +2 -2
  37. package/dist/core/runner/prepare-acquire-reconcile.d.ts +0 -4
  38. package/dist/core/runner/prepare-acquire-reconcile.js +1 -3
  39. package/dist/core/runner/prepare-file-history.d.ts +13 -15
  40. package/dist/core/runner/prepare-file-history.js +57 -43
  41. package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
  42. package/dist/core/runner/prepare-listings.d.ts +3 -1
  43. package/dist/core/runner/prepare-listings.js +2 -2
  44. package/dist/core/runner/prepare-memory.js +16 -5
  45. package/dist/core/runner/prepare-park-ask.js +27 -16
  46. package/dist/core/runner/prepare-prompt-inputs.d.ts +8 -1
  47. package/dist/core/runner/prepare-prompt-inputs.js +5 -3
  48. package/dist/core/runner/prepare-task.js +7 -5
  49. package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -0
  50. package/dist/core/runner/prepare-wiring-manifest.js +2 -1
  51. package/dist/core/runner/run-attachment-seats.js +1 -1
  52. package/dist/core/runner/run-leg.js +61 -52
  53. package/dist/core/runner/run-settle-and-teardown.js +4 -0
  54. package/dist/core/runner/run-turn-boundary.js +6 -6
  55. package/dist/core/runner/turn-attachments.d.ts +6 -6
  56. package/dist/core/runner-deps.d.ts +3 -1
  57. package/dist/core/secret-env.d.ts +12 -1
  58. package/dist/core/secret-env.js +27 -2
  59. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  60. package/dist/core/store-contracts/checkpoint-store-contract.js +22 -0
  61. package/dist/core/task-result.d.ts +1 -1
  62. package/dist/core/task-spec.d.ts +17 -8
  63. package/dist/core/tool-spec.d.ts +1 -1
  64. package/dist/core/wiring-manifest.d.ts +10 -0
  65. package/dist/core/wiring-manifest.js +7 -1
  66. package/dist/engine/execution-env/node-execution-env.d.ts +6 -0
  67. package/dist/engine/execution-env/node-execution-env.js +15 -17
  68. package/dist/engine/harness/agent-harness.js +7 -5
  69. package/dist/engine/lsp/node-lsp-manager.d.ts +4 -0
  70. package/dist/engine/lsp/node-lsp-manager.js +10 -4
  71. package/dist/index.d.ts +1 -1
  72. package/dist/index.js +1 -1
  73. package/dist/tools/fs/shell-read-plan.d.ts +2 -2
  74. package/dist/tools/fs/shell-read-plan.js +15 -10
  75. package/dist/tools/fs/shell-read-word.d.ts +5 -4
  76. package/dist/tools/fs/shell-read-word.js +3 -2
  77. package/package.json +1 -1
  78. package/test/export-surface.snapshot.json +7 -1
  79. package/wire-changes/core@7.31.0.json +705 -0
  80. package/wire-changes/core@7.31.1.json +189 -0
@@ -213,12 +213,12 @@ export interface TaskSpec {
213
213
  * that could support this without a second root is a recorded design candidate, not built.)
214
214
  *
215
215
  * File-history interaction (design/381): with {@link restoreFiles}, the `"before"` file anchor is the
216
- * target's OWN entry id first — history boundaries are keyed by a turn's initial entry and mean "file
217
- * state when that turn began", so B(target) IS the before-state. When the target carries no boundary
218
- * (a mixed-epoch session, or a non-turn-initial target), the anchor falls back to the nearest
219
- * boundary-carrying ANCESTOR, so files and context land on the same side of the cut. If no such
220
- * boundary exists, prepare fails loud (`rewind_snapshot.unresolvable`) instead of silently leaving
221
- * the files at their pre-rewind state while the conversation moved back.
216
+ * target's OWN entry id — history boundaries are keyed by a turn's initial entry and mean "file state
217
+ * when that turn began", so B(target) IS the before-state. A target with no boundary of its own (a
218
+ * steer / follow-up entry, a turn that recorded no history, a mixed-epoch session) refuses at prepare
219
+ * (`rewind_snapshot.unresolvable`) with the files untouched: an earlier boundary would also revert edits
220
+ * the conversation keeps, so the anchor never falls back to an ancestor and never crosses into an
221
+ * earlier turn.
222
222
  * Fail-loud: setting this without {@link resumeAt}, or an unknown mode value, rejects at prepare.
223
223
  */
224
224
  resumeAtMode?: "at" | "before";
@@ -311,6 +311,15 @@ export interface TaskSpec {
311
311
  count?: number;
312
312
  role?: ModelRole;
313
313
  };
314
+ /**
315
+ * The per-run OFF SWITCH for file history (`"disabled"`, the only word; `checkpointStore`'s spelling). Omitted ⇒
316
+ * the deployment (`RunnerDeps.fileHistoryStore` / `fileHistoryCapture`) decides. `"disabled"` ⇒ this run makes no history-store call: no first-touch record
317
+ * (the edit proceeds), no turn boundary, no fork carry, no liveness advisory; a restore request ({@link restoreFiles}
318
+ * / {@link rewindFilesTo}) refuses `rewind.capture_disabled` (the store may be wired; the request turned it off).
319
+ * The manifest reports `fileHistory: "disabled"`. Children inherit it (parent spec seat; a retained child woken by
320
+ * such a run, for that cycle); no word turns it back on below. Any other value refuses (`rewind.invalid_spec`).
321
+ */
322
+ fileHistory?: "disabled";
314
323
  /**
315
324
  * design/381 (DV-9) — the RESTORE half of file rewind: when this task uses {@link resumeAt} to branch at
316
325
  * a prior entry, ALSO converge the working tree's **agent-edited files** (the tracked set — files this
@@ -323,8 +332,8 @@ export interface TaskSpec {
323
332
  * Fail-loud: setting this without {@link resumeAt} rejects at prepare (`rewind.invalid_spec`) — a
324
333
  * restore with no anchor is a contradiction, not a default.
325
334
  *
326
- * There is NO capture flag anymore: first-touch tracking is ALWAYS ON whenever a
327
- * `RunnerDeps.fileHistoryStore` is wired (per-edited-file backups are near-zero cost — no tree walk,
335
+ * There is NO capture request anymore: first-touch tracking is ON whenever a `RunnerDeps.fileHistoryStore`
336
+ * is wired, `RunnerDeps.fileHistoryCapture` is not `"off"`, and no {@link fileHistory} off switch is set (per-edited-file backups are near-zero cost — no tree walk,
328
337
  * no size gate), which is what retired the old `rewindFiles` capture request. The retired
329
338
  * `rewindFiles` spelling is handled at prepare: its capture sense (true, no `resumeAt`) is a
330
339
  * tolerated no-op with a deprecation disclosure (the intent is structurally satisfied); its restore
@@ -752,7 +752,7 @@ export interface ToolExecuteContext {
752
752
  parentCwd?: string;
753
753
  /**
754
754
  * The RUNNING task's file-history LINEAGE (Runner-filled, read-only, never a model/tool argument;
755
- * present iff a `RunnerDeps.fileHistoryStore` is live for the run): the history scope this run
755
+ * present iff the run records file history — a `RunnerDeps.fileHistoryStore` wired, capture not `"off"`, not `"disabled"`): the history scope this run
756
756
  * records its first-touch edits into and the TREE those records' keys are minted against — the
757
757
  * canonical root spelling plus the env's filesystem identity (`fs`: a remote workspace's handle,
758
758
  * `"host"` for the control-plane host's filesystem, else a per-env-instance token).
@@ -407,6 +407,14 @@ export interface WiringManifest {
407
407
  autoConsolidation?: {
408
408
  onRecommendation: true;
409
409
  };
410
+ /**
411
+ * EFFECTIVE half only, ALWAYS present on an engine-minted effective manifest: does this leg capture file history.
412
+ * `"on"` — it records (store wired, neither layer declared it off, a filesystem env mounted). `"disabled"` — it does
413
+ * not, whichever layer or missing half made it so (`TaskSpec.fileHistory`, `RunnerDeps.fileHistoryStore: "disabled"`, `RunnerDeps.fileHistoryCapture: "off"`,
414
+ * no store, no filesystem env); the host knows its own deployment seat. ABSENT = an older mint or an external
415
+ * derivation, never a state. Out of {@link configFingerprint}: the request word is a per-task seat (`hooks` reasoning).
416
+ */
417
+ fileHistory?: "on" | "disabled";
410
418
  /** EFFECTIVE half only — a short, non-sensitive fingerprint (sha256 prefix over the canonical
411
419
  * JSON of this manifest's own resolved facts; every field here is an enum/boolean/count, no
412
420
  * secrets) so an operator can correlate legs that ran under the same resolved assembly. */
@@ -498,6 +506,8 @@ export interface WiringFacts {
498
506
  /** Effective half only — `RunnerDeps.autoRunOnRecommendation === true`
499
507
  * ({@link WiringManifest.autoConsolidation}); false ⇒ no section (byte-identical manifest). */
500
508
  autoConsolidationOnRecommendation?: boolean;
509
+ /** Effective half only — the file-history phase's word ({@link WiringManifest.fileHistory}); the engine always passes it. */
510
+ fileHistory?: "on" | "disabled";
501
511
  }
502
512
  /** Named view of the deps seats the static half reads (a `Pick` of the real {@link RunnerDeps} —
503
513
  * single-source shapes, no parallel hand-copied interface). */
@@ -84,6 +84,11 @@ function readAutoModeFact(fact) {
84
84
  }
85
85
  return { armed, reason, ...(deniedSource !== undefined ? { deniedSource } : {}) };
86
86
  }
87
+ function readFileHistoryFact(word) {
88
+ if (word !== "on" && word !== "disabled")
89
+ throw new Error(`the file-history wiring fact ${JSON.stringify(word)} is not one of on|disabled`);
90
+ return word;
91
+ }
87
92
  export function deriveWiringManifest(facts) {
88
93
  if (facts.half === "static" && facts.leg !== undefined) {
89
94
  throw new Error("a static wiring manifest has no leg — leg identity is an effective-half fact");
@@ -146,13 +151,14 @@ export function deriveWiringManifest(facts) {
146
151
  ? { readDeny: { builtinTiers: [...facts.readDenyBuiltinTiersActive] } }
147
152
  : {}),
148
153
  ...(facts.half === "effective" && facts.autoConsolidationOnRecommendation === true ? { autoConsolidation: { onRecommendation: true } } : {}),
154
+ ...(facts.half === "effective" && facts.fileHistory !== undefined ? { fileHistory: readFileHistoryFact(facts.fileHistory) } : {}),
149
155
  ...(facts.half === "effective" && facts.teamRole !== undefined ? { teamRole: facts.teamRole } : {}),
150
156
  ...(facts.half === "effective" && facts.peerPresence !== undefined && (facts.peerPresence.agent !== undefined || facts.peerPresence.intent !== undefined)
151
157
  ? { peerPresence: { ...(facts.peerPresence.agent !== undefined ? { agent: facts.peerPresence.agent } : {}), ...(facts.peerPresence.intent !== undefined ? { intent: facts.peerPresence.intent } : {}) } }
152
158
  : {}),
153
159
  };
154
160
  if (facts.half === "effective") {
155
- const { leg: _leg, mcp: _mcp, tools: _tools, lsp: _lsp, hooks: _hooks, teamRole: _teamRole, peerPresence: _peerPresence, ...assembly } = manifest;
161
+ const { leg: _leg, mcp: _mcp, tools: _tools, lsp: _lsp, hooks: _hooks, teamRole: _teamRole, peerPresence: _peerPresence, fileHistory: _fileHistory, ...assembly } = manifest;
156
162
  const { provenance: _askSeat, ...askForHash } = assembly.ask;
157
163
  const { provenance: _questionSeat, ...questionForHash } = assembly.question;
158
164
  manifest.configFingerprint = createHash("sha256")
@@ -1,5 +1,6 @@
1
1
  import { type ExecResult, type ExecutionEnv, ExecutionError, type ExecutionEnvExecOptions, FileError, type FileInfo, type Result } from "../harness/types.js";
2
2
  import { type SecretEnvFinding } from "../../core/secret-env.js";
3
+ import type { EngineNotice } from "../../core/engine-notice.js";
3
4
  import type { SymlinkChain } from "../harness/types.js";
4
5
  import type { BackgroundShellCapability, BackgroundShellId, BackgroundPoll, BackgroundSpawnOptions } from "../../core/background-shell.js";
5
6
  import { BackgroundShellError } from "../../core/background-shell.js";
@@ -106,6 +107,7 @@ export declare class NodeExecutionEnv implements ExecutionEnv, BackgroundShellCa
106
107
  private bgCounter;
107
108
  private readonly retainBackgroundProcesses;
108
109
  private readonly onSecretEnvScrub?;
110
+ private readonly onNotice?;
109
111
  /** design/128 T1-1: lazily-created env-owned spool dir (mkdtemp under os tmp — unpredictable, never the cwd). */
110
112
  private bgSpoolDir?;
111
113
  readonly backgroundCapabilities: BackgroundShellCapability["backgroundCapabilities"];
@@ -135,6 +137,10 @@ export declare class NodeExecutionEnv implements ExecutionEnv, BackgroundShellCa
135
137
  * "scrub" leg withholds from a shell child. Absent ⇒ a once-per-process console summary (loud
136
138
  * enough to notice, quiet enough not to spam). Never called on the "all"/allowlist legs. */
137
139
  onSecretEnvScrub?: (findings: readonly SecretEnvFinding[]) => void;
140
+ /** #1081 — the structured-notice seat for the SAME withheld-keys disclosure (`EngineNotice`,
141
+ * `code: "config.secret_env_scrubbed"`, `detail.names`); independent of `onSecretEnvScrub` (a raw
142
+ * findings collector) except: that seat wired alone stays console-silent, as before this existed. */
143
+ onNotice?: (notice: EngineNotice) => void;
138
144
  });
139
145
  get schedulerCapabilities(): SchedulerCapability["schedulerCapabilities"];
140
146
  schedule(intent: ScheduledIntent, ctx: SchedulerContext): Promise<Result<{
@@ -7,7 +7,7 @@ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
7
7
  import { createInterface } from "node:readline";
8
8
  import { ExecutionError, err, FileError, ok, toError, } from "../harness/types.js";
9
9
  import { killProcessTree, shutdownDebug } from "./kill-tree.js";
10
- import { scrubSecretEnv } from "../../core/secret-env.js";
10
+ import { __resetSecretEnvScrubNoticeAnnouncement, announceSecretEnvScrub, scrubSecretEnv } from "../../core/secret-env.js";
11
11
  import { physicalPathOfAsync } from "../../core/physical-path.js";
12
12
  import { isUncSpelling } from "../../tools/fs/safety.js";
13
13
  import { RollingTailBuffer, markTruncated, newStreamCursorState, sliceStreamIncrement, } from "../../core/exec-output-tail.js";
@@ -197,13 +197,12 @@ export async function getShellConfig(customShellPath) {
197
197
  return ok({ shell: "sh", args: ["-c"] });
198
198
  }
199
199
  const EXEC_FORCE_SETTLE_GRACE_MS = 3000;
200
- let secretScrubConsoleAnnounced = false;
201
200
  let secretScrubSeatDefectAnnounced = false;
202
201
  export function __resetSecretScrubAnnouncement() {
203
- secretScrubConsoleAnnounced = false;
204
202
  secretScrubSeatDefectAnnounced = false;
203
+ __resetSecretEnvScrubNoticeAnnouncement();
205
204
  }
206
- function getShellEnv(inheritEnv, baseEnv, extraEnv, onScrub) {
205
+ function getShellEnv(inheritEnv, baseEnv, extraEnv, onScrub, onNotice) {
207
206
  let inherited;
208
207
  if (inheritEnv === "all") {
209
208
  inherited = process.env;
@@ -230,20 +229,17 @@ function getShellEnv(inheritEnv, baseEnv, extraEnv, onScrub) {
230
229
  catch {
231
230
  }
232
231
  }
233
- else {
234
- if (onScrub !== undefined && !secretScrubSeatDefectAnnounced) {
235
- secretScrubSeatDefectAnnounced = true;
236
- try {
237
- console.warn(`onSecretEnvScrub seat holds ${onScrub === null ? "null" : typeof onScrub} — not a function; scrub findings fall back to the console summary (once per process)`);
238
- }
239
- catch {
240
- }
232
+ else if (onScrub !== undefined && !secretScrubSeatDefectAnnounced) {
233
+ secretScrubSeatDefectAnnounced = true;
234
+ try {
235
+ console.warn(`onSecretEnvScrub seat holds ${onScrub === null ? "null" : typeof onScrub} — not a function; scrub findings fall back to the console summary (once per process)`);
241
236
  }
242
- if (!secretScrubConsoleAnnounced) {
243
- secretScrubConsoleAnnounced = true;
244
- console.warn(`secret_env_scrubbed: ${findings.length} env key(s) withheld from the shell child (${findings.map((f) => f.key).join(", ")}) — once per process; wire onSecretEnvScrub for per-exec telemetry`);
237
+ catch {
245
238
  }
246
239
  }
240
+ if (!(typeof onScrub === "function" && onNotice === undefined)) {
241
+ announceSecretEnvScrub(onNotice, findings);
242
+ }
247
243
  }
248
244
  }
249
245
  return {
@@ -302,6 +298,7 @@ export class NodeExecutionEnv {
302
298
  bgCounter = 0;
303
299
  retainBackgroundProcesses;
304
300
  onSecretEnvScrub;
301
+ onNotice;
305
302
  bgSpoolDir;
306
303
  backgroundCapabilities;
307
304
  constructor(options) {
@@ -312,6 +309,7 @@ export class NodeExecutionEnv {
312
309
  this.scheduler = options.scheduler;
313
310
  this.retainBackgroundProcesses = options.retainBackgroundProcesses ?? false;
314
311
  this.onSecretEnvScrub = options.onSecretEnvScrub;
312
+ this.onNotice = options.onNotice;
315
313
  this.backgroundCapabilities = {
316
314
  supported: true,
317
315
  maxConcurrent: 8,
@@ -469,7 +467,7 @@ export class NodeExecutionEnv {
469
467
  child = spawn(shellConfig.value.shell, [...shellConfig.value.args, command], {
470
468
  cwd,
471
469
  detached: process.platform !== "win32",
472
- env: getShellEnv(this.inheritEnv, this.shellEnv, options?.env, this.onSecretEnvScrub),
470
+ env: getShellEnv(this.inheritEnv, this.shellEnv, options?.env, this.onSecretEnvScrub, this.onNotice),
473
471
  stdio: execSpoolFds ? ["ignore", execSpoolFds[0], execSpoolFds[1]] : ["ignore", "pipe", "pipe"],
474
472
  windowsHide: true,
475
473
  });
@@ -1221,7 +1219,7 @@ export class NodeExecutionEnv {
1221
1219
  child = spawn(shellConfig.value.shell, [...shellConfig.value.args, command], {
1222
1220
  cwd: options?.cwd ? resolvePath(this.cwd, options.cwd) : this.cwd,
1223
1221
  detached: process.platform !== "win32",
1224
- env: getShellEnv(this.inheritEnv, this.shellEnv, options?.env, this.onSecretEnvScrub),
1222
+ env: getShellEnv(this.inheritEnv, this.shellEnv, options?.env, this.onSecretEnvScrub, this.onNotice),
1225
1223
  stdio: spoolFds ? ["ignore", spoolFds[0], spoolFds[1]] : ["ignore", "pipe", "pipe"],
1226
1224
  windowsHide: true,
1227
1225
  });
@@ -858,7 +858,7 @@ export class AgentHarness {
858
858
  await this.handleAgentEvent({ type: "agent_end", messages: [failureMessage] }, signal);
859
859
  return [failureMessage];
860
860
  }
861
- async executeTurn(turnState, text, options) {
861
+ async executeTurn(turnState, text, options, abortController) {
862
862
  let activeTurnState = turnState;
863
863
  const userMessage = createUserMessage(text, options?.images, options);
864
864
  const beforeResult = await this.emitHook({
@@ -897,12 +897,10 @@ export class AgentHarness {
897
897
  }
898
898
  return messages;
899
899
  };
900
- const abortController = new AbortController();
901
900
  const getTurnState = () => activeTurnState;
902
901
  const setTurnState = (nextTurnState) => {
903
902
  activeTurnState = nextTurnState;
904
903
  };
905
- this.runAbortController = abortController;
906
904
  const runResultPromise = (async () => {
907
905
  try {
908
906
  return await runAgentLoop(takePrompts, this.createContext(turnState, beforeResult?.systemPrompt), this.createLoopConfig(getTurnState, setTurnState, beforeResult?.systemPrompt), (event) => this.handleAgentEvent(event, abortController.signal), abortController.signal, this.createStreamFn(getTurnState), undefined, this.loopTrace, {
@@ -938,7 +936,7 @@ export class AgentHarness {
938
936
  return mergeTruncatedOutputChain(newMessages, i);
939
937
  }
940
938
  }
941
- if (this.stopRequested)
939
+ if (this.stopRequested || abortController.signal.aborted)
942
940
  return undefined;
943
941
  if (this.trailingBatchEndedOn !== undefined)
944
942
  return this.trailingBatchEndedOn;
@@ -976,17 +974,21 @@ export class AgentHarness {
976
974
  }
977
975
  }
978
976
  this.phase = "turn";
977
+ const abortController = new AbortController();
978
+ this.runAbortController = abortController;
979
979
  const finishRunPromise = this.startRunPromise();
980
980
  try {
981
981
  await this.flushPendingSessionWrites();
982
982
  const turnState = await this.createTurnState();
983
- return await this.executeTurn(turnState, text, options);
983
+ return await this.executeTurn(turnState, text, options, abortController);
984
984
  }
985
985
  catch (error) {
986
986
  this.phase = "idle";
987
987
  throw normalizeHarnessError(error, "unknown");
988
988
  }
989
989
  finally {
990
+ if (this.runAbortController === abortController)
991
+ this.runAbortController = undefined;
990
992
  finishRunPromise();
991
993
  }
992
994
  }
@@ -2,6 +2,7 @@ import { type SessionWarmup } from "../../core/lsp-session.js";
2
2
  import { type LspChildProcess } from "./stdio-lsp-transport.js";
3
3
  import { type LspServerManager, type LspSession, type LspReadText } from "../../core/lsp.js";
4
4
  import type { ExecutionEnv } from "../../internal/harness-types.js";
5
+ import type { EngineNotice } from "../../core/engine-notice.js";
5
6
  /**
6
7
  * Default language → server command table (the DETERMINED supported set). Each value is a `command [args…]`
7
8
  * whose binary must be on the host `PATH`; absent ⇒ that language degrades. Mirrors the servers service bakes
@@ -22,6 +23,9 @@ export interface NodeLspManagerOptions {
22
23
  /** The language server's environment for the default spawn, verbatim. Omitted ⇒ the host environment scrubbed of
23
24
  * credential-shaped names (`scrubSecretEnv`, a tool child's scrub). Unread when {@link spawn} is supplied. */
24
25
  env?: Record<string, string>;
26
+ /** #1081 — the withheld-keys notice sink, SAME shared once-per-process latch/code as the shell-exec
27
+ * site's `NodeExecutionEnv.onNotice`. Absent ⇒ console only. Unread when {@link spawn} is supplied. */
28
+ onNotice?: (notice: EngineNotice) => void;
25
29
  /** Read a file's CURRENT text for the session's didOpen/didChange re-sync (default = local `fs.readFile` utf8). */
26
30
  readText?: LspReadText;
27
31
  /** Resolve the server `cwd`/`rootUri` for a file (default = env.cwd ?? nearest project marker ?? file dir). */
@@ -9,7 +9,7 @@ import { TransportLspSession } from "../../core/lsp-session.js";
9
9
  import { StdioLspTransport } from "./stdio-lsp-transport.js";
10
10
  import { MAX_LSP_FILE_BYTES, SharedAbortScope, settleOnAbort } from "../../core/lsp.js";
11
11
  import { asThrownError, thrownText } from "../../core/thrown-value.js";
12
- import { scrubSecretEnv } from "../../core/secret-env.js";
12
+ import { announceSecretEnvScrub, scrubSecretEnv } from "../../core/secret-env.js";
13
13
  export const DEFAULT_LSP_SERVERS = {
14
14
  typescript: "typescript-language-server --stdio",
15
15
  javascript: "typescript-language-server --stdio",
@@ -67,7 +67,7 @@ export class NodeLspManager {
67
67
  disposed = false;
68
68
  constructor(opts = {}) {
69
69
  this.servers = { ...DEFAULT_LSP_SERVERS, ...opts.servers };
70
- this.spawnFn = opts.spawn ?? (opts.env !== undefined ? lspSpawnWithEnv(opts.env) : defaultLspSpawn);
70
+ this.spawnFn = opts.spawn ?? lspSpawnWithEnv(opts.env, opts.onNotice);
71
71
  this.readTextFn = opts.readText ?? defaultLspReadText;
72
72
  this.resolveRootFn = opts.resolveRoot ?? defaultResolveRoot;
73
73
  this.log = opts.log ?? (() => { });
@@ -330,10 +330,16 @@ export function lspSpawnOptions(cwd) {
330
330
  : { cwd, stdio: ["pipe", "pipe", "ignore"] };
331
331
  }
332
332
  export const defaultLspSpawn = (command, args, cwd, signal) => lspSpawnWithEnv(undefined)(command, args, cwd, signal);
333
- const lspSpawnWithEnv = (env) => (command, args, cwd, signal) => new Promise((resolve, reject) => {
333
+ const lspSpawnWithEnv = (env, onNotice) => (command, args, cwd, signal) => new Promise((resolve, reject) => {
334
334
  let child;
335
335
  try {
336
- child = spawn(command, args, { ...lspSpawnOptions(cwd), env: env ?? scrubSecretEnv(process.env) });
336
+ let scrubbedEnv = env;
337
+ if (scrubbedEnv == null) {
338
+ const findings = [];
339
+ scrubbedEnv = scrubSecretEnv(process.env, findings);
340
+ announceSecretEnvScrub(onNotice, findings);
341
+ }
342
+ child = spawn(command, args, { ...lspSpawnOptions(cwd), env: scrubbedEnv });
337
343
  }
338
344
  catch (e) {
339
345
  return reject(asThrownError(e));
package/dist/index.d.ts CHANGED
@@ -229,7 +229,7 @@ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootA
229
229
  export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, type AdoptionStatus, type AdoptLocalDataRootOptions, type AdoptLocalDataRootResult, type AdoptionCarriageLeg, type AdoptionCarriageLegContext, type AdoptionConfigWitnessReceipt, } from "./stores/file/adoption/adopt.js";
230
230
  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";
231
231
  export { HOOK_FAMILIES, HOOKS_INVALID_CODE, type HookFamily, type WiringManifestHookEntry } from "./core/hook-seats.js";
232
- 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";
232
+ 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, MEMORY_PROJECT_KEY_ABSENT_REASONS, type MemoryProjectKeyAbsentReason, type MemoryProjectKey, } from "./core/memory.js";
233
233
  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, deriveShadowedSet, 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";
234
234
  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";
235
235
  export { sharedMemoryStoreContract, type SharedMemoryFixture, type SharedMemoryStoreContractHooks, } from "./core/shared-memory/contract.js";
package/dist/index.js CHANGED
@@ -179,7 +179,7 @@ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootA
179
179
  export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, } from "./stores/file/adoption/adopt.js";
180
180
  export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreToolUseConstraintPolicy, normalizePersistedRuleHit, } from "./core/hooks.js";
181
181
  export { HOOK_FAMILIES, HOOKS_INVALID_CODE } from "./core/hook-seats.js";
182
- export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, MemoryProjectionError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, } from "./core/memory.js";
182
+ export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, MemoryProjectionError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, MEMORY_PROJECT_KEY_ABSENT_REASONS, } from "./core/memory.js";
183
183
  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, deriveShadowedSet, 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";
184
184
  export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, } from "./core/shared-memory/types.js";
185
185
  export { sharedMemoryStoreContract, } from "./core/shared-memory/contract.js";
@@ -62,8 +62,8 @@ export interface ReadCandidate {
62
62
  * A deny row is a floor, so the judge resolves the spelling against these for deny rows ONLY — never to
63
63
  * establish a read. `cd sub && cd $'\x2e' && cat <deny row under sub>` (bash: `cd .`) is refused through it. */
64
64
  readonly floorBases?: readonly string[];
65
- /** A home word the plan does not place (the text may rebind `HOME`, or it sits in a value): spelled with the DECLARED
66
- * home. A deny row is a floor: the judge reads it for deny rows ONLY, never to establish a read. */
65
+ /** A home word the plan does not place (the text may rebind `HOME`, it sits in a value, or it is a literal tilde the
66
+ * program may expand itself): spelled with the DECLARED home. The judge reads it for deny rows ONLY. */
67
67
  readonly floorSpelled?: string;
68
68
  /** A deny-floor shadow of a `floorSpelled` spelling, established as a read and judged for deny rows only. */
69
69
  readonly denyOnly?: true;
@@ -193,7 +193,7 @@ function segmentFacts(segment, p, decl, home, nestedPlan) {
193
193
  unresolvedRaw.add(w.raw);
194
194
  }
195
195
  const homeWithheld = home === undefined && decl.homeDir !== undefined;
196
- const floor = (c) => { const f = c.expands === "unresolvable" && decl.homeDir !== undefined ? spelledWithHome(c.spelled, c.raw, decl.homeDir) : undefined; return f === undefined ? c : { ...c, floorSpelled: f }; };
196
+ const floor = (c) => { const f = (c.expands === "unresolvable" || c.spelled.startsWith("./~")) && decl.homeDir !== undefined ? spelledWithHome(c.spelled, c.raw, decl.homeDir) : undefined; return f === undefined ? c : { ...c, floorSpelled: f }; };
197
197
  const unsettle = (c) => floor((unresolvedRaw.has(c.raw) || (homeWithheld && c.spelled.startsWith("~"))) && c.expands !== "unresolvable" ? { ...c, expands: "unresolvable" } : c);
198
198
  const move = facts.baseMove;
199
199
  return {
@@ -268,9 +268,8 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
268
268
  const basename = program?.basename;
269
269
  const listed = basename !== undefined && (decl.readers.has(basename) || basename === "cd");
270
270
  const verb = program === undefined ? undefined : { name: program.name, basename: program.basename, listed };
271
- const envOpens = basename !== undefined && !NO_PATH_OPERAND_COMMANDS.has(basename);
272
271
  for (const k of pos.assignments)
273
- pushAssignment(reads, toks[k].word, "env_assignment", envOpens);
272
+ pushAssignment(reads, toks[k].word, "env_assignment");
274
273
  for (const l of pos.launchers) {
275
274
  const r = toks[l.index];
276
275
  if (isPathShapedToken(r.folded))
@@ -313,7 +312,7 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
313
312
  continue;
314
313
  }
315
314
  if (r.word.assignment !== undefined) {
316
- pushAssignment(reads, r.word, "env_assignment", envOpens);
315
+ pushAssignment(reads, r.word, "env_assignment");
317
316
  continue;
318
317
  }
319
318
  reads.push(bare(r, "unlisted_word"));
@@ -462,7 +461,7 @@ function dedupe(reads) {
462
461
  const seen = new Set();
463
462
  const out = [];
464
463
  for (const c of reads) {
465
- const key = `${c.provenance}\u0000${c.spelled}\u0000${c.expands}\u0000${c.denyOnly === true}`;
464
+ const key = `${c.provenance}\u0000${c.spelled}\u0000${c.expands}\u0000${c.raw}\u0000${c.denyOnly === true}`;
466
465
  if (seen.has(key))
467
466
  continue;
468
467
  seen.add(key);
@@ -488,7 +487,7 @@ function redirectFact(r, home) {
488
487
  return { op: r.op, direction: "in" };
489
488
  return { op: r.op, direction: "out", target: bare(readsOf([r.target], home)[0], "write_redirect_target") };
490
489
  }
491
- function pushAssignment(reads, w, provenance, opensValue = true) {
490
+ function pushAssignment(reads, w, provenance) {
492
491
  const a = w.assignment;
493
492
  if (a === undefined)
494
493
  return;
@@ -498,13 +497,19 @@ function pushAssignment(reads, w, provenance, opensValue = true) {
498
497
  const named = a.value.folded === "" || (a.op === "+=" ? expandsTilde : !settled) ? undefined : value;
499
498
  if (a.value.folded === "" && settled)
500
499
  return;
501
- const v = { spelled: a.value.folded, raw: a.bare ? a.value.raw : w.raw, provenance };
500
+ const v = { spelled: value, raw: a.bare ? a.value.raw : w.raw, provenance };
502
501
  const unsettled = provenance !== "env_assignment" ? { ...v, expands: "unresolvable" } : v.spelled.startsWith("~") && !v.spelled.includes(":") ? { ...v, expands: expandsOf(a.value) } : undefined;
503
- if (!settled && opensValue && unsettled !== undefined)
502
+ if (!settled && unsettled !== undefined)
504
503
  reads.push(unsettled);
505
504
  const substitutes = a.value.expansions.some((p) => p.expansion?.kind === "command" || p.expansion?.kind === "process" || p.expansion?.kind === "arithmetic");
506
505
  if (named !== undefined && !substitutes)
507
506
  reads.push({ spelled: named, raw: w.raw, provenance, expands: expandsOf(w) });
507
+ if (a.value.folded.includes(":"))
508
+ for (const m of a.value.folded.split(":"))
509
+ if (m !== "") {
510
+ const r = readOfText(m);
511
+ reads.push({ spelled: r.folded, raw: r.raw, provenance, expands: r.expands, denyOnly: true });
512
+ }
508
513
  if (a.form === "compound")
509
514
  for (const e of compoundArrayElements(a.value))
510
515
  reads.push({ spelled: e.folded, raw: e.raw, provenance, expands: e.expands, ...(e.denyOnly === true ? { denyOnly: true } : {}) });
@@ -527,7 +532,7 @@ function unlistedWords(reads, toks, from, launcherIndices, provenance, program)
527
532
  expanding.add(j);
528
533
  reads.push(bare(toks[j], j === argvStart ? "program_path" : provenance));
529
534
  if (j < argvStart)
530
- pushAssignment(reads, toks[j].word, "env_assignment", slotVerb !== undefined && !noPathOperands);
535
+ pushAssignment(reads, toks[j].word, "env_assignment");
531
536
  else if (j > argvStart && !declaredNotPaths)
532
537
  pushAssignment(reads, toks[j].word, provenance);
533
538
  }
@@ -605,7 +610,7 @@ function unlistedWords(reads, toks, from, launcherIndices, provenance, program)
605
610
  }
606
611
  reads.push(bare(r, j === argvStart ? "program_path" : provenance));
607
612
  if (j < argvStart)
608
- pushAssignment(reads, r.word, "env_assignment", slotVerb !== undefined && !noPathOperands);
613
+ pushAssignment(reads, r.word, "env_assignment");
609
614
  else if (!declaredNotPaths)
610
615
  pushAssignment(reads, r.word, provenance);
611
616
  }
@@ -25,10 +25,11 @@ export declare function readsOf(words: readonly LexedWord[], home: string | unde
25
25
  export declare const rewritten: (r: Read) => boolean;
26
26
  /** The reading of a text that came out of a word (a sed script's file operand): one shell word, or unresolvable. */
27
27
  export declare function readOfText(text: string): Read;
28
- /** A home-rooted candidate spelled with `home`: a leading `~` / `~/` (a literal tilde is spelled `./~` before this
29
- * point, so a leading `~` IS the expansion), or a `$HOME` / `${HOME}` word as {@link homeExpansionOf} reads it —
30
- * read off the VALUE's pieces when the word is assignment-shaped (`A="$HOME/x"`, `dd if="$HOME/x"`: the candidate is
31
- * the value, quotes kept); `undefined` for any other (`~user`, a payload, a word that is no home word). */
28
+ /** A home-rooted candidate spelled with `home`: a leading `~` / `~/` — the shell's expansion, or the literal-tilde
29
+ * spelling `./~` of one it leaves alone (quoted, escaped, after an option's `=`), which the program it reaches may
30
+ * expand itself — or a `$HOME` / `${HOME}` word as {@link homeExpansionOf} reads it, read off the VALUE's pieces when
31
+ * the word is assignment-shaped (`A="$HOME/x"`, `dd if="$HOME/x"`: the candidate is the value, quotes kept);
32
+ * `undefined` for any other (`~user`, a word that is no home word). */
32
33
  export declare function spelledWithHome(spelled: string, raw: string, home: string): string | undefined;
33
34
  /** A function definition's head names nothing that runs here and is dropped from a segment's tokens, in place:
34
35
  * `NAME ( )` (an identifier), and `function NAME [( )]` — a bare `function` at command position, a name of any
@@ -20,8 +20,9 @@ export function readOfText(text) {
20
20
  return { word, raw: text, folded: text, expands: expandsOf(word) };
21
21
  }
22
22
  export function spelledWithHome(spelled, raw, home) {
23
- if (spelled.startsWith("~"))
24
- return /^~(?:\/|$)/.test(spelled) ? `${home}${spelled.slice(1)}` : undefined;
23
+ const tilde = spelled.startsWith("./~") ? spelled.slice(2) : spelled;
24
+ if (tilde.startsWith("~"))
25
+ return /^~(?:\/|$)/.test(tilde) ? `${home}${tilde.slice(1)}` : undefined;
25
26
  if (!raw.includes("HOME"))
26
27
  return undefined;
27
28
  const w = readOfText(raw).word;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.30.1",
3
+ "version": "7.31.1",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_comment": "design/87 L3 — frozen public export surface of src/index.ts (name -> kind). DO NOT edit by hand to silence a red test. A removed/changed entry = a SemVer-BREAKING change; bump MAJOR and update this fixture in the SAME commit (design/87 §4.2 / §5.2). Regenerate via REGEN in test/export-surface.test.ts.",
3
3
  "_tierComment": "#435 v1 — machine-readable layering of the public surface: stable = demonstrated by README.md / src/examples; internal = an `Internal`-marked name or a runner/engine deep-subtree declaration (the model seam src/engine/llm is excluded — it is the BYOM contract, not an engine internal); advanced = a supported export the front door does not walk you through. THIS IS AN INITIAL HEURISTIC, derived mechanically and expected to be refined ticket by ticket: no human reviewed these 1700+ entries one by one, and nothing here claims otherwise. Known bias: a short or English-word export name (ok, err, Result, Usage) can match ordinary prose in README.md and land `stable` on a coincidence. Every export MUST carry a tier — a new export with no row fails the gate in export-surface.test.ts.",
4
- "count": 2475,
4
+ "count": 2478,
5
5
  "exports": {
6
6
  "A2ATaskState": "type",
7
7
  "A2ATaskStateReversal": "type",
@@ -688,6 +688,7 @@
688
688
  "MEMORY_INSTRUCTION_TEMPLATE": "variable",
689
689
  "MEMORY_ORIGIN_CAUSES": "variable",
690
690
  "MEMORY_PREFERENCE_DISCIPLINE": "variable",
691
+ "MEMORY_PROJECT_KEY_ABSENT_REASONS": "variable",
691
692
  "MEMORY_READONLY_NOTICE": "variable",
692
693
  "MEMORY_RECALL_DISCIPLINE": "variable",
693
694
  "MEMORY_SAFETY": "variable",
@@ -769,6 +770,8 @@
769
770
  "MemoryNoteRecord": "interface",
770
771
  "MemoryNoteType": "type",
771
772
  "MemoryOriginCause": "type",
773
+ "MemoryProjectKey": "type",
774
+ "MemoryProjectKeyAbsentReason": "type",
772
775
  "MemoryProjectionError": "class",
773
776
  "MemoryPromptArtifactStore": "class",
774
777
  "MemoryPromptSourceStateStore": "class",
@@ -3165,6 +3168,7 @@
3165
3168
  "MEMORY_INSTRUCTION_TEMPLATE": "advanced",
3166
3169
  "MEMORY_ORIGIN_CAUSES": "advanced",
3167
3170
  "MEMORY_PREFERENCE_DISCIPLINE": "advanced",
3171
+ "MEMORY_PROJECT_KEY_ABSENT_REASONS": "advanced",
3168
3172
  "MEMORY_READONLY_NOTICE": "advanced",
3169
3173
  "MEMORY_RECALL_DISCIPLINE": "advanced",
3170
3174
  "MEMORY_SAFETY": "advanced",
@@ -3246,6 +3250,8 @@
3246
3250
  "MemoryNoteRecord": "stable",
3247
3251
  "MemoryNoteType": "advanced",
3248
3252
  "MemoryOriginCause": "advanced",
3253
+ "MemoryProjectKey": "advanced",
3254
+ "MemoryProjectKeyAbsentReason": "advanced",
3249
3255
  "MemoryProjectionError": "advanced",
3250
3256
  "MemoryPromptArtifactStore": "advanced",
3251
3257
  "MemoryPromptSourceStateStore": "advanced",