@sema-agent/core 7.20.0 → 7.21.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 +84 -0
- package/dist/core/ask-origin.d.ts +95 -8
- package/dist/core/ask-origin.js +37 -5
- package/dist/core/engine-notice.d.ts +9 -0
- package/dist/core/gate-bounded-await.d.ts +6 -1
- package/dist/core/gate-bounded-await.js +13 -3
- package/dist/core/gate-fold.js +18 -5
- package/dist/core/gate-lanes.js +6 -12
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/memory-engine/consolidation-driver.d.ts +3 -0
- package/dist/core/memory-engine/consolidation-driver.js +20 -1
- package/dist/core/memory-engine/consolidation.d.ts +7 -0
- package/dist/core/memory-engine/consolidation.js +4 -1
- package/dist/core/memory-engine/distiller.d.ts +26 -1
- package/dist/core/memory-engine/distiller.js +9 -2
- package/dist/core/memory-engine/tools.js +6 -0
- package/dist/core/path-identity.d.ts +13 -0
- package/dist/core/path-identity.js +14 -0
- package/dist/core/runner/env-capability-census.d.ts +3 -1
- package/dist/core/runner/env-capability-census.js +24 -7
- package/dist/core/runner/gate-exit.d.ts +7 -0
- package/dist/core/runner/inherited-ask-grants.d.ts +1 -0
- package/dist/core/runner/inherited-ask-grants.js +5 -2
- package/dist/core/runner/memory-consolidation-driver.d.ts +72 -2
- package/dist/core/runner/memory-consolidation-driver.js +57 -0
- package/dist/core/runner/prepare-config-doors.js +2 -0
- package/dist/core/runner/prepare-gate-stations.d.ts +6 -8
- package/dist/core/runner/prepare-gate-stations.js +1 -7
- package/dist/core/runner/prepare-policy-chain.d.ts +1 -0
- package/dist/core/runner/prepare-policy-chain.js +52 -20
- package/dist/core/runner/prepare-task.js +2 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -1
- package/dist/core/runner/prepare-wiring-manifest.js +7 -2
- package/dist/core/runner/run-settle-and-teardown.js +5 -0
- package/dist/core/runner/write-protection-station.d.ts +59 -0
- package/dist/core/runner/write-protection-station.js +32 -0
- package/dist/core/runner-deps.d.ts +21 -0
- package/dist/core/sensitive-path-policy.js +13 -0
- package/dist/core/wiring-manifest.d.ts +47 -0
- package/dist/core/wiring-manifest.js +2 -0
- package/dist/core/write-protect.d.ts +41 -3
- package/dist/core/write-protect.js +40 -14
- package/dist/engine/execution-env/node-execution-env.d.ts +10 -0
- package/dist/engine/execution-env/node-execution-env.js +29 -1
- package/dist/engine/harness/types.d.ts +10 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/tools/fs/bash-readonly-classifier.js +36 -7
- package/dist/tools/fs/read-deny.d.ts +4 -2
- package/dist/tools/fs/read-deny.js +5 -0
- package/dist/tools/fs/safety.d.ts +12 -0
- package/dist/tools/fs/safety.js +43 -38
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +9 -1
- package/wire-changes/core@7.20.1.json +183 -0
- package/wire-changes/core@7.21.0.json +452 -0
|
@@ -2,6 +2,7 @@ import { canonicalizeTarget, isWinFormPath, writeTargetPath, isUncPath, isAbsolu
|
|
|
2
2
|
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
3
3
|
import { pathTargetBaseOf } from "./effective-path-target.js";
|
|
4
4
|
import { canonicalDataRootKeys, dataRootSpellings, relativeUnderRoot } from "./sensitive-path-policy.js";
|
|
5
|
+
import { symlinkChainHops } from "./path-identity.js";
|
|
5
6
|
const freezeTable = (rows) => Object.freeze(rows.map((r) => Object.freeze(r)));
|
|
6
7
|
export const WRITE_PROTECTED_DEFAULT_TABLE = freezeTable([
|
|
7
8
|
{ name: ".gitconfig", kind: "basename" },
|
|
@@ -156,6 +157,7 @@ function relativeUnderDataRoot(path, roots) {
|
|
|
156
157
|
}
|
|
157
158
|
return null;
|
|
158
159
|
}
|
|
160
|
+
export const WRITE_PROTECTION_TARGET_VIEWS = ["spelling-only", "target"];
|
|
159
161
|
export function compileWriteProtection(entries, opts) {
|
|
160
162
|
const rows = resolveWriteProtectedTable(entries);
|
|
161
163
|
if (rows.length === 0)
|
|
@@ -206,16 +208,18 @@ export function compileWriteProtection(entries, opts) {
|
|
|
206
208
|
const judgeViews = (spelling) => judgeSegments(pathViewSegments(spelling, false, POSIX_SEPARATOR_FAMILY, uncAnchorSegments(spelling))) ??
|
|
207
209
|
judgeSegments(pathViewSegments(spelling, false)) ??
|
|
208
210
|
judgeSegments(pathViewSegments(spelling, true));
|
|
211
|
+
const judgeSpelled = (spelled) => judgeViews(relativeUnderDataRoot(spelled, rootViews) ?? spelled);
|
|
209
212
|
const judgeSpelling = (path, cwd) => {
|
|
210
213
|
const base = typeof cwd === "string" && cwd.length > 0 ? cwd : undefined;
|
|
211
214
|
const family = pathFamilyOf({ cwd: base });
|
|
212
215
|
const spelled = base !== undefined && family !== undefined && !isAbsoluteForFamily(family, path) ? joinForFamily(family, base, path) : path;
|
|
213
|
-
return
|
|
216
|
+
return judgeSpelled(spelled);
|
|
214
217
|
};
|
|
215
218
|
const env = opts?.env;
|
|
216
219
|
const rootPath = opts?.rootPath;
|
|
217
220
|
return {
|
|
218
221
|
rows,
|
|
222
|
+
targetView: env === undefined ? "spelling-only" : "target",
|
|
219
223
|
matchPath(path, cwd) {
|
|
220
224
|
return judgeSpelling(path, cwd);
|
|
221
225
|
},
|
|
@@ -225,22 +229,35 @@ export function compileWriteProtection(entries, opts) {
|
|
|
225
229
|
return { kind: "hit", hit: bySpelling, via: "spelling" };
|
|
226
230
|
if (env === undefined)
|
|
227
231
|
return { kind: "clear" };
|
|
228
|
-
const
|
|
232
|
+
const family = pathFamilyOf({ root: rootPath, cwd: ctx.cwd ?? env.cwd });
|
|
233
|
+
const base = ctx.base ?? ctx.cwd ?? rootPath;
|
|
234
|
+
const canon = await canonicalizeTarget(env, path, ctx.signal, family, base);
|
|
229
235
|
if (!canon.ok)
|
|
230
236
|
return { kind: "unresolvable", cause: canon.message, unresolvedSymlink: canon.unresolvedSymlink === true };
|
|
231
237
|
const byTarget = judgeViews(canon.key);
|
|
232
|
-
if (byTarget
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
238
|
+
if (byTarget !== null) {
|
|
239
|
+
for (const rootKey of await canonicalDataRootKeys(env, dataRootPaths, ctx.signal)) {
|
|
240
|
+
const relative = relativeUnderRoot(canon.key, rootKey);
|
|
241
|
+
if (relative === null)
|
|
242
|
+
continue;
|
|
243
|
+
if (relative === "")
|
|
244
|
+
return { kind: "clear" };
|
|
245
|
+
const remainder = judgeViews(relative);
|
|
246
|
+
return remainder === null ? { kind: "clear" } : { kind: "hit", hit: remainder, via: "target" };
|
|
247
|
+
}
|
|
248
|
+
return { kind: "hit", hit: byTarget, via: "target" };
|
|
249
|
+
}
|
|
250
|
+
for (const hop of await symlinkChainHops(env, path, ctx.signal, family, base)) {
|
|
251
|
+
const byHop = judgeSpelled(hop);
|
|
252
|
+
if (byHop === null)
|
|
237
253
|
continue;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
254
|
+
for (const rootKey of await canonicalDataRootKeys(env, dataRootPaths, ctx.signal)) {
|
|
255
|
+
if (relativeUnderRoot(canon.key, rootKey) !== null)
|
|
256
|
+
return { kind: "clear" };
|
|
257
|
+
}
|
|
258
|
+
return { kind: "hit", hit: byHop, via: "chain" };
|
|
242
259
|
}
|
|
243
|
-
return { kind: "
|
|
260
|
+
return { kind: "clear" };
|
|
244
261
|
},
|
|
245
262
|
};
|
|
246
263
|
}
|
|
@@ -250,7 +267,9 @@ export function writeProtectionTightenDecision(toolName, verdict) {
|
|
|
250
267
|
action: "ask",
|
|
251
268
|
message: verdict.via === "target"
|
|
252
269
|
? `tool "${toolName}" writes to a write-protected path — its real target resolves onto table entry "${verdict.hit.name}" — explicit approval required`
|
|
253
|
-
:
|
|
270
|
+
: verdict.via === "chain"
|
|
271
|
+
? `tool "${toolName}" writes to a write-protected path — its symlink chain passes through table entry "${verdict.hit.name}" — explicit approval required`
|
|
272
|
+
: `tool "${toolName}" writes to a write-protected path (table entry "${verdict.hit.name}") — explicit approval required`,
|
|
254
273
|
decisionReason: "safety",
|
|
255
274
|
};
|
|
256
275
|
}
|
|
@@ -265,11 +284,17 @@ export function writeProtectionTightenDecision(toolName, verdict) {
|
|
|
265
284
|
}
|
|
266
285
|
return undefined;
|
|
267
286
|
}
|
|
287
|
+
export function writeProtectionMandated(verdict) {
|
|
288
|
+
return verdict.kind === "hit";
|
|
289
|
+
}
|
|
290
|
+
export function writeProtectionUnresolved(verdict) {
|
|
291
|
+
return verdict.kind === "unresolvable";
|
|
292
|
+
}
|
|
268
293
|
export function createWriteProtectionCheck(entries, opts) {
|
|
269
294
|
const matcher = compileWriteProtection(entries, opts);
|
|
270
295
|
if (matcher === undefined)
|
|
271
296
|
return undefined;
|
|
272
|
-
|
|
297
|
+
const check = (toolName, args, face, cwd, signal) => {
|
|
273
298
|
const call = { toolName, args, ...(face !== undefined ? { face } : {}) };
|
|
274
299
|
const protective = protectivePathTargetOf(call);
|
|
275
300
|
if (protective === undefined || protective.access === "read")
|
|
@@ -279,4 +304,5 @@ export function createWriteProtectionCheck(entries, opts) {
|
|
|
279
304
|
return null;
|
|
280
305
|
return matcher.judge(path, { ...(cwd !== undefined ? { cwd } : {}), ...(signal !== undefined ? { signal } : {}), ...(() => { const b = pathTargetBaseOf(protective, { root: opts?.rootPath, cwd }); return b !== undefined ? { base: b } : {}; })() });
|
|
281
306
|
};
|
|
307
|
+
return { check, targetView: matcher.targetView };
|
|
282
308
|
}
|
|
@@ -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 { SymlinkChain } from "../harness/types.js";
|
|
3
4
|
import type { BackgroundShellCapability, BackgroundShellId, BackgroundPoll, BackgroundSpawnOptions } from "../../core/background-shell.js";
|
|
4
5
|
import { BackgroundShellError } from "../../core/background-shell.js";
|
|
5
6
|
import type { SchedulerCapability, ScheduledIntent, SchedulerContext, ScheduledTaskId, ScheduledTaskSummary } from "../../core/scheduler.js";
|
|
@@ -165,6 +166,15 @@ export declare class NodeExecutionEnv implements ExecutionEnv, BackgroundShellCa
|
|
|
165
166
|
listDir(path: string, abortSignal?: AbortSignal): Promise<Result<FileInfo[], FileError>>;
|
|
166
167
|
canonicalPath(path: string): Promise<Result<string, FileError>>;
|
|
167
168
|
readLink(path: string): Promise<Result<string, FileError>>;
|
|
169
|
+
/**
|
|
170
|
+
* The LEAF symlink chain ({@link FileSystem.canonicalChain}): `readlink` on the whole spelling, then on each
|
|
171
|
+
* target, until a hop is not a link (`EINVAL`), is absent (`ENOENT` / `ENOTDIR`), repeats, or the bound is
|
|
172
|
+
* reached. A relative target is ANCHORED at its link's directory and never folded — `..` is the filesystem's
|
|
173
|
+
* to resolve during the next `readlink`'s own lookup (a lexical fold reads `bridge/../next` as `next` and misses
|
|
174
|
+
* every link behind `bridge`). One syscall per hop, no `lstat`. On a win32 host a share-shaped hop is kept and
|
|
175
|
+
* ends the walk unprobed. Any other errno is an answer nobody can use (`err`), never a shortened chain.
|
|
176
|
+
*/
|
|
177
|
+
canonicalChain(path: string): Promise<Result<SymlinkChain, FileError>>;
|
|
168
178
|
exists(path: string): Promise<Result<boolean, FileError>>;
|
|
169
179
|
createDir(path: string, options?: {
|
|
170
180
|
recursive?: boolean;
|
|
@@ -3,12 +3,13 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { closeSync, constants, createReadStream, mkdtempSync, openSync, readSync, statSync, truncateSync, unlinkSync } from "node:fs";
|
|
4
4
|
import { access, appendFile, lstat, mkdir, mkdtemp, open, readdir, readFile, readlink, rename, rm, unlink, writeFile, } from "node:fs/promises";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
6
|
+
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
10
|
import { scrubSecretEnv } from "../../core/secret-env.js";
|
|
11
11
|
import { physicalPathOfAsync } from "../../core/physical-path.js";
|
|
12
|
+
import { isUncSpelling } from "../../tools/fs/safety.js";
|
|
12
13
|
import { RollingTailBuffer, markTruncated, newStreamCursorState, sliceStreamIncrement, } from "../../core/exec-output-tail.js";
|
|
13
14
|
import { BackgroundShellError } from "../../core/background-shell.js";
|
|
14
15
|
import { SchedulerError } from "../../core/scheduler.js";
|
|
@@ -41,6 +42,7 @@ function fileKindFromStats(stats) {
|
|
|
41
42
|
return undefined;
|
|
42
43
|
}
|
|
43
44
|
const HAS_POSIX_MODE_FACE = process.platform !== "win32";
|
|
45
|
+
const SYMLINK_CHAIN_HOP_BOUND = 64;
|
|
44
46
|
function fileInfoFromStats(path, stats) {
|
|
45
47
|
const kind = fileKindFromStats(stats);
|
|
46
48
|
if (!kind) {
|
|
@@ -1026,6 +1028,32 @@ export class NodeExecutionEnv {
|
|
|
1026
1028
|
return err(toFileError(error, resolved));
|
|
1027
1029
|
}
|
|
1028
1030
|
}
|
|
1031
|
+
async canonicalChain(path) {
|
|
1032
|
+
const resolved = resolvePath(this.cwd, path);
|
|
1033
|
+
const hops = [];
|
|
1034
|
+
const seen = new Set([resolved]);
|
|
1035
|
+
let cur = resolved;
|
|
1036
|
+
for (let hop = 0; hop < SYMLINK_CHAIN_HOP_BOUND; hop++) {
|
|
1037
|
+
let target;
|
|
1038
|
+
try {
|
|
1039
|
+
target = await readlink(cur);
|
|
1040
|
+
}
|
|
1041
|
+
catch (error) {
|
|
1042
|
+
if (isNodeError(error) && (error.code === "EINVAL" || error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
1043
|
+
return ok({ hops });
|
|
1044
|
+
return err(toFileError(error, cur));
|
|
1045
|
+
}
|
|
1046
|
+
const next = isAbsolute(target) ? target : `${dirname(cur)}${sep}${target}`;
|
|
1047
|
+
if (seen.has(next))
|
|
1048
|
+
break;
|
|
1049
|
+
hops.push(next);
|
|
1050
|
+
if (process.platform === "win32" && isUncSpelling(next))
|
|
1051
|
+
break;
|
|
1052
|
+
seen.add(next);
|
|
1053
|
+
cur = next;
|
|
1054
|
+
}
|
|
1055
|
+
return ok({ hops });
|
|
1056
|
+
}
|
|
1029
1057
|
async exists(path) {
|
|
1030
1058
|
const result = await this.fileInfo(path);
|
|
1031
1059
|
if (result.ok) {
|
|
@@ -271,6 +271,14 @@ export interface FileInfo {
|
|
|
271
271
|
*/
|
|
272
272
|
mode?: number;
|
|
273
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* @contract fs.canonical_chain.leaf_hops_only — `hops` lists, in order, the target each link on the LEAF chain writes
|
|
276
|
+
* ({@link FileSystem.canonicalChain}): a relative target anchored, unfolded, at its own link's directory (`..` is the
|
|
277
|
+
* filesystem's to resolve on the next lookup, never the walk's); the addressed spelling and the canonical terminal ({@link FileSystem.canonicalPath}'s one answer) are not repeated; a directory-component symlink is not expanded; a non-link leaf answers `[]`.
|
|
278
|
+
*/
|
|
279
|
+
export interface SymlinkChain {
|
|
280
|
+
readonly hops: readonly string[];
|
|
281
|
+
}
|
|
274
282
|
/** Options for {@link Shell.exec}. */
|
|
275
283
|
export interface ExecutionEnvExecOptions {
|
|
276
284
|
/** Working directory for the command. Relative paths are resolved against {@link ExecutionEnv.cwd}. Defaults to {@link ExecutionEnv.cwd}. */
|
|
@@ -397,6 +405,8 @@ export interface FileSystem {
|
|
|
397
405
|
* target of `write_file innocent.txt` where `innocent.txt -> .git/hooks/payload`.
|
|
398
406
|
*/
|
|
399
407
|
readLink?(path: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
|
|
408
|
+
/** OPTIONAL: the LEAF symlink chain of `path` ({@link SymlinkChain}). Omitted, or `ok: false` ⇒ the path judges read the spelling and the canonical terminal only (the reach they had before this seat); a remote adapter answers in one `readlink` round trip and a caller never emulates it through {@link readLink}; bounded (64 hops, a cycle ends it), a network hop kept unprobed. */
|
|
409
|
+
canonicalChain?(path: string, abortSignal?: AbortSignal): Promise<Result<SymlinkChain, FileError>>;
|
|
400
410
|
/** Return false for missing paths. Other errors, such as permission failures, return a {@link FileError}. */
|
|
401
411
|
exists(path: string, abortSignal?: AbortSignal): Promise<Result<boolean, FileError>>;
|
|
402
412
|
/** Create a directory. Defaults: `recursive: true`, no abort signal. */
|
package/dist/index.d.ts
CHANGED
|
@@ -87,7 +87,7 @@ export { isSecretEnvKey, scrubSecretEnv } from "./core/secret-env.js";
|
|
|
87
87
|
export type { SecretEnvFinding, SecretEnvFindingKind } from "./core/secret-env.js";
|
|
88
88
|
export { MAX_EXEC_OUTPUT_BYTES, RollingTailBuffer, markTruncated } from "./core/exec-output-tail.js";
|
|
89
89
|
export { physicalPathOf, physicalPathOfExisting } from "./core/physical-path.js";
|
|
90
|
-
export type { ExecutionEnv, FileInfo, Result, FileErrorCode, ExecutionErrorCode, WriteExpectation, WriteReceipt } from "./internal/harness.js";
|
|
90
|
+
export type { ExecutionEnv, FileInfo, Result, FileErrorCode, ExecutionErrorCode, WriteExpectation, WriteReceipt, SymlinkChain } from "./internal/harness.js";
|
|
91
91
|
export type { ExecResult } from "./internal/harness.js";
|
|
92
92
|
export { RemoteExecutionError, hasDestroy, isRemoteExecutionEnv, isSuspendable, isIsolated, canonicalPathIsAuthoritative, missingRestoreSurface, isRetryableRemoteErrorCode, RETRYABLE_REMOTE_ERROR_CODES, } from "./core/remote-env.js";
|
|
93
93
|
export { withRetry } from "./core/with-retry.js";
|
|
@@ -105,7 +105,7 @@ export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, ty
|
|
|
105
105
|
export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
|
|
106
106
|
export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, type ReadDenyEntry, type ReadDenyMatcher, type NormalizedReadDenyEntry, type ReadDenyBuiltinTier, type ReadDenyBuiltinRow, type ReadDenyBuiltinConfig, } from "./tools/fs/index.js";
|
|
107
107
|
export { deploymentFaceClampNotice, resolveContainmentFace, type ContainmentFace, type ContainmentFaceInputs, type ContainmentFaceKind, type ContainmentFaceLayer, } from "./tools/fs/index.js";
|
|
108
|
-
export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, type WriteProtectedEntry, type WriteProtectedRow, type WriteProtectedKind, type WriteProtectedHit, type WriteProtectionMatcher, type WriteProtectionVerdict, type WriteProtectionJudgeContext, type WriteProtectionCompileOptions, type WriteProtectionCheck, } from "./core/write-protect.js";
|
|
108
|
+
export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, type WriteProtectedEntry, type WriteProtectedRow, type WriteProtectedKind, type WriteProtectedHit, type WriteProtectionMatcher, type WriteProtectionVerdict, type WriteProtectionJudgeContext, type WriteProtectionCompileOptions, type WriteProtectionCheck, WRITE_PROTECTION_TARGET_VIEWS, type WriteProtectionTargetView, type WriteProtectionMount, } from "./core/write-protect.js";
|
|
109
109
|
export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, type ToolResultProvenance, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, type ToolResultStore, type ToolResultSlice, type ToolResultDeletionReport, } from "./core/tool-result-store.js";
|
|
110
110
|
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointId, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type ProbeCause, type ProbeCauseOperands, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type TerminalClaimIntent, type TerminalClaimOutcome, type SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, type CheckpointRow, realApprovalOrgFact, resolveCheckpointStore, checkpointStoreExecutionUndeclared, } from "./core/checkpoint-store.js";
|
|
111
111
|
export { EXECUTION_OUTCOME_RECORD_WORDS, EXECUTION_RECORD_LEAVES_ROW_UNRECORDED, type ExecutionOutcomeRecordWord, type ExecutionRecordRowFacts, type ExecutionVerdict, ExecutionOutcomeConflictError, checkpointExecutionRecorded, executionRecordDisposition, sameExecutionRecord, executionVerdict, } from "./core/checkpoint-execution-record.js";
|
package/dist/index.js
CHANGED
|
@@ -83,7 +83,7 @@ export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, }
|
|
|
83
83
|
export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
|
|
84
84
|
export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, } from "./tools/fs/index.js";
|
|
85
85
|
export { deploymentFaceClampNotice, resolveContainmentFace, } from "./tools/fs/index.js";
|
|
86
|
-
export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, } from "./core/write-protect.js";
|
|
86
|
+
export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, WRITE_PROTECTION_TARGET_VIEWS, } from "./core/write-protect.js";
|
|
87
87
|
export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, } from "./core/tool-result-store.js";
|
|
88
88
|
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointId, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, realApprovalOrgFact, resolveCheckpointStore, checkpointStoreExecutionUndeclared, } from "./core/checkpoint-store.js";
|
|
89
89
|
export { EXECUTION_OUTCOME_RECORD_WORDS, EXECUTION_RECORD_LEAVES_ROW_UNRECORDED, ExecutionOutcomeConflictError, checkpointExecutionRecorded, executionRecordDisposition, sameExecutionRecord, executionVerdict, } from "./core/checkpoint-execution-record.js";
|
|
@@ -8,7 +8,7 @@ export type { CompactionPreparation, SummarizationClampDryRun } from "../engine/
|
|
|
8
8
|
export type { InvokedSkillRetention } from "../engine/compaction/utils.js";
|
|
9
9
|
export type { AgentCoreRuntimeDeps } from "../engine/loop/runtime-deps.js";
|
|
10
10
|
export type { AgentMessage, AgentTool, AgentToolResult, AgentToolUpdateCallback, ThinkingLevel, ToolExecutionMode, ToolInputValidationContext, ToolInputVerdict, } from "../engine/loop/types.js";
|
|
11
|
-
export type { AgentHarnessEvent, CompactionSettings, ExecutionEnv, ExecutionErrorCode, FileError, FileErrorCode, FileInfo, Result, WriteExpectation, WriteReceipt, Session, SessionMetadata, SessionRepo, SessionStorage, SessionTreeEntry, Skill, } from "../engine/harness/types.js";
|
|
11
|
+
export type { AgentHarnessEvent, CompactionSettings, ExecutionEnv, ExecutionErrorCode, FileError, FileErrorCode, FileInfo, Result, WriteExpectation, WriteReceipt, SymlinkChain, Session, SessionMetadata, SessionRepo, SessionStorage, SessionTreeEntry, Skill, } from "../engine/harness/types.js";
|
|
12
12
|
export type { ExecutionEnvExecOptions, ExecResult } from "../engine/harness/types.js";
|
|
13
13
|
export type { SessionWriteOptions, CompactionEntry } from "../engine/harness/types.js";
|
|
14
14
|
export type { ActiveWorktreeSession, WorkspaceState } from "../engine/harness/types.js";
|
|
@@ -307,11 +307,21 @@ function refusedCommandExempt(command, allow, boundary, depth, baseMovedOutside
|
|
|
307
307
|
const resolved = resolveOperandLexically(boundary.cwd ?? boundary.roots[0], word, boundary.homeDir);
|
|
308
308
|
if (resolved === undefined || boundary.denyMatch?.(resolved) != null)
|
|
309
309
|
return false;
|
|
310
|
-
return withinAnyRoot(boundary.roots, resolved);
|
|
310
|
+
return boundary.face === "open" || withinAnyRoot(boundary.roots, resolved);
|
|
311
311
|
};
|
|
312
|
-
const unlistedSegmentJudgedInside = (words, from) => {
|
|
312
|
+
const unlistedSegmentJudgedInside = (words, from, leadingAssignments = []) => {
|
|
313
313
|
if (boundary === undefined)
|
|
314
314
|
return true;
|
|
315
|
+
const assignmentJudgedInside = (word) => {
|
|
316
|
+
const payload = assignmentWordPayload(word);
|
|
317
|
+
return payload === undefined || (payload.kind === "value" && stdinFileJudgedInside(payload.value));
|
|
318
|
+
};
|
|
319
|
+
for (const k of leadingAssignments) {
|
|
320
|
+
if (k >= from)
|
|
321
|
+
continue;
|
|
322
|
+
if (!assignmentJudgedInside(words.folded[k]))
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
315
325
|
let endOfOptions = false;
|
|
316
326
|
for (let j = from; j < words.folded.length; j++) {
|
|
317
327
|
const word = words.folded[j];
|
|
@@ -330,6 +340,8 @@ function refusedCommandExempt(command, allow, boundary, depth, baseMovedOutside
|
|
|
330
340
|
if (!stdinFileJudgedInside(candidate))
|
|
331
341
|
return false;
|
|
332
342
|
}
|
|
343
|
+
if (!optionShaped && !assignmentJudgedInside(word))
|
|
344
|
+
return false;
|
|
333
345
|
}
|
|
334
346
|
return true;
|
|
335
347
|
};
|
|
@@ -352,7 +364,7 @@ function refusedCommandExempt(command, allow, boundary, depth, baseMovedOutside
|
|
|
352
364
|
continue;
|
|
353
365
|
if (argvReentryUnjudged(pos, NO_PATH_OPERAND_COMMANDS))
|
|
354
366
|
return false;
|
|
355
|
-
if (!unlistedSegmentJudgedInside(words, pos.launchers[0].index))
|
|
367
|
+
if (!unlistedSegmentJudgedInside(words, pos.launchers[0].index, pos.assignments))
|
|
356
368
|
return false;
|
|
357
369
|
continue;
|
|
358
370
|
}
|
|
@@ -366,7 +378,7 @@ function refusedCommandExempt(command, allow, boundary, depth, baseMovedOutside
|
|
|
366
378
|
if (argvReentryUnjudged(pos, NO_PATH_OPERAND_COMMANDS))
|
|
367
379
|
return false;
|
|
368
380
|
if (!listed(program.basename)) {
|
|
369
|
-
if (!unlistedSegmentJudgedInside(words, pos.launchers[0].index))
|
|
381
|
+
if (!unlistedSegmentJudgedInside(words, pos.launchers[0].index, pos.assignments))
|
|
370
382
|
return false;
|
|
371
383
|
continue;
|
|
372
384
|
}
|
|
@@ -375,7 +387,7 @@ function refusedCommandExempt(command, allow, boundary, depth, baseMovedOutside
|
|
|
375
387
|
if (head === "cd" || UNMODELLED_BASE_MOVERS.has(head))
|
|
376
388
|
continue;
|
|
377
389
|
if (!allow.has(head)) {
|
|
378
|
-
if (!unlistedSegmentJudgedInside(words, program.index))
|
|
390
|
+
if (!unlistedSegmentJudgedInside(words, program.index, pos.assignments))
|
|
379
391
|
return false;
|
|
380
392
|
continue;
|
|
381
393
|
}
|
|
@@ -1516,6 +1528,18 @@ function isGrepFileStdinLongOption(tok) {
|
|
|
1516
1528
|
return name !== undefined && isLongOptionAbbrevOf(name, "file");
|
|
1517
1529
|
}
|
|
1518
1530
|
const CLUSTER_OPTION_CHAR = /[A-Za-z0-9]/;
|
|
1531
|
+
const ASSIGNMENT_OR_APPEND_WORD = /^[A-Za-z_][A-Za-z0-9_]*\+?=/;
|
|
1532
|
+
function assignmentWordPayload(word) {
|
|
1533
|
+
if (!ASSIGNMENT_OR_APPEND_WORD.test(word))
|
|
1534
|
+
return undefined;
|
|
1535
|
+
const eq = word.indexOf("=");
|
|
1536
|
+
const value = word.slice(eq + 1);
|
|
1537
|
+
if (word[eq - 1] === "+")
|
|
1538
|
+
return { kind: "unresolved", reason: `"${word}" appends to a value the text does not contain` };
|
|
1539
|
+
if (value.includes("~"))
|
|
1540
|
+
return { kind: "unresolved", reason: `"${word}" carries a tilde whose expansion the text does not settle` };
|
|
1541
|
+
return value === "" ? undefined : { kind: "value", value };
|
|
1542
|
+
}
|
|
1519
1543
|
function attachedOptionPayloads(tok) {
|
|
1520
1544
|
const literal = literalTildeSpelling;
|
|
1521
1545
|
const out = [];
|
|
@@ -1651,6 +1675,11 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
1651
1675
|
continue;
|
|
1652
1676
|
positionalOperands.push(t);
|
|
1653
1677
|
judgeOperand(t, argGlobs(k));
|
|
1678
|
+
const payload = assignmentWordPayload(t);
|
|
1679
|
+
if (payload?.kind === "value")
|
|
1680
|
+
judgeOperand(payload.value, argGlobs(k));
|
|
1681
|
+
else if (payload?.kind === "unresolved")
|
|
1682
|
+
findings.push({ kind: "unresolvable", operand: t, reason: `"${name}": ${payload.reason} ${NOT_AUTO_ALLOWED}` });
|
|
1654
1683
|
}
|
|
1655
1684
|
}
|
|
1656
1685
|
for (let k = 0; k < args.length; k++) {
|
|
@@ -2163,7 +2192,7 @@ export function classifySimpleCommandReadBoundary(command, boundary) {
|
|
|
2163
2192
|
return evaluateReadBoundary([toks], boundary, true);
|
|
2164
2193
|
}
|
|
2165
2194
|
const NOT_GATED = { gated: false, paths: [] };
|
|
2166
|
-
const UNREADABLE_WHITELIST_WORD = /[$`()*?[\]{}
|
|
2195
|
+
const UNREADABLE_WHITELIST_WORD = /[$`()*?[\]{}]/;
|
|
2167
2196
|
const UNMODELLED_BASE_MOVERS = new Set(["pushd", "popd"]);
|
|
2168
2197
|
export const COMMAND_LAUNCHERS_FOR_TEST = COMMAND_LAUNCHERS;
|
|
2169
2198
|
const LAUNCHER_SCAN_HEAD = "cat";
|
|
@@ -2211,7 +2240,7 @@ export function classifyOutOfRootReadGate(command, allow, boundary) {
|
|
|
2211
2240
|
if (UNMODELLED_BASE_MOVERS.has(name))
|
|
2212
2241
|
baseMoved = true;
|
|
2213
2242
|
if (!allow.has(name) && name !== "cd") {
|
|
2214
|
-
pushReader({ folded: [LAUNCHER_SCAN_HEAD, ...toks.folded
|
|
2243
|
+
pushReader({ folded: [LAUNCHER_SCAN_HEAD, ...toks.folded], raw: [LAUNCHER_SCAN_HEAD, ...toks.raw], slotVerb: name });
|
|
2215
2244
|
continue;
|
|
2216
2245
|
}
|
|
2217
2246
|
pushReader(i === 0 && name === written ? toks : { folded: [name, ...toks.folded.slice(i + 1)], raw: [toks.raw[i] ?? name, ...toks.raw.slice(i + 1)] });
|
|
@@ -47,10 +47,12 @@ export interface ReadDenyMatcher {
|
|
|
47
47
|
matchPath(path: string, opts?: {
|
|
48
48
|
readonly aliasResolved?: boolean;
|
|
49
49
|
}): ReadDenyHit | null;
|
|
50
|
-
/** §3.3 target judgment: canonical view + lexical view
|
|
51
|
-
*
|
|
50
|
+
/** §3.3 target judgment: canonical view + lexical view + every hop of the leaf symlink chain (`hops`,
|
|
51
|
+
* each an unresolved spelling some link wrote); ANY hit decides. `aliasResolved` applies to the
|
|
52
|
+
* CANONICAL arm only — the lexical view and the hops are unresolved spellings. */
|
|
52
53
|
matchTarget(canonicalKey: string, lexicalView?: string, opts?: {
|
|
53
54
|
readonly aliasResolved?: boolean;
|
|
55
|
+
readonly hops?: readonly string[];
|
|
54
56
|
}): ReadDenyHit | null;
|
|
55
57
|
/** Traversal exclusions for the rg legs: `!`-polarity glob pairs — `!**\/<p>` plus `!**\/<p>/…`
|
|
56
58
|
* (the entry itself and its descendants). */
|
|
@@ -227,6 +227,11 @@ export function compileReadDeny(additions = [], layer = "additions", builtin) {
|
|
|
227
227
|
if (lexical !== null)
|
|
228
228
|
return { ...lexical, matchedView: lexicalView };
|
|
229
229
|
}
|
|
230
|
+
for (const hop of opts?.hops ?? []) {
|
|
231
|
+
const viaHop = matchPath(hop);
|
|
232
|
+
if (viaHop !== null)
|
|
233
|
+
return { ...viaHop, matchedView: hop };
|
|
234
|
+
}
|
|
230
235
|
return null;
|
|
231
236
|
},
|
|
232
237
|
rgExclusionGlobs,
|
|
@@ -290,6 +290,10 @@ export declare function pathFamilyOf(bases: {
|
|
|
290
290
|
readonly root?: string | undefined;
|
|
291
291
|
readonly cwd?: string | undefined;
|
|
292
292
|
}): PathFamily | undefined;
|
|
293
|
+
/** UNC under the win family: two leading separators, a host, a share — either separator, as `path.win32`
|
|
294
|
+
* reads it. The ONE UNC recognizer behind {@link isAbsoluteForFamily}, {@link pathIdentityOf} and
|
|
295
|
+
* {@link joinForFamily}. */
|
|
296
|
+
export declare function isUncSpelling(p: string): boolean;
|
|
293
297
|
/**
|
|
294
298
|
* FULLY QUALIFIED under the family. win: drive-rooted (`C:\`/`C:/`) or UNC — a `/x` / `\x` is rooted-not-
|
|
295
299
|
* qualified and joins the base's drive or share root ({@link joinForFamily}), a `C:rel` is drive-relative.
|
|
@@ -422,6 +426,14 @@ export declare function resolveKey(env: ExecutionEnv, rootCanonical: string, pat
|
|
|
422
426
|
* `createSensitivePathPolicy` (which matches the real target against a guarded list).
|
|
423
427
|
* A single source of truth for symlink resolution: a sensitive-path policy that matched the raw path
|
|
424
428
|
* instead of the real target would compare against the spelling, not the destination.
|
|
429
|
+
*
|
|
430
|
+
* @contract fs.canonical_target.probed_and_bounded — every model-spelled path is resolved by asking the
|
|
431
|
+
* execution environment where it really lands; no spelling shape is exempt from that, and the win-form
|
|
432
|
+
* UNC spelling stopped being the one that was. The ask is bounded by the tree's shared bounded-await
|
|
433
|
+
* deadline rather than by a timer of this module's own, and a wait that elapses or a probe that fails
|
|
434
|
+
* comes back as a refusal carrying no key — the spelling is never handed back as a substitute for the
|
|
435
|
+
* answer. What the deadline bounds is the CALLER'S wait: the underlying filesystem call is not
|
|
436
|
+
* cancellable and may go on occupying the runtime's I/O capacity until the kernel gives up.
|
|
425
437
|
*/
|
|
426
438
|
export declare function canonicalizeTarget(env: ExecutionEnv, path: string, signal: AbortSignal | undefined, family: PathFamily, baseCwd?: string): Promise<{
|
|
427
439
|
ok: true;
|
package/dist/tools/fs/safety.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { sliceHeadSafe } from "../../core/surrogate-safe-slice.js";
|
|
3
3
|
import { pathTargetValue, protectivePathTargetOf } from "../../core/tool-registry.js";
|
|
4
|
+
import { withGateTimeout, DEFAULT_GATE_AWAIT_TIMEOUT_MS } from "../../core/gate-bounded-await.js";
|
|
4
5
|
export function fileArgPath(args) {
|
|
5
6
|
const a = args;
|
|
6
7
|
const fp = a?.file_path;
|
|
@@ -291,29 +292,6 @@ function stripWin32ExtendedPrefix(p) {
|
|
|
291
292
|
}
|
|
292
293
|
return unc ? "\\\\" + rest.slice(unc[0].length) : rest;
|
|
293
294
|
}
|
|
294
|
-
function foldUncLexically(p) {
|
|
295
|
-
const body = p.slice(2);
|
|
296
|
-
const segs = body.split(/[\\/]+/).filter((s) => s !== "");
|
|
297
|
-
const host = segs.shift() ?? "";
|
|
298
|
-
const share = segs.shift();
|
|
299
|
-
const out = [];
|
|
300
|
-
let climbed = false;
|
|
301
|
-
for (const seg of segs) {
|
|
302
|
-
if (seg === ".")
|
|
303
|
-
continue;
|
|
304
|
-
if (seg === "..") {
|
|
305
|
-
if (out.length === 0)
|
|
306
|
-
climbed = true;
|
|
307
|
-
else
|
|
308
|
-
out.pop();
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
const stripped = seg.replace(/[. ]+$/, "");
|
|
312
|
-
out.push(stripped.length > 0 ? stripped : seg);
|
|
313
|
-
}
|
|
314
|
-
const anchor = `\\\\${host}${share !== undefined ? `\\${share}` : ""}`;
|
|
315
|
-
return { key: out.length > 0 ? `${anchor}\\${out.join("\\")}` : anchor, climbedAboveShare: climbed };
|
|
316
|
-
}
|
|
317
295
|
export function hasWin32AliasedComponent(p) {
|
|
318
296
|
if (!isWinFormPath(p))
|
|
319
297
|
return false;
|
|
@@ -325,7 +303,7 @@ export function pathFamilyOf(bases) {
|
|
|
325
303
|
const tree = bases.root ?? bases.cwd;
|
|
326
304
|
return tree === undefined ? undefined : isWinFormPath(tree) ? "win" : "posix";
|
|
327
305
|
}
|
|
328
|
-
function isUncSpelling(p) {
|
|
306
|
+
export function isUncSpelling(p) {
|
|
329
307
|
return /^[\\/]{2}[^\\/]+[\\/]+[^\\/]/.test(p);
|
|
330
308
|
}
|
|
331
309
|
export function isAbsoluteForFamily(family, p) {
|
|
@@ -511,16 +489,26 @@ export async function canonicalizeTarget(env, path, signal, family, baseCwd) {
|
|
|
511
489
|
if (!screened.ok) {
|
|
512
490
|
return { ok: false, message: `path "${clipSpelling(path)}" ${WIN32_NAMESPACE_REFUSAL_TEXT[screened.refuse]}; refused (${screened.refuse}).`, refused: screened.refuse };
|
|
513
491
|
}
|
|
514
|
-
const unprefixed = screened.spelled;
|
|
515
|
-
if (family === "win" && isUncSpelling(unprefixed)) {
|
|
516
|
-
const folded = foldUncLexically(unprefixed);
|
|
517
|
-
if (folded.climbedAboveShare) {
|
|
518
|
-
return { ok: false, message: `path "${path}" climbs above its UNC share root via ".." (\\\\host\\share is the root of a network path); refused.` };
|
|
519
|
-
}
|
|
520
|
-
return { ok: true, key: folded.key };
|
|
521
|
-
}
|
|
492
|
+
const unprefixed = nativeUncSpellingOf(family, screened.spelled);
|
|
522
493
|
const spelled = family === "posix" && unprefixed.startsWith("//") ? unprefixed.replace(/^\/+/, "/") : unprefixed;
|
|
523
494
|
const target = baseCwd && !isAbsoluteForFamily(family, spelled) ? joinForFamily(family, baseCwd, spelled) : spelled;
|
|
495
|
+
let thrownByProbe;
|
|
496
|
+
let seenThrow = false;
|
|
497
|
+
const probe = probeRealTarget(env, path, target, family, signal).catch((e) => {
|
|
498
|
+
thrownByProbe = e;
|
|
499
|
+
seenThrow = true;
|
|
500
|
+
throw e;
|
|
501
|
+
});
|
|
502
|
+
try {
|
|
503
|
+
return await withGateTimeout(probe, DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "pathCanonicalization");
|
|
504
|
+
}
|
|
505
|
+
catch (err) {
|
|
506
|
+
if (seenThrow && err === thrownByProbe)
|
|
507
|
+
throw err;
|
|
508
|
+
return { ok: false, message: `cannot resolve path "${path}": ${err instanceof Error ? err.message : String(err)}` };
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
async function probeRealTarget(env, path, target, family, signal) {
|
|
524
512
|
const absR = await env.absolutePath(target, signal);
|
|
525
513
|
if (!absR.ok)
|
|
526
514
|
return { ok: false, message: `cannot resolve path "${path}": ${absR.error.message}` };
|
|
@@ -531,7 +519,7 @@ export async function canonicalizeTarget(env, path, signal, family, baseCwd) {
|
|
|
531
519
|
return { ok: false, message: `cannot determine whether "${path}" exists: ${exists.error.message}`, unresolvedSymlink: true };
|
|
532
520
|
}
|
|
533
521
|
if (!exists.value) {
|
|
534
|
-
const newPath = await canonicalizeNewPath(env, abs, signal);
|
|
522
|
+
const newPath = await canonicalizeNewPath(env, abs, family, signal);
|
|
535
523
|
if (!newPath.ok) {
|
|
536
524
|
return { ok: false, message: newPath.message, unresolvedSymlink: true };
|
|
537
525
|
}
|
|
@@ -545,7 +533,7 @@ export async function canonicalizeTarget(env, path, signal, family, baseCwd) {
|
|
|
545
533
|
return { ok: false, message: `cannot determine whether "${path}" is a symlink: ${info.error.message}`, unresolvedSymlink: true };
|
|
546
534
|
}
|
|
547
535
|
if (info.value.kind !== "symlink") {
|
|
548
|
-
const viaAncestors = await canonicalizeNewPath(env, abs, signal, { skipLeaf: true });
|
|
536
|
+
const viaAncestors = await canonicalizeNewPath(env, abs, family, signal, { skipLeaf: true });
|
|
549
537
|
if (!viaAncestors.ok) {
|
|
550
538
|
return { ok: false, message: viaAncestors.message, unresolvedSymlink: true };
|
|
551
539
|
}
|
|
@@ -574,13 +562,26 @@ function parentDir(abs) {
|
|
|
574
562
|
const head = abs.slice(0, i);
|
|
575
563
|
return /^[A-Za-z]:$/.test(head) ? head + abs[i] : head;
|
|
576
564
|
}
|
|
577
|
-
|
|
565
|
+
function uncAnchorIndex(parts) {
|
|
566
|
+
let seen = 0;
|
|
567
|
+
for (let i = 0; i < parts.length; i++) {
|
|
568
|
+
if (parts[i] === "")
|
|
569
|
+
continue;
|
|
570
|
+
seen++;
|
|
571
|
+
if (seen === 2)
|
|
572
|
+
return i;
|
|
573
|
+
}
|
|
574
|
+
return parts.length;
|
|
575
|
+
}
|
|
576
|
+
async function canonicalizeNewPath(env, abs, family, signal, opts) {
|
|
578
577
|
const sep = nativeSepOf(abs);
|
|
579
|
-
const
|
|
578
|
+
const winForm = family === "win" || isWinFormPath(abs);
|
|
579
|
+
const parts = winForm ? abs.split(/[\\/]/) : abs.split("/");
|
|
580
580
|
const tail = [];
|
|
581
|
+
const floor = family === "win" && isUncSpelling(abs) ? uncAnchorIndex(parts) : 1;
|
|
581
582
|
if (opts?.skipLeaf === true && parts.length > 1)
|
|
582
583
|
tail.push(parts.pop());
|
|
583
|
-
while (parts.length >
|
|
584
|
+
while (parts.length > floor) {
|
|
584
585
|
const candidate = parts.join(sep) || sep;
|
|
585
586
|
const ex = await env.exists(candidate, signal);
|
|
586
587
|
if (!ex.ok) {
|
|
@@ -591,7 +592,11 @@ async function canonicalizeNewPath(env, abs, signal, opts) {
|
|
|
591
592
|
if (!canon.ok) {
|
|
592
593
|
return { ok: false, message: `cannot canonicalize existing ancestor "${candidate}": ${canon.error.message}` };
|
|
593
594
|
}
|
|
594
|
-
|
|
595
|
+
const tailSegments = tail.reverse();
|
|
596
|
+
const canonSep = nativeSepOf(canon.value);
|
|
597
|
+
if (tailSegments.length === 0)
|
|
598
|
+
return { ok: true, key: canon.value };
|
|
599
|
+
return { ok: true, key: [canon.value.replace(family === "win" ? /[\\/]+$/ : /\/+$/, ""), ...tailSegments].join(canonSep) };
|
|
595
600
|
}
|
|
596
601
|
tail.push(parts.pop());
|
|
597
602
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/core",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.21.0",
|
|
4
4
|
"description": "Stateless, task-oriented AI agent core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
"gate:docs-tool-names": "node scripts/verify-docs-tool-names.mjs",
|
|
89
89
|
"gate:comment-ratio": "vitest run test/comment-ratio-ratchet.test.ts",
|
|
90
90
|
"gate:negative-controls": "vitest run test/gate-negative-controls.test.ts",
|
|
91
|
+
"gate:memory-ga": "node scripts/gate-memory-distillation-ga.mjs",
|
|
91
92
|
"gate:all": "node scripts/gate-all.mjs",
|
|
92
93
|
"car:gate": "node scripts/car-gate.mjs"
|
|
93
94
|
},
|