@sema-agent/core 7.30.0 → 7.31.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.
- package/CHANGELOG.md +274 -0
- package/dist/agents/cross-session-judge.d.ts +4 -12
- package/dist/agents/cross-session-judge.js +4 -11
- package/dist/agents/peer-session-drain.js +0 -5
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +1 -0
- package/dist/core/ask-origin.d.ts +6 -0
- package/dist/core/ask-origin.js +10 -0
- package/dist/core/checkpoint-store.d.ts +14 -0
- package/dist/core/checkpoint-store.js +1 -0
- package/dist/core/engine-notice.d.ts +4 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/memory-engine/data-plane.d.ts +3 -1
- package/dist/core/memory-engine/data-plane.js +1 -1
- package/dist/core/memory-engine/delegation-settlement.d.ts +44 -3
- package/dist/core/memory-engine/delegation-settlement.js +38 -3
- package/dist/core/memory-engine/engine.d.ts +123 -26
- package/dist/core/memory-engine/engine.js +426 -149
- package/dist/core/memory-engine/entry-scan.d.ts +23 -0
- package/dist/core/memory-engine/entry-scan.js +55 -0
- package/dist/core/memory-engine/file-backend.d.ts +16 -23
- package/dist/core/memory-engine/file-backend.js +122 -99
- package/dist/core/memory-engine/layer-observations.d.ts +109 -0
- package/dist/core/memory-engine/layer-observations.js +148 -0
- package/dist/core/memory-engine/layout.d.ts +11 -0
- package/dist/core/memory-engine/layout.js +20 -4
- package/dist/core/memory-engine/types.d.ts +26 -2
- package/dist/core/parent-spec-seat.d.ts +4 -1
- package/dist/core/parent-spec-seat.js +2 -0
- package/dist/core/park-selfcheck.js +1 -0
- package/dist/core/runner/announce-once-ledger.d.ts +26 -0
- package/dist/core/runner/announce-once-ledger.js +37 -0
- package/dist/core/runner/contracts.d.ts +22 -5
- package/dist/core/runner/denial-limit-arms.d.ts +2 -2
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +0 -4
- package/dist/core/runner/prepare-acquire-reconcile.js +1 -3
- package/dist/core/runner/prepare-file-history.d.ts +13 -15
- package/dist/core/runner/prepare-file-history.js +57 -43
- package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
- package/dist/core/runner/prepare-listings.d.ts +3 -1
- package/dist/core/runner/prepare-listings.js +2 -2
- package/dist/core/runner/prepare-memory.js +16 -5
- package/dist/core/runner/prepare-park-ask.js +27 -16
- package/dist/core/runner/prepare-prompt-inputs.d.ts +8 -1
- package/dist/core/runner/prepare-prompt-inputs.js +5 -3
- package/dist/core/runner/prepare-task.js +7 -5
- package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -0
- package/dist/core/runner/prepare-wiring-manifest.js +2 -1
- package/dist/core/runner/run-attachment-seats.js +1 -1
- package/dist/core/runner/run-leg.js +61 -52
- package/dist/core/runner/run-settle-and-teardown.js +4 -0
- package/dist/core/runner/run-turn-boundary.js +6 -6
- package/dist/core/runner/turn-attachments.d.ts +6 -6
- package/dist/core/runner-deps.d.ts +3 -1
- package/dist/core/secret-env.d.ts +11 -0
- package/dist/core/secret-env.js +22 -1
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +22 -0
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +17 -8
- package/dist/core/tool-spec.d.ts +1 -1
- package/dist/core/wiring-manifest.d.ts +10 -0
- package/dist/core/wiring-manifest.js +7 -1
- package/dist/engine/execution-env/node-execution-env.d.ts +6 -0
- package/dist/engine/execution-env/node-execution-env.js +15 -17
- package/dist/engine/harness/agent-harness.js +7 -5
- package/dist/engine/lsp/node-lsp-manager.d.ts +7 -0
- package/dist/engine/lsp/node-lsp-manager.js +11 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shell/lex.d.ts +8 -3
- package/dist/shell/lex.js +2 -2
- package/dist/tools/fs/bash-readonly-classifier.d.ts +7 -1
- package/dist/tools/fs/bash-readonly-classifier.js +7 -3
- package/dist/tools/fs/shell-read-establish.d.ts +1 -1
- package/dist/tools/fs/shell-read-establish.js +6 -34
- package/dist/tools/fs/shell-read-identity.d.ts +13 -0
- package/dist/tools/fs/shell-read-identity.js +39 -0
- package/dist/tools/fs/shell-read-judge.d.ts +8 -0
- package/dist/tools/fs/shell-read-judge.js +7 -0
- package/dist/tools/fs/shell-read-plan.d.ts +5 -1
- package/dist/tools/fs/shell-read-plan.js +13 -21
- package/dist/tools/fs/shell-read-word.d.ts +16 -2
- package/dist/tools/fs/shell-read-word.js +40 -5
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +7 -1
- package/wire-changes/core@7.30.1.json +154 -0
- package/wire-changes/core@7.31.0.json +705 -0
|
@@ -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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
|
@@ -19,6 +20,12 @@ export interface NodeLspManagerOptions {
|
|
|
19
20
|
servers?: Record<string, string | null>;
|
|
20
21
|
/** Injectable spawn (tests supply a fake server; default = {@link defaultLspSpawn} over `child_process.spawn`). */
|
|
21
22
|
spawn?: LspSpawn;
|
|
23
|
+
/** The language server's environment for the default spawn, verbatim. Omitted ⇒ the host environment scrubbed of
|
|
24
|
+
* credential-shaped names (`scrubSecretEnv`, a tool child's scrub). Unread when {@link spawn} is supplied. */
|
|
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;
|
|
22
29
|
/** Read a file's CURRENT text for the session's didOpen/didChange re-sync (default = local `fs.readFile` utf8). */
|
|
23
30
|
readText?: LspReadText;
|
|
24
31
|
/** Resolve the server `cwd`/`rootUri` for a file (default = env.cwd ?? nearest project marker ?? file dir). */
|
|
@@ -9,6 +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 { announceSecretEnvScrub, scrubSecretEnv } from "../../core/secret-env.js";
|
|
12
13
|
export const DEFAULT_LSP_SERVERS = {
|
|
13
14
|
typescript: "typescript-language-server --stdio",
|
|
14
15
|
javascript: "typescript-language-server --stdio",
|
|
@@ -66,7 +67,7 @@ export class NodeLspManager {
|
|
|
66
67
|
disposed = false;
|
|
67
68
|
constructor(opts = {}) {
|
|
68
69
|
this.servers = { ...DEFAULT_LSP_SERVERS, ...opts.servers };
|
|
69
|
-
this.spawnFn = opts.spawn ??
|
|
70
|
+
this.spawnFn = opts.spawn ?? lspSpawnWithEnv(opts.env, opts.onNotice);
|
|
70
71
|
this.readTextFn = opts.readText ?? defaultLspReadText;
|
|
71
72
|
this.resolveRootFn = opts.resolveRoot ?? defaultResolveRoot;
|
|
72
73
|
this.log = opts.log ?? (() => { });
|
|
@@ -328,10 +329,17 @@ export function lspSpawnOptions(cwd) {
|
|
|
328
329
|
? { cwd, stdio: ["pipe", "pipe", "ignore"], shell: true }
|
|
329
330
|
: { cwd, stdio: ["pipe", "pipe", "ignore"] };
|
|
330
331
|
}
|
|
331
|
-
export const defaultLspSpawn = (command, args, cwd, signal) =>
|
|
332
|
+
export const defaultLspSpawn = (command, args, cwd, signal) => lspSpawnWithEnv(undefined)(command, args, cwd, signal);
|
|
333
|
+
const lspSpawnWithEnv = (env, onNotice) => (command, args, cwd, signal) => new Promise((resolve, reject) => {
|
|
332
334
|
let child;
|
|
333
335
|
try {
|
|
334
|
-
|
|
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 });
|
|
335
343
|
}
|
|
336
344
|
catch (e) {
|
|
337
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";
|
package/dist/shell/lex.d.ts
CHANGED
|
@@ -25,8 +25,10 @@ export declare const SHELL_LEX_DIALECT = "bash";
|
|
|
25
25
|
/** What holds a character quoted: the four quote forms and the backslash escape, one reason each. */
|
|
26
26
|
export declare const QUOTE_KINDS: readonly ["'", "\"", "$'", "$\"", "\\"];
|
|
27
27
|
export type QuoteKind = (typeof QUOTE_KINDS)[number];
|
|
28
|
-
/** The expansions this lexer names. Extended globs are
|
|
29
|
-
* `?(…)` is a syntax error
|
|
28
|
+
/** The expansions this lexer names. Extended globs are not among them: `extglob` is off when `bash -c` starts, where
|
|
29
|
+
* `?(…)` is a syntax error and `(` is a group token here — but a text may turn it on (`shopt -s extglob` on an earlier
|
|
30
|
+
* line), after which bash reads `pack@(age.json)` as one pattern. A reader that plans reads treats an unquoted opener
|
|
31
|
+
* as a pattern it does not expand (`extglobOpenerIn`, the read station's `unreadable` floor). */
|
|
30
32
|
export declare const EXPANSION_KINDS: readonly ["parameter", "command", "process", "arithmetic", "tilde", "brace", "glob"];
|
|
31
33
|
export type ExpansionKind = (typeof EXPANSION_KINDS)[number];
|
|
32
34
|
/** Why a word's value cannot be read off the text. Each member is a spelling whose value the text does not
|
|
@@ -242,7 +244,10 @@ export declare function argvOf(segment: LexedSegment): readonly LexedWord[];
|
|
|
242
244
|
export declare function wordsOf(segment: LexedSegment): SegmentTokens;
|
|
243
245
|
/** The substitution bodies of an UNQUOTED here-document body: `$(…)` and backticks are live there, quotes are
|
|
244
246
|
* ordinary characters, a backslash protects the next character. `undefined` when a substitution never closes. */
|
|
245
|
-
export declare function substitutionsInHeredocBody(body: string):
|
|
247
|
+
export declare function substitutionsInHeredocBody(body: string): {
|
|
248
|
+
readonly body: string;
|
|
249
|
+
readonly kind: "command" | "arithmetic";
|
|
250
|
+
}[] | undefined;
|
|
246
251
|
/** Lex a command: its segments, or the one reason bash (or this lexer) cannot delimit them. */
|
|
247
252
|
export declare function lexShell(source: string): ShellLex;
|
|
248
253
|
/**
|
package/dist/shell/lex.js
CHANGED
|
@@ -305,7 +305,7 @@ export function substitutionsInHeredocBody(body) {
|
|
|
305
305
|
const tick = backtickBody(body, i);
|
|
306
306
|
if (tick === undefined)
|
|
307
307
|
return undefined;
|
|
308
|
-
out.push(tick.body);
|
|
308
|
+
out.push({ body: tick.body, kind: "command" });
|
|
309
309
|
i = tick.close;
|
|
310
310
|
continue;
|
|
311
311
|
}
|
|
@@ -313,7 +313,7 @@ export function substitutionsInHeredocBody(body) {
|
|
|
313
313
|
const end = parenBodyEnd(body, i + 1);
|
|
314
314
|
if (typeof end !== "number")
|
|
315
315
|
return undefined;
|
|
316
|
-
out.push(body.slice(i + 2, end - 1));
|
|
316
|
+
out.push({ body: body.slice(i + 2, end - 1), kind: body[i + 2] === "(" ? "arithmetic" : "command" });
|
|
317
317
|
i = end - 1;
|
|
318
318
|
}
|
|
319
319
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type LexedSegment, type LexedWord, type ShellLex, type ShellSegmentConnector, type WordPiece } from "../../shell/lex.js";
|
|
1
|
+
import { type LexedCommand, type LexedSegment, type LexedWord, type ShellLex, type ShellSegmentConnector, type WordPiece } from "../../shell/lex.js";
|
|
2
2
|
/**
|
|
3
3
|
* Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the
|
|
4
4
|
* The {@link import("./fs-bash.js").bashReversibilityProbe} classifier. **Coarse first filter, NOT a security
|
|
@@ -876,6 +876,12 @@ export declare function homeExpansionOf(w: LexedWord, home: string): {
|
|
|
876
876
|
* Elsewhere a computed word is its own unresolvable candidate, and an escape the lexer does not decode is
|
|
877
877
|
* unresolved on its own (asked). */
|
|
878
878
|
export declare function homeMayBeRebound(text: string): boolean;
|
|
879
|
+
/** {@link hasUnquotedExtglobOpener} over an already-lexed text. A here-document delimiter counts too: the lexer ends
|
|
880
|
+
* it at the `(` (`<<@(x)` reads as the delimiter `@` and a group), where bash with `extglob` on reads the one word
|
|
881
|
+
* `@(x)` — a different delimiter, so the body the lexer read as data is not the body bash reads. The token keeps no
|
|
882
|
+
* per-character quoting, so a delimiter quoted anywhere (`<<'E'@(X)`) counts as well: a quoted opener is bash's
|
|
883
|
+
* syntax error, where reading it as one costs a card and runs nothing. */
|
|
884
|
+
export declare function extglobOpenerIn(lexed: LexedCommand, text: string): boolean;
|
|
879
885
|
/**
|
|
880
886
|
* {@link classifyCompoundReadonly} with the structured verdict. The name/operator/blocking arms
|
|
881
887
|
* run FIRST and unchanged, so an existing demotion keeps its exact reason and never acquires the
|
|
@@ -1510,10 +1510,14 @@ export function homeMayBeRebound(text) {
|
|
|
1510
1510
|
}
|
|
1511
1511
|
function hasUnquotedExtglobOpener(text) {
|
|
1512
1512
|
const lexed = lexShell(text);
|
|
1513
|
-
|
|
1514
|
-
|
|
1513
|
+
return "parseFailure" in lexed || extglobOpenerIn(lexed, text);
|
|
1514
|
+
}
|
|
1515
|
+
export function extglobOpenerIn(lexed, text) {
|
|
1515
1516
|
for (const seg of lexed.segments) {
|
|
1516
|
-
for (const tok of seg.tokens) {
|
|
1517
|
+
for (const [k, tok] of seg.tokens.entries()) {
|
|
1518
|
+
const next = seg.tokens[k + 1];
|
|
1519
|
+
if (tok.kind === "heredoc" && /[?*+@!]$/.test(tok.delimiter) && next?.kind === "group" && next.op === "(")
|
|
1520
|
+
return true;
|
|
1517
1521
|
const t = tok.kind === "word" ? tok : tok.kind === "redirect" ? tok.target : undefined;
|
|
1518
1522
|
if (t === undefined)
|
|
1519
1523
|
continue;
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
37
37
|
import type { ShellReadPlan, ShellReadSegment } from "./shell-read-plan.js";
|
|
38
|
-
import type
|
|
38
|
+
import { type EstablishedRead, type ShellReadBoundary } from "./shell-read-judge.js";
|
|
39
39
|
/**
|
|
40
40
|
* How many directories one glob operand (or one traversal root) may enumerate, and how many names it may
|
|
41
41
|
* match, before the leg gives up and answers `unestablishable`.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { normalizeAbsPathLexically, pathFamilyOf } from "./safety.js";
|
|
2
2
|
import { boundaryIsTheSandbox, canonicalPathIsAuthoritative } from "../../core/remote-env.js";
|
|
3
3
|
import { resolveOperandSpellings } from "./bash-readonly-classifier.js";
|
|
4
|
+
import { denyFloorShadowsOf } from "./shell-read-judge.js";
|
|
5
|
+
import { movedReads } from "./shell-read-identity.js";
|
|
4
6
|
const GLOB_METACHAR_RE = /[*?[]/;
|
|
5
7
|
export const MAX_GLOB_DIRS = 256;
|
|
6
8
|
export const MAX_GLOB_MATCHES = 8192;
|
|
@@ -94,7 +96,7 @@ function basesFor(candidate, boundary) {
|
|
|
94
96
|
}
|
|
95
97
|
function lexicalReadsOf(candidate, boundary) {
|
|
96
98
|
if (candidate.expands === "unresolvable") {
|
|
97
|
-
return [{ candidate, established: "unestablishable", spellings: [candidate.spelled
|
|
99
|
+
return [{ candidate, established: "unestablishable", spellings: [candidate.spelled], cause: `"${candidate.raw}" is rewritten by the shell before the program sees it` }];
|
|
98
100
|
}
|
|
99
101
|
const bases = basesFor(candidate, boundary);
|
|
100
102
|
if (bases === "unmodelled") {
|
|
@@ -130,7 +132,8 @@ function lexicalReadsOf(candidate, boundary) {
|
|
|
130
132
|
return out;
|
|
131
133
|
}
|
|
132
134
|
function judgedCandidatesOf(segment) {
|
|
133
|
-
|
|
135
|
+
const reads = segment.reads.filter((c) => c.provenance !== "write_redirect_target");
|
|
136
|
+
return [...reads, ...denyFloorShadowsOf(reads)];
|
|
134
137
|
}
|
|
135
138
|
export function establishShellReadsLexically(plan, boundary) {
|
|
136
139
|
const reads = new Map();
|
|
@@ -424,38 +427,7 @@ export function createShellReadEstablisher(env, readRoots) {
|
|
|
424
427
|
if (lexicalOnly)
|
|
425
428
|
return { changed: [] };
|
|
426
429
|
const again = await establishPlan(previous.plan, { ...boundary, roots: previous.roots }, previous.roots, signal);
|
|
427
|
-
|
|
428
|
-
const out = [];
|
|
429
|
-
const walk = (p) => {
|
|
430
|
-
for (const reads of p.reads.values())
|
|
431
|
-
for (const r of reads)
|
|
432
|
-
if ((r.established === "canonical" || r.established === "expansion") && r.target !== undefined)
|
|
433
|
-
out.push(r.target);
|
|
434
|
-
for (const n of p.nested.values())
|
|
435
|
-
walk(n);
|
|
436
|
-
};
|
|
437
|
-
walk(e);
|
|
438
|
-
return out.sort();
|
|
439
|
-
};
|
|
440
|
-
const before = settled(previous);
|
|
441
|
-
const after = new Set(settled(again));
|
|
442
|
-
const beforeSet = new Set(before);
|
|
443
|
-
const moved = before.filter((t) => !after.has(t));
|
|
444
|
-
const appeared = [...after].filter((t) => !beforeSet.has(t));
|
|
445
|
-
if (moved.length === 0 && appeared.length === 0)
|
|
446
|
-
return { changed: [] };
|
|
447
|
-
const changed = [];
|
|
448
|
-
const collect = (p) => {
|
|
449
|
-
for (const reads of p.reads.values())
|
|
450
|
-
for (const r of reads)
|
|
451
|
-
if (r.target !== undefined && (moved.includes(r.target) || appeared.includes(r.target)))
|
|
452
|
-
changed.push(r);
|
|
453
|
-
for (const n of p.nested.values())
|
|
454
|
-
collect(n);
|
|
455
|
-
};
|
|
456
|
-
collect(previous);
|
|
457
|
-
collect(again);
|
|
458
|
-
return { changed };
|
|
430
|
+
return { changed: movedReads(previous, again) };
|
|
459
431
|
},
|
|
460
432
|
};
|
|
461
433
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pre-spawn identity comparison: which established reads of one plan resolve somewhere else now.
|
|
3
|
+
*
|
|
4
|
+
* Compared PER CANDIDATE, never as one pooled set of targets: a candidate is keyed by where it stands in the plan
|
|
5
|
+
* (segment, nested plan) and by what it is (deny-only or a read, provenance, spelling, expansion, raw word), and its
|
|
6
|
+
* set of established targets is compared with the same candidate's set from the re-establishment. A pooled set let
|
|
7
|
+
* one candidate's targets hide another's move — `head linkx` retargeted onto a path some other word's pattern already
|
|
8
|
+
* matched, or onto a home shadow's target, left the pool unchanged and the call was admitted unjudged.
|
|
9
|
+
*/
|
|
10
|
+
import type { EstablishedPlan } from "./shell-read-establish.js";
|
|
11
|
+
import type { EstablishedRead } from "./shell-read-judge.js";
|
|
12
|
+
/** Every read of either establishment whose candidate's settled target set differs between the two. */
|
|
13
|
+
export declare function movedReads(previous: EstablishedPlan, again: EstablishedPlan): EstablishedRead[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function movedReads(previous, again) {
|
|
2
|
+
const before = targetsByCandidate(previous);
|
|
3
|
+
const after = targetsByCandidate(again);
|
|
4
|
+
const changed = [];
|
|
5
|
+
for (const key of new Set([...before.keys(), ...after.keys()])) {
|
|
6
|
+
const b = before.get(key);
|
|
7
|
+
const a = after.get(key);
|
|
8
|
+
if (b !== undefined && a !== undefined && sameTargets(b.targets, a.targets))
|
|
9
|
+
continue;
|
|
10
|
+
changed.push(...(b?.reads ?? []), ...(a?.reads ?? []));
|
|
11
|
+
}
|
|
12
|
+
return changed.filter((r) => r.target !== undefined);
|
|
13
|
+
}
|
|
14
|
+
function targetsByCandidate(e) {
|
|
15
|
+
const out = new Map();
|
|
16
|
+
const walk = (p, at) => {
|
|
17
|
+
p.plan.segments.forEach((segment, si) => {
|
|
18
|
+
for (const r of p.reads.get(segment) ?? []) {
|
|
19
|
+
const c = r.candidate;
|
|
20
|
+
const key = [at, si, c.denyOnly === true ? "floor" : "read", c.provenance, c.spelled, String(c.expands), c.raw].join("\0");
|
|
21
|
+
const landing = out.get(key) ?? { targets: new Set(), reads: [] };
|
|
22
|
+
out.set(key, landing);
|
|
23
|
+
landing.reads.push(r);
|
|
24
|
+
if ((r.established === "canonical" || r.established === "expansion") && r.target !== undefined)
|
|
25
|
+
landing.targets.add(r.target);
|
|
26
|
+
}
|
|
27
|
+
segment.nested.forEach((n, ni) => {
|
|
28
|
+
const nested = p.nested.get(n);
|
|
29
|
+
if (nested !== undefined)
|
|
30
|
+
walk(nested, `${at}/${si}.${ni}`);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
walk(e, "");
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
function sameTargets(a, b) {
|
|
38
|
+
return a.size === b.size && [...a].every((t) => b.has(t));
|
|
39
|
+
}
|
|
@@ -173,6 +173,14 @@ export interface ReadFinding {
|
|
|
173
173
|
* neither is a containment candidate — both still meet the deny judge, on every spelling.
|
|
174
174
|
*/
|
|
175
175
|
export declare function judgeRead(read: EstablishedRead, boundary: ShellReadBoundary): ReadFinding;
|
|
176
|
+
/**
|
|
177
|
+
* The deny floor of every home word the plan does not place: each candidate carrying a `floorSpelled` (the word
|
|
178
|
+
* spelled with the DECLARED home) gets a SHADOW candidate at that spelling, established exactly as a read is — the
|
|
179
|
+
* canonical target, a pattern's matches (a glob the word carries unquoted), a traversal root's tree — and judged for
|
|
180
|
+
* deny rows only (`denyOnly`). A deny row is a floor the text's rebinding may never reach past; the shadow adds no
|
|
181
|
+
* other finding, so a read that is not a deny row still asks through its own unplaced candidate.
|
|
182
|
+
*/
|
|
183
|
+
export declare function denyFloorShadowsOf(candidates: readonly ReadCandidate[]): ReadCandidate[];
|
|
176
184
|
/** One segment's judged reads and declines, with its nested plans judged the same way. */
|
|
177
185
|
export interface JudgedSegment {
|
|
178
186
|
readonly segment: ShellReadSegment;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { hasWin32AliasedComponent, normalizeAbsPathLexically, withinAnyRoot } from "./safety.js";
|
|
2
|
+
import { globInWord } from "./bash-readonly-classifier.js";
|
|
3
|
+
import { readOfText } from "./shell-read-word.js";
|
|
2
4
|
export const SHELL_READ_FINDINGS = ["deny_hit", "outside_roots", "unestablishable", "declined", "identity_changed", "none"];
|
|
3
5
|
export const SHELL_READ_SEATS = ["bash_off", "bash_classify", "readonly", "rejudge"];
|
|
4
6
|
export const SHELL_READ_FINDING_RANK = {
|
|
@@ -32,6 +34,8 @@ export function judgeRead(read, boundary) {
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
const containment = containmentOf(read, boundary);
|
|
37
|
+
if (read.candidate.denyOnly === true)
|
|
38
|
+
return { read, kind: "none", containment };
|
|
35
39
|
const provenance = read.candidate.provenance;
|
|
36
40
|
if (provenance === "env_assignment" || (provenance === "program_path" && read.candidate.expands !== "unresolvable"))
|
|
37
41
|
return { read, kind: "none", containment };
|
|
@@ -39,6 +43,9 @@ export function judgeRead(read, boundary) {
|
|
|
39
43
|
return { read, kind: "unestablishable", containment };
|
|
40
44
|
return { read, kind: containment === "outside" ? "outside_roots" : "none", containment };
|
|
41
45
|
}
|
|
46
|
+
export function denyFloorShadowsOf(candidates) {
|
|
47
|
+
return candidates.flatMap((c) => (c.floorSpelled === undefined ? [] : [{ spelled: c.floorSpelled, raw: c.raw, provenance: c.provenance, expands: globInWord(readOfText(c.raw).word) ? "glob" : false, bases: [], denyOnly: true }]));
|
|
48
|
+
}
|
|
42
49
|
function containmentOf(read, boundary) {
|
|
43
50
|
if (boundary.face === "open")
|
|
44
51
|
return "open";
|
|
@@ -65,6 +65,8 @@ export interface ReadCandidate {
|
|
|
65
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
66
|
* home. A deny row is a floor: the judge reads it for deny rows ONLY, never to establish a read. */
|
|
67
67
|
readonly floorSpelled?: string;
|
|
68
|
+
/** A deny-floor shadow of a `floorSpelled` spelling, established as a read and judged for deny rows only. */
|
|
69
|
+
readonly denyOnly?: true;
|
|
68
70
|
}
|
|
69
71
|
/** How the shell reached one segment — what the base-candidate walk needs from the connectors. */
|
|
70
72
|
export interface SegmentReach {
|
|
@@ -123,4 +125,6 @@ export interface ShellReadPlan {
|
|
|
123
125
|
readonly strayRedirection: boolean;
|
|
124
126
|
};
|
|
125
127
|
}
|
|
126
|
-
|
|
128
|
+
/** Plan every read candidate of one command (the contract is this module's header). `arithmetic` = the text is an
|
|
129
|
+
* `$((…))` body, which bash evaluates and never globs (no extended-glob floor). */
|
|
130
|
+
export declare function planShellReads(command: string, decl: ShellReadDeclarations, depth?: number, homeRebound?: boolean, arithmetic?: boolean): ShellReadPlan;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { lexShell, PARSE_FAILURE_SENTENCE,
|
|
2
|
-
import { compoundArrayElements, dropDefinitionHeads, expandsOf, readOfText, readsOf, rewritten, spelledWithHome } from "./shell-read-word.js";
|
|
1
|
+
import { lexShell, PARSE_FAILURE_SENTENCE, substitutionsInHeredocBody, literalTildeSpelling, } from "../../shell/lex.js";
|
|
2
|
+
import { compoundArrayElements, dropDefinitionHeads, expandsOf, readOfText, readsOf, rewritten, spelledWithHome, isArithmeticBody, unreadableSentenceOf } from "./shell-read-word.js";
|
|
3
3
|
import { LAUNCHER_TABLE, SHELL_BLOCK_WORDS, programBasename, programPositionRunsShell, readProgramPosition, shellScanDeclineOf, } from "./bash-program-position.js";
|
|
4
4
|
import { ASSIGNMENT_VALUE_NOT_PATHS, INTERPRETER_INLINE_PROGRAMS, NO_PATH_OPERAND_COMMANDS, POLL_LOOP_MAX_BEATS, RECURSIVE_READ_FORMS, SHELL_REENTRY_PROGRAM_RE, UNMODELLED_BASE_MOVERS, argvProgramOf, homeMayBeRebound, attachedOptionPayloads, resolveOperandLexically, inlineProgramClause, interpreterKey, isPathShapedToken, readVerbOptionSlot, sedScriptSlotFiles, segmentSelectsRecursiveRead, slotValueCandidates, suppliesFirstOperandSlot, verbFirstOperandSlot, verbOptionsArePositionIndependent, } from "./bash-readonly-classifier.js";
|
|
5
5
|
import { isAbsolutePathForm } from "./safety.js";
|
|
@@ -18,17 +18,7 @@ export const READ_CANDIDATE_PROVENANCES = [
|
|
|
18
18
|
];
|
|
19
19
|
const NESTING_MAX = 4;
|
|
20
20
|
const BASE_CANDIDATE_MAX = 256;
|
|
21
|
-
function
|
|
22
|
-
for (const segment of lexed.segments) {
|
|
23
|
-
for (const token of segment.tokens) {
|
|
24
|
-
const word = token.kind === "word" ? token : token.kind === "redirect" ? token.target : undefined;
|
|
25
|
-
if (word?.unresolved !== undefined)
|
|
26
|
-
return UNRESOLVED_SENTENCE[word.unresolved];
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
31
|
-
export function planShellReads(command, decl, depth = 0, homeRebound = false) {
|
|
21
|
+
export function planShellReads(command, decl, depth = 0, homeRebound = false, arithmetic = false) {
|
|
32
22
|
const shape = { grouped: false, backgrounded: false, strayRedirection: false };
|
|
33
23
|
if (depth > NESTING_MAX)
|
|
34
24
|
return { segments: [], unreadable: `command text nested more than ${NESTING_MAX} levels deep`, shape };
|
|
@@ -36,8 +26,8 @@ export function planShellReads(command, decl, depth = 0, homeRebound = false) {
|
|
|
36
26
|
if ("parseFailure" in lexed)
|
|
37
27
|
return { segments: [], unreadable: PARSE_FAILURE_SENTENCE[lexed.parseFailure.reason], shape };
|
|
38
28
|
const home = homeRebound || homeMayBeRebound(command) ? undefined : decl.homeDir;
|
|
39
|
-
const unreadable =
|
|
40
|
-
const nestedPlan = (text) => planShellReads(text, decl, depth + 1, home === undefined);
|
|
29
|
+
const unreadable = unreadableSentenceOf(lexed, command, !arithmetic);
|
|
30
|
+
const nestedPlan = (text, arithmetic = false) => planShellReads(text, decl, depth + 1, home === undefined, arithmetic);
|
|
41
31
|
const projected = lexed.segments.map((segment) => projectSegment(segment));
|
|
42
32
|
const facts = projected.map((p, si) => segmentFacts(lexed.segments[si], p, decl, home, nestedPlan));
|
|
43
33
|
shape.grouped = projected.some((p) => p.grouped);
|
|
@@ -86,7 +76,7 @@ export function planShellReads(command, decl, depth = 0, homeRebound = false) {
|
|
|
86
76
|
if (move === undefined || baseLost)
|
|
87
77
|
return;
|
|
88
78
|
if (typeof move.target === "string" || move.target.expands !== false) {
|
|
89
|
-
loseBase(reachable, typeof move.target
|
|
79
|
+
loseBase(reachable, typeof move.target !== "string" ? move.target.floorSpelled : move.target === "home" ? decl.homeDir : undefined);
|
|
90
80
|
return;
|
|
91
81
|
}
|
|
92
82
|
const target = move.target.spelled;
|
|
@@ -214,8 +204,10 @@ function segmentFacts(segment, p, decl, home, nestedPlan) {
|
|
|
214
204
|
}
|
|
215
205
|
function segmentFactsRead(segment, p, decl, home, nestedPlan) {
|
|
216
206
|
const nested = [];
|
|
207
|
+
const arithmetic = segment.tokens.flatMap((t) => (t.kind === "word" ? t : t.kind === "redirect" ? t.target : undefined)?.pieces ?? []).flatMap((q) => (q.expansion?.kind === "arithmetic" && q.expansion.body !== undefined && isArithmeticBody(q.expansion.body) ? [q.expansion.body] : []));
|
|
208
|
+
const count = (list, body) => list.filter((b) => b === body).length;
|
|
217
209
|
for (const body of segment.substitutions)
|
|
218
|
-
nested.push(nestedPlan(body));
|
|
210
|
+
nested.push(nestedPlan(body, count(arithmetic, body) === count(segment.substitutions, body)));
|
|
219
211
|
let unreadableHeredoc = false;
|
|
220
212
|
for (const t of segment.tokens) {
|
|
221
213
|
if (t.kind !== "heredoc" || t.quoted)
|
|
@@ -225,8 +217,8 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
|
|
|
225
217
|
unreadableHeredoc = true;
|
|
226
218
|
continue;
|
|
227
219
|
}
|
|
228
|
-
for (const
|
|
229
|
-
nested.push(nestedPlan(body));
|
|
220
|
+
for (const s of inner)
|
|
221
|
+
nested.push(nestedPlan(s.body, s.kind === "arithmetic" && isArithmeticBody(s.body)));
|
|
230
222
|
}
|
|
231
223
|
if (unreadableHeredoc)
|
|
232
224
|
nested.push({ segments: [], unreadable: "a here-document body carries a substitution this lexer cannot delimit", shape: { grouped: false, backgrounded: false, strayRedirection: false } });
|
|
@@ -470,7 +462,7 @@ function dedupe(reads) {
|
|
|
470
462
|
const seen = new Set();
|
|
471
463
|
const out = [];
|
|
472
464
|
for (const c of reads) {
|
|
473
|
-
const key = `${c.provenance}\u0000${c.spelled}\u0000${c.expands}`;
|
|
465
|
+
const key = `${c.provenance}\u0000${c.spelled}\u0000${c.expands}\u0000${c.denyOnly === true}`;
|
|
474
466
|
if (seen.has(key))
|
|
475
467
|
continue;
|
|
476
468
|
seen.add(key);
|
|
@@ -515,7 +507,7 @@ function pushAssignment(reads, w, provenance, opensValue = true) {
|
|
|
515
507
|
reads.push({ spelled: named, raw: w.raw, provenance, expands: expandsOf(w) });
|
|
516
508
|
if (a.form === "compound")
|
|
517
509
|
for (const e of compoundArrayElements(a.value))
|
|
518
|
-
reads.push({ spelled: e.folded, raw: e.raw, provenance, expands: e.expands });
|
|
510
|
+
reads.push({ spelled: e.folded, raw: e.raw, provenance, expands: e.expands, ...(e.denyOnly === true ? { denyOnly: true } : {}) });
|
|
519
511
|
}
|
|
520
512
|
function unlistedWords(reads, toks, from, launcherIndices, provenance, program) {
|
|
521
513
|
const argv = program !== undefined ? { program: program.basename, from: program.index } : argvProgramOf(toks.map((r) => r.word));
|