@sema-agent/core 7.14.0 → 7.16.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 +59 -0
- package/dist/core/ask-origin.d.ts +2 -2
- package/dist/core/checkpoint-store.d.ts +14 -15
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +4 -3
- package/dist/core/memory-engine/delegation-settlement.d.ts +37 -12
- package/dist/core/memory-engine/delegation-settlement.js +128 -39
- package/dist/core/memory-engine/engine.d.ts +9 -8
- package/dist/core/memory-engine/engine.js +115 -120
- package/dist/core/memory-engine/file-backend.d.ts +17 -5
- package/dist/core/memory-engine/file-backend.js +78 -34
- package/dist/core/memory-engine/index.d.ts +2 -2
- package/dist/core/memory-engine/index.js +2 -2
- package/dist/core/memory-engine/layout.d.ts +152 -54
- package/dist/core/memory-engine/layout.js +441 -104
- package/dist/core/memory-engine/types.d.ts +8 -3
- package/dist/core/memory-engine/types.js +5 -0
- package/dist/core/runner/abort-race.d.ts +3 -3
- package/dist/core/runner/active-skill-scope.d.ts +2 -2
- package/dist/core/runner/assemble-result.d.ts +29 -29
- package/dist/core/runner/checkpoint-scope.d.ts +4 -4
- package/dist/core/runner/clock-and-limits.d.ts +3 -3
- package/dist/core/runner/compaction-call-options.d.ts +11 -11
- package/dist/core/runner/compaction-knobs.d.ts +4 -4
- package/dist/core/runner/compaction-seams.d.ts +5 -5
- package/dist/core/runner/contracts.d.ts +224 -226
- package/dist/core/runner/decide-continuation.d.ts +6 -6
- package/dist/core/runner/denial-limit-arms.d.ts +8 -8
- package/dist/core/runner/derived-route-fallback.d.ts +2 -2
- package/dist/core/runner/gate-exit.d.ts +6 -6
- package/dist/core/runner/git-status-frame.d.ts +7 -7
- package/dist/core/runner/inherited-ask-grants.d.ts +6 -6
- package/dist/core/runner/memory-capture-optout.d.ts +1 -1
- package/dist/core/runner/memory-consolidation-driver.d.ts +4 -4
- package/dist/core/runner/memory-consolidation.d.ts +6 -6
- package/dist/core/runner/park-commit.d.ts +6 -6
- package/dist/core/runner/permission-rule-lanes.d.ts +22 -20
- package/dist/core/runner/permission-rule-lanes.js +1 -1
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +4 -4
- package/dist/core/runner/prepare-ask-lane.d.ts +3 -3
- package/dist/core/runner/prepare-boundary-parks.d.ts +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -5
- package/dist/core/runner/prepare-config-doors.d.ts +21 -21
- package/dist/core/runner/prepare-context-lane.d.ts +6 -6
- package/dist/core/runner/prepare-defer-classify.d.ts +3 -3
- package/dist/core/runner/prepare-defer-classify.js +12 -10
- package/dist/core/runner/prepare-delegation-surface.d.ts +1 -1
- package/dist/core/runner/prepare-file-history.d.ts +2 -2
- package/dist/core/runner/prepare-gate-stations.d.ts +8 -9
- package/dist/core/runner/prepare-gate-stations.js +2 -2
- package/dist/core/runner/prepare-hands-readface.d.ts +41 -41
- package/dist/core/runner/prepare-hands-readface.js +27 -29
- package/dist/core/runner/prepare-inherited-gate.d.ts +6 -6
- package/dist/core/runner/prepare-listings.d.ts +3 -3
- package/dist/core/runner/prepare-lsp.d.ts +3 -3
- package/dist/core/runner/prepare-memory-engine-session.d.ts +5 -5
- package/dist/core/runner/prepare-memory.js +6 -7
- package/dist/core/runner/prepare-offload-wrappers.d.ts +2 -2
- package/dist/core/runner/prepare-park-ask.d.ts +11 -12
- package/dist/core/runner/prepare-park-ask.js +3 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +6 -6
- package/dist/core/runner/prepare-policy-chain.js +2 -2
- package/dist/core/runner/prepare-prompt-inputs.d.ts +5 -5
- package/dist/core/runner/prepare-protocol-tools.d.ts +1 -1
- package/dist/core/runner/prepare-question-face.d.ts +4 -4
- package/dist/core/runner/prepare-run-refs.d.ts +11 -11
- package/dist/core/runner/prepare-safety-scan.d.ts +1 -1
- package/dist/core/runner/prepare-suspend-saga.d.ts +4 -4
- package/dist/core/runner/prepare-task.d.ts +12 -12
- package/dist/core/runner/prepare-task.js +5 -7
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +2 -3
- package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -4
- package/dist/core/runner/prepare-workspace-restore.d.ts +1 -1
- package/dist/core/runner/remote-env-retry.d.ts +3 -3
- package/dist/core/runner/resume-admission.d.ts +1 -1
- package/dist/core/runner/resume-claim.d.ts +1 -1
- package/dist/core/runner/resume-internals-and-config.d.ts +1 -1
- package/dist/core/runner/resume-policy-outcome.d.ts +1 -1
- package/dist/core/runner/resume-preflight.d.ts +1 -1
- package/dist/core/runner/resume-review-outcome.d.ts +1 -1
- package/dist/core/runner/run-brain-sinks.d.ts +1 -1
- package/dist/core/runner/run-clock-and-content.d.ts +4 -4
- package/dist/core/runner/run-compaction-machinery.d.ts +3 -3
- package/dist/core/runner/run-git-lane.d.ts +1 -1
- package/dist/core/runner/run-harness-handlers.d.ts +2 -2
- package/dist/core/runner/run-identity-wiring.d.ts +4 -4
- package/dist/core/runner/run-leg.d.ts +3 -3
- package/dist/core/runner/run-notification-lane.d.ts +2 -2
- package/dist/core/runner/run-reasoning-seat.d.ts +2 -2
- package/dist/core/runner/run-settle-and-teardown.d.ts +4 -4
- package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
- package/dist/core/runner/run-terminal-adoption.d.ts +5 -5
- package/dist/core/runner/runtask.d.ts +14 -14
- package/dist/core/runner/session-file-state-replay.d.ts +3 -3
- package/dist/core/runner/session-rule-policy.d.ts +2 -2
- package/dist/core/runner/steer-admission.d.ts +2 -2
- package/dist/core/runner/stream-halt-verbs.d.ts +1 -1
- package/dist/core/runner/stream-reap.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.d.ts +8 -8
- package/dist/core/runner/teardown-bounded.d.ts +2 -2
- package/dist/core/runner/tool-defer-gate.d.ts +86 -0
- package/dist/core/runner/tool-defer-gate.js +57 -0
- package/dist/core/runner/tool-disclosure.d.ts +33 -69
- package/dist/core/runner/tool-disclosure.js +0 -43
- package/dist/core/runner/tool-end-body.d.ts +3 -3
- package/dist/core/runner/tool-face-overlay.d.ts +2 -2
- package/dist/core/runner/tool-output-projection.d.ts +2 -2
- package/dist/core/runner/turn-attachments.d.ts +53 -53
- package/dist/core/runner/usage-accounting.d.ts +4 -4
- package/dist/core/runner/workspace-path.d.ts +1 -1
- package/dist/core/runner-deps.d.ts +16 -9
- package/dist/core/task-spec.d.ts +8 -2
- package/dist/core/tool-spec.d.ts +7 -4
- package/dist/core/types.d.ts +1 -1
- package/dist/engine/llm/types.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/tools/fs/bash-readonly-classifier.d.ts +40 -40
- package/dist/tools/fs/encoding.d.ts +31 -40
- package/dist/tools/fs/fs-bash.d.ts +76 -35
- package/dist/tools/fs/fs-bash.js +86 -29
- package/dist/tools/fs/fs-pdf.d.ts +8 -8
- package/dist/tools/fs/fs-search-tools.d.ts +4 -4
- package/dist/tools/fs/fs-shared.d.ts +144 -158
- package/dist/tools/fs/fs-write.d.ts +8 -8
- package/dist/tools/fs/gh-rate-limit.d.ts +13 -14
- package/dist/tools/fs/index.d.ts +74 -78
- package/dist/tools/fs/notebook.d.ts +24 -25
- package/dist/tools/fs/pdf.d.ts +3 -3
- package/dist/tools/fs/read-deny.d.ts +22 -22
- package/dist/tools/fs/read-face.d.ts +21 -24
- package/dist/tools/fs/repo-map.d.ts +8 -8
- package/dist/tools/fs/safety.d.ts +116 -129
- package/dist/tools/fs/search.d.ts +63 -71
- package/package.json +5 -2
- package/test/export-surface.snapshot.json +9 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
2
|
import { protectivePathTargetOf, type ToolCallLike } from "../../core/tool-registry.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* CC parity: Read/Edit/Write take the file path as `file_path`. Read the target the same way
|
|
5
5
|
* EVERYWHERE the path is consumed from a tool-call's args — not just the tool body but ALSO every gate that
|
|
6
6
|
* extracts the write target to confine it (sensitive-path, skill allowPaths, session allowDirs). A gate that only
|
|
7
7
|
* read `args.path` would see `undefined` for a `file_path` call and either fail-OPEN (circumvent the guard — a real
|
|
8
8
|
* hole) or fail-closed (deny a legitimate write). Single source.
|
|
9
9
|
*
|
|
10
|
-
* The `path` arm is deliberately BROADER than what Read/Edit/Write accept
|
|
10
|
+
* The `path` arm is deliberately BROADER than what Read/Edit/Write accept: their schema requires
|
|
11
11
|
* `file_path` and rejects the retired `path` alias outright (fs-shared.ts FILE_PATH_PARAMS), but this reader is
|
|
12
12
|
* shared with gates that judge OTHER tool vocabularies — Glob's real `path` parameter, deployment-authored tools —
|
|
13
13
|
* and a gate must never see LESS than the tool it is confining might act on. So the asymmetry is the design: the
|
|
@@ -15,8 +15,8 @@ import { protectivePathTargetOf, type ToolCallLike } from "../../core/tool-regis
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function fileArgPath(args: unknown): string | undefined;
|
|
17
17
|
/**
|
|
18
|
-
* The WRITE TARGET of a path-confinable write call, tool-aware
|
|
19
|
-
*
|
|
18
|
+
* The WRITE TARGET of a path-confinable write call, tool-aware — single source for every gate that
|
|
19
|
+
* confines a write target, same discipline as {@link fileArgPath} above:
|
|
20
20
|
*
|
|
21
21
|
* · canonical `NotebookEdit` → `notebook_path` FIRST, `file_path`/`path` only as fallback. The tool
|
|
22
22
|
* itself writes `notebook_path`; its schema tolerates extra keys, so a stray/decoy `file_path` on a
|
|
@@ -25,30 +25,30 @@ export declare function fileArgPath(args: unknown): string | undefined;
|
|
|
25
25
|
* · every other tool → `file_path`/`path` first ({@link fileArgPath}), `notebook_path` as the
|
|
26
26
|
* historical fallback (unchanged behavior for the pre-existing gate callers).
|
|
27
27
|
*
|
|
28
|
-
* `canonicalName` is the tool name as mounted
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* `canonicalName` is the tool name as mounted, raw: there is no legacy normalizer, and this module
|
|
29
|
+
* stays dependency-free of the alias table — "NotebookEdit" is the canonical CC name and old→new
|
|
30
|
+
* normalization is identity for it.
|
|
31
31
|
*/
|
|
32
32
|
export declare function writeTargetPath(call: ToolCallLike, pt?: ReturnType<typeof protectivePathTargetOf>): string | undefined;
|
|
33
33
|
/**
|
|
34
|
-
* The "hand" file tools
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* this dir compose it. Per-task state and the execution env are passed in (factory-closure wiring,
|
|
38
|
-
*
|
|
34
|
+
* The "hand" file tools' safety invariants, ported from CC's `FileEditTool`: without these a weak
|
|
35
|
+
* model blind-edits / overwrites files (a safety property, not polish). This module is the
|
|
36
|
+
* pure/testable core (path resolution + the three edit invariants + content hashing); the tools in
|
|
37
|
+
* this dir compose it. Per-task state and the execution env are passed in (factory-closure wiring),
|
|
38
|
+
* never read from a shared/global.
|
|
39
39
|
*/
|
|
40
|
-
/** What we remember about a file the agent has read
|
|
40
|
+
/** What we remember about a file the agent has read. */
|
|
41
41
|
export interface ReadEntry {
|
|
42
42
|
/** SHA-256 of the content at read time. Staleness is detected by content hash, NOT mtime — mtime is
|
|
43
|
-
* unreliable across containers/volumes/NFS and across a
|
|
43
|
+
* unreliable across containers/volumes/NFS and across a suspend→resume on another replica. */
|
|
44
44
|
hash: string;
|
|
45
|
-
/** Total line count at read time, and whether the read was truncated (for the edit truncation hint
|
|
45
|
+
/** Total line count at read time, and whether the read was truncated (for the edit truncation hint). */
|
|
46
46
|
totalLines: number;
|
|
47
47
|
truncated: boolean;
|
|
48
|
-
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
48
|
+
/** CC `isPartialView` parity: set ONLY when the auto-pagination branch fires — the model asked for
|
|
49
|
+
* the file by DEFAULT (no explicit offset/limit) and the token cap truncated it anyway. An EXPLICIT
|
|
50
|
+
* offset/limit slice that fits under the cap is NEVER marked this way, even though it is equally
|
|
51
|
+
* "not the whole file". Deliberately narrower than {@link truncated} above,
|
|
52
52
|
* which also fires for a fully-intentional, successful slice read (`start>1 || end<total`) — folding
|
|
53
53
|
* THIS flag into that broader one would make {@link OVERSIZE_READ_ESCAPE_HINT}'s documented promise
|
|
54
54
|
* ("read it in slices with explicit offset/limit to satisfy the read-first rule") false: a model paging
|
|
@@ -56,51 +56,50 @@ export interface ReadEntry {
|
|
|
56
56
|
* additive: undefined on every non-Read write-back (Write/Edit re-record the file they just fully wrote)
|
|
57
57
|
* and on every seeded/attachment entry — those are never partial by construction. */
|
|
58
58
|
isPartialView?: boolean;
|
|
59
|
-
/** The line window actually served to the model on the last read (
|
|
59
|
+
/** The line window actually served to the model on the last read (the dedup stub's key). Lets
|
|
60
60
|
* read_file return a `file_unchanged` stub when the SAME window is re-requested and the content hash is
|
|
61
61
|
* unchanged — saving the re-transmitted body. Optional/additive: undefined (e.g. an entry seeded from an
|
|
62
|
-
* older
|
|
62
|
+
* older checkpoint) just means "no dedup", never a false hit. */
|
|
63
63
|
view?: {
|
|
64
64
|
start: number;
|
|
65
65
|
end: number;
|
|
66
66
|
};
|
|
67
|
-
/** Millisecond timestamp of the last read/write-back that recorded this entry
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* seeded from an older checkpoint) sorts last, never breaks. */
|
|
67
|
+
/** Millisecond timestamp of the last read/write-back that recorded this entry: the post-compaction
|
|
68
|
+
* attachment picks the most RECENTLY read files, the way CC sorts readFileState by timestamp.
|
|
69
|
+
* Optional/additive: undefined (an entry seeded from an older checkpoint) sorts last, never breaks. */
|
|
71
70
|
lastReadAt?: number;
|
|
72
|
-
/** CC 2.1.204 parity
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
71
|
+
/** CC 2.1.204 parity: TRUE when the Runner pre-seeded this entry because the file's FULL
|
|
72
|
+
* disk-verbatim text was injected into the model's context at startup
|
|
73
|
+
* (ProjectMemoryLoad.seededFiles — CC's own memory preload). A DEFAULT whole-file Read of an
|
|
74
|
+
* UNCHANGED seeded file
|
|
76
75
|
* answers with the already-in-context reminder instead of re-transmitting the body. Any real
|
|
77
76
|
* Read / edit write-back records a FRESH entry without this flag, so a file that changed on
|
|
78
77
|
* disk (hash mismatch) always serves real content and the dedup disarms permanently.
|
|
79
78
|
* Optional/additive: absent = normal entry. */
|
|
80
79
|
seededFromContext?: boolean;
|
|
81
80
|
}
|
|
82
|
-
/** Per-task record of which files have been read, keyed by canonical path
|
|
83
|
-
*
|
|
81
|
+
/** Per-task record of which files have been read, keyed by canonical path: owned by prepare-task,
|
|
82
|
+
* closure-captured by the tools, and serialized into the durable checkpoint. */
|
|
84
83
|
export type ReadFileState = Map<string, ReadEntry>;
|
|
85
|
-
/** SHA-256 hex of UTF-8 content — the staleness/identity key
|
|
84
|
+
/** SHA-256 hex of UTF-8 content — the staleness/identity key. */
|
|
86
85
|
export declare function sha256(content: string): string;
|
|
87
86
|
/** A failed path resolution / invariant check, surfaced to the model as a self-correctable tool error. */
|
|
88
87
|
export interface FsViolation {
|
|
89
88
|
code: "path_not_in_root" | "not_read" | "stale" | "ambiguous_edit" | "invalid" | "read_path_denied";
|
|
90
|
-
/**
|
|
89
|
+
/** Set on the `not_read` violation raised by an AUTO-TRUNCATED read
|
|
91
90
|
* ({@link ReadEntry.isPartialView}) rather than by a never-read file. Both carry the same CC-verbatim
|
|
92
91
|
* message; only this one needs {@link PARTIAL_VIEW_READ_ESCAPE_HINT} appended, because for it a plain
|
|
93
92
|
* re-read is a fixpoint. */
|
|
94
93
|
partialView?: true;
|
|
95
|
-
/**
|
|
94
|
+
/** Set on `path_not_in_root` and `read_path_denied` — the CANONICAL key the
|
|
96
95
|
* refused path resolved to (symlinks followed, tracked cwd applied), i.e. the value the fence
|
|
97
96
|
* actually judged. Rides into the tool result's `details` (see {@link violationDetails}) so a
|
|
98
97
|
* consumer can count/aggregate boundary refusals from fields instead of regexing the prose. */
|
|
99
98
|
target?: string;
|
|
100
|
-
/**
|
|
99
|
+
/** Set ONLY on `path_not_in_root` — the containment roots in effect (primary first,
|
|
101
100
|
* then additionalDirectories), canonical form. */
|
|
102
101
|
roots?: readonly string[];
|
|
103
|
-
/**
|
|
102
|
+
/** Set ONLY on `read_path_denied` — the deny-list pattern (verbatim entry text)
|
|
104
103
|
* the judged target matched. */
|
|
105
104
|
pattern?: string;
|
|
106
105
|
message: string;
|
|
@@ -108,21 +107,17 @@ export interface FsViolation {
|
|
|
108
107
|
/**
|
|
109
108
|
* Absolute in EITHER family: POSIX `/…` or drive-letter `C:\…`/`C:/…`.
|
|
110
109
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* carried a comment claiming the same philosophy as this file while keeping their own copy. The
|
|
115
|
-
* failure mode is not the duplication itself but the drift it invites: a family added at one
|
|
116
|
-
* coordinate leaves the other eight judging the same string differently, and this predicate gates a
|
|
110
|
+
* This is the ONE definition of "already absolute, therefore do not prepend a base". The failure mode
|
|
111
|
+
* of a private re-typing is not the duplication but the drift it invites: a family added at one
|
|
112
|
+
* coordinate leaves every other copy judging the same string differently, and this predicate gates a
|
|
117
113
|
* security fence (frozen-path deny, readonly-boundary containment) as well as URI key minting.
|
|
118
114
|
* Callers that need a family the predicate does not cover must widen it HERE, not locally.
|
|
119
115
|
*
|
|
120
|
-
* The UNC arm (`\\host\share\…`)
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* POSIX/drive root, i.e. it is a location the working directory does not contain.
|
|
116
|
+
* The UNC arm (`\\host\share\…`) is the shape that drift produced: held privately in one consumer, it
|
|
117
|
+
* made that consumer resolve a UNC path as absolute while a sibling prefixed it with the task root —
|
|
118
|
+
* two URI keys for one file. Folding it in here settles the disagreement in the direction the rest of
|
|
119
|
+
* this file takes: a literal `\\host\share\…` is win-form (see {@link isWinFormPath}) and `within()`
|
|
120
|
+
* lands it outside every POSIX/drive root, i.e. a location the working directory does not contain.
|
|
126
121
|
*/
|
|
127
122
|
export declare function isAbsolutePathForm(p: string): boolean;
|
|
128
123
|
/**
|
|
@@ -133,10 +128,10 @@ export declare function isAbsolutePathForm(p: string): boolean;
|
|
|
133
128
|
* contains a literal backslash (legal on POSIX), and `//host/share` is therefore POSIX-form too — which is
|
|
134
129
|
* the whole point: on POSIX that spelling is an ordinary absolute path, not a network share.
|
|
135
130
|
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
131
|
+
* This is the ONE definition of the win-family spelling; core/file-snapshot-store.ts conditions its
|
|
132
|
+
* manifest key / restore-target folds on this import rather than a private twin. core/ already depends
|
|
133
|
+
* on this module elsewhere, and this file's own imports are `node:crypto` plus a type, so there is no
|
|
134
|
+
* layering wall between them.
|
|
140
135
|
*/
|
|
141
136
|
export declare function isWinFormPath(p: string): boolean;
|
|
142
137
|
/**
|
|
@@ -153,7 +148,7 @@ export declare function isWinFormPath(p: string): boolean;
|
|
|
153
148
|
export declare function expandHomeTilde(p: string, home: string): string;
|
|
154
149
|
export declare function isBlockedDevicePath(key: string): boolean;
|
|
155
150
|
/**
|
|
156
|
-
*
|
|
151
|
+
* Purely LEXICAL path normalization: collapse repeated separators, drop `.` segments, and
|
|
157
152
|
* resolve `..` against the preceding segment. No filesystem access, no cwd: this is a spelling
|
|
158
153
|
* normalizer, not a resolver. (Deliberately NOT `canonicalizeNewPath`'s job — that one walks the real
|
|
159
154
|
* filesystem precisely because folding `..` lexically across a SYMLINKED parent is unsound. Use this one
|
|
@@ -164,9 +159,8 @@ export declare function isBlockedDevicePath(key: string): boolean;
|
|
|
164
159
|
* three different ways. Callers that match a device list against a raw argument must normalize first or
|
|
165
160
|
* the list is a spelling filter rather than a device filter.
|
|
166
161
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* covered between them is now the contract here:
|
|
162
|
+
* Coverage — the union of every fold the callers need, held once here rather than re-implemented per
|
|
163
|
+
* consumer:
|
|
170
164
|
* · POSIX absolute — `/a/./b` → `/a/b`; `/..` is `/`, matching the kernel.
|
|
171
165
|
* · drive-letter (`C:\a\..\b`) and UNC (`\\host\share\..\x`) — prefix preserved, folding applies
|
|
172
166
|
* to the tail only, and the REJOIN uses the path's own native separator so one file cannot acquire
|
|
@@ -175,14 +169,14 @@ export declare function isBlockedDevicePath(key: string): boolean;
|
|
|
175
169
|
* `dev/zero` under some other directory is an ordinary file), and a leading `..` is KEPT rather than
|
|
176
170
|
* popped into nothing — dropping it would move the path.
|
|
177
171
|
*
|
|
178
|
-
* FAMILY-awareness is the
|
|
179
|
-
* win-form path; in a POSIX path it is an ordinary filename character, and
|
|
180
|
-
*
|
|
181
|
-
*
|
|
172
|
+
* FAMILY-awareness is the part a private copy tends to get wrong. A backslash is a separator only in a
|
|
173
|
+
* win-form path; in a POSIX path it is an ordinary filename character, and an unconditional `\`→`/`
|
|
174
|
+
* fold rewrites a POSIX file literally named `a\..\b` into a different location before the boundary
|
|
175
|
+
* check ever sees it — in both directions: false refusal and false allow.
|
|
182
176
|
*/
|
|
183
177
|
export declare function normalizeAbsPathLexically(p: string): string;
|
|
184
178
|
/**
|
|
185
|
-
* The READ-leg fold
|
|
179
|
+
* The READ-leg fold: removes `.` segments and duplicate
|
|
186
180
|
* separators ONLY — transformations that name the SAME file on every real filesystem, symlinks
|
|
187
181
|
* included. `..` is deliberately KEPT: `a/../x` and `x` are different files when `a` is a symlink,
|
|
188
182
|
* and this function's callers go on to OPEN the result (fileInfo/readText), not just to mint a
|
|
@@ -197,7 +191,7 @@ export declare function hasBinaryExtension(path: string): boolean;
|
|
|
197
191
|
export declare function imageMimeForRead(path: string): string | undefined;
|
|
198
192
|
/** Verify the raw bytes actually ARE the image format the extension claims (magic-number sniff). Extension alone
|
|
199
193
|
* is forgeable: a `.png` holding text / an empty file / a truncated blob would otherwise become a malformed
|
|
200
|
-
* `ImageContent` that makes the NEXT provider request fail (400). Pure — no I/O.
|
|
194
|
+
* `ImageContent` that makes the NEXT provider request fail (400). Pure — no I/O. */
|
|
201
195
|
export declare function imageMagicMatches(bytes: Uint8Array, mimeType: string): boolean;
|
|
202
196
|
/** One contiguous byte pattern that must appear at a fixed offset for its signature to match. */
|
|
203
197
|
export interface BinaryMagicSegment {
|
|
@@ -216,11 +210,11 @@ export interface BinaryMagicSignature {
|
|
|
216
210
|
readonly segments: readonly BinaryMagicSegment[];
|
|
217
211
|
}
|
|
218
212
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* (
|
|
222
|
-
*
|
|
223
|
-
*
|
|
213
|
+
* Magic-byte criteria for the formats {@link hasBinaryExtension} names. That list carries 103
|
|
214
|
+
* extensions; without this table only PDF (`pdfMagicMatches`, in the PDF lane) and the four API image
|
|
215
|
+
* formats ({@link imageMagicMatches}) have a content criterion, so for every other listed format the
|
|
216
|
+
* NAME is the only evidence available. Where the content IS reachable that leaves identification to
|
|
217
|
+
* the statistical fallback ({@link isBinaryContent}),
|
|
224
218
|
* which samples the first 8192 decoded characters and passes anything whose control-character ratio
|
|
225
219
|
* stays under 10% — a short archive/executable header followed by printable payload clears that bar
|
|
226
220
|
* and gets delivered as text.
|
|
@@ -249,36 +243,33 @@ export declare const BINARY_MAGIC_SIGNATURES: readonly BinaryMagicSignature[];
|
|
|
249
243
|
*/
|
|
250
244
|
export declare function binaryMagicFormat(bytes: Uint8Array): string | undefined;
|
|
251
245
|
/**
|
|
252
|
-
* Content sniff for binary data (
|
|
246
|
+
* Content sniff for binary data (CC `isBinaryContent` parity): a NUL byte,
|
|
253
247
|
* or more than 10% non-printable characters in the sample, means binary. Second layer behind
|
|
254
248
|
* {@link hasBinaryExtension} — catches extension-less or mis-named binaries (a `Dockerfile` that is really
|
|
255
249
|
* a compiled blob) that would otherwise be read as mojibake. Plain text (incl. UTF-8, tabs/newlines) passes.
|
|
256
250
|
*/
|
|
257
251
|
export declare function isBinaryContent(sample: string): boolean;
|
|
258
252
|
/**
|
|
259
|
-
* CC's own UNC/network-path predicate, verbatim (
|
|
260
|
-
* `n.startsWith("\\\\") || n.startsWith("//")`). Pure.
|
|
253
|
+
* CC's own UNC/network-path predicate, verbatim (`n.startsWith("\\\\") || n.startsWith("//")`). Pure.
|
|
261
254
|
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
* This function stays a pure spelling test with no verdict of its own.
|
|
255
|
+
* In CC this test is an EARLY ALLOW — it skips the binary-extension and device checks that follow it —
|
|
256
|
+
* and CC applies it to the NORMALIZED path, not the raw argument. See {@link canonicalizeTarget} for
|
|
257
|
+
* how sema mirrors that and for the two deliberate differences. This function stays a pure spelling
|
|
258
|
+
* test with no verdict of its own.
|
|
267
259
|
*/
|
|
268
260
|
export declare function isUncPath(path: string): boolean;
|
|
269
|
-
/**
|
|
270
|
-
*
|
|
271
|
-
*
|
|
261
|
+
/** The family-aware LEXICAL view of a possibly-relative spelling against a resolution base (no I/O,
|
|
262
|
+
* `.`/`..` folded via {@link normalizeAbsPathLexically}): the SECOND deny-judgment view. It asks
|
|
263
|
+
* "does the NAME land on a guarded path", complementing the canonical
|
|
272
264
|
* view's "does the TARGET" — a symlink AT a guarded spelling resolves away from it canonically,
|
|
273
265
|
* and only this view still names it. */
|
|
274
266
|
export declare function lexicalViewOf(spelled: string, base: string): string;
|
|
275
267
|
/**
|
|
276
|
-
* Resolve a model-supplied path to a canonical key AND enforce rootPath containment
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* `executionEnv` — see design/44 §5.)
|
|
268
|
+
* Resolve a model-supplied path to a canonical key AND enforce rootPath containment. Existing paths
|
|
269
|
+
* use `canonicalPath` (resolves symlinks, so the same file cannot get two keys). New paths
|
|
270
|
+
* canonicalize the **deepest existing ancestor** then rejoin the missing tail — a symlinked parent
|
|
271
|
+
* therefore cannot land the target outside root. Returns the canonical key, or a `path_not_in_root`
|
|
272
|
+
* violation. Defense-in-depth at the tool layer; NOT a substitute for a sandboxed `executionEnv`.
|
|
282
273
|
*/
|
|
283
274
|
export declare function resolveKey(env: ExecutionEnv, rootCanonical: string, path: string, signal?: AbortSignal, baseCwd?: string, additionalRootsCanonical?: readonly string[], exactFileReadExemption?: (canonicalKey: string) => boolean, readDeny?: {
|
|
284
275
|
matchTarget(canonicalKey: string, lexicalView?: string, opts?: {
|
|
@@ -299,9 +290,9 @@ export declare function resolveKey(env: ExecutionEnv, rootCanonical: string, pat
|
|
|
299
290
|
* doesn't exist yet, the deepest EXISTING ancestor is canonicalized then the missing tail rejoined —
|
|
300
291
|
* so a symlinked parent cannot land the target elsewhere). NO containment check — this is the pure
|
|
301
292
|
* canonicalization shared by `resolveKey` (which adds rootPath containment) and
|
|
302
|
-
* `createSensitivePathPolicy` (
|
|
293
|
+
* `createSensitivePathPolicy` (which matches the real target against a guarded list).
|
|
303
294
|
* A single source of truth for symlink resolution: a sensitive-path policy that matched the raw path
|
|
304
|
-
* instead of the real target would compare against the spelling, not the destination
|
|
295
|
+
* instead of the real target would compare against the spelling, not the destination.
|
|
305
296
|
*/
|
|
306
297
|
export declare function canonicalizeTarget(env: ExecutionEnv, path: string, signal?: AbortSignal, baseCwd?: string): Promise<{
|
|
307
298
|
ok: true;
|
|
@@ -315,16 +306,16 @@ export declare function canonicalizeTarget(env: ExecutionEnv, path: string, sign
|
|
|
315
306
|
/** Render a `FsViolation` as the model-facing tool error text (returned, never thrown — model retries). */
|
|
316
307
|
export declare function violationText(toolName: string, v: FsViolation): string;
|
|
317
308
|
/**
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
309
|
+
* Structured `details` for a rendered violation: non-`undefined` ONLY for the two codes with
|
|
310
|
+
* per-refusal coordinates worth counting (which canonical target, against which roots or which deny
|
|
311
|
+
* pattern). Designed to be passed straight as `errorResult`'s second argument by every renderer of a
|
|
312
|
+
* {@link resolveKey} violation — `undefined` keeps the detail-less shape for all other violation
|
|
313
|
+
* codes.
|
|
323
314
|
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
* `tool_end.structured` and the "count from fields" promise above
|
|
327
|
-
* `code`
|
|
315
|
+
* `type` is the WIRE discriminator: the card projector (`createStructuredProjector`,
|
|
316
|
+
* tool-output-projection.ts) keys on `details.type`, so without it the card never reaches
|
|
317
|
+
* `tool_end.structured` and the "count from fields" promise above holds for in-process readers only.
|
|
318
|
+
* `code` carries the same value for consumers that read it.
|
|
328
319
|
*/
|
|
329
320
|
export declare function violationDetails(v: FsViolation): {
|
|
330
321
|
type: "path_not_in_root";
|
|
@@ -338,7 +329,7 @@ export declare function violationDetails(v: FsViolation): {
|
|
|
338
329
|
pattern: string;
|
|
339
330
|
} | undefined;
|
|
340
331
|
/**
|
|
341
|
-
*
|
|
332
|
+
* Does `p` sit inside ANY of the canonical containment roots? Exposed for the Bash
|
|
342
333
|
* tool's post-`cd` disclosure note (fs-bash.ts): the note reuses the SAME segment-aware `within`
|
|
343
334
|
* the fence itself uses, so the disclosure and the fence can never disagree about what counts as
|
|
344
335
|
* "inside". Pure/lexical — callers own any canonicalization of `p` (the tracked cwd stays raw by
|
|
@@ -346,37 +337,34 @@ export declare function violationDetails(v: FsViolation): {
|
|
|
346
337
|
*/
|
|
347
338
|
export declare function withinAnyRoot(rootsCanonical: readonly string[], p: string): boolean;
|
|
348
339
|
/**
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
340
|
+
* The escape-hatch tail for the `path_not_in_root` refusal: like its three sibling hints below, it
|
|
341
|
+
* names a sanctioned next step, so the model does not oscillate between "the boundary is hard" and
|
|
342
|
+
* hunting for a way around it.
|
|
352
343
|
*
|
|
353
344
|
* The card names the SANCTIONED way out and nothing else: widening the declaration
|
|
354
|
-
* (`additionalDirectories` / read-only `additionalReadDirectories
|
|
355
|
-
*
|
|
345
|
+
* (`additionalDirectories` / read-only `additionalReadDirectories`), or the deployment-level
|
|
346
|
+
* `readFace: "open"` for reads.
|
|
356
347
|
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
* fence, and every shell call still passes the deployment's approval policy); it is simply no longer
|
|
366
|
-
* handed to the model inside a denial. What the card says now: this is denied, and here is the
|
|
367
|
-
* sanctioned way to be allowed.
|
|
348
|
+
* It deliberately does NOT name the shell as unconfined by this fence, even though that is a true
|
|
349
|
+
* statement rather than a recommendation. A live retest settled the question: a refused agent read
|
|
350
|
+
* such a sentence as the next step, went to the shell, and rewrote through it the very file the fence
|
|
351
|
+
* had just protected. A refusal card is a PROMPT surface — naming, at the moment of denial, a second
|
|
352
|
+
* tool that does not enforce the boundary is a recipe whatever its grammar, and the model cannot be
|
|
353
|
+
* relied on to hear "fact, not advice". The underlying fact is documented where humans read it (the
|
|
354
|
+
* shell is deliberately unconfined by this fence, and every shell call still passes the deployment's
|
|
355
|
+
* approval policy); it is simply not handed to the model inside a denial.
|
|
368
356
|
*/
|
|
369
357
|
export declare const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories \u2014 or additionalReadDirectories for read-only access; a deployment that wants reads open everywhere can declare readFace: \"open\" instead of listing directories.)";
|
|
370
358
|
/** inv 1 (read-before-edit): a file must have been read this task before it can be edited/overwritten.
|
|
371
359
|
* Message is CC 2.1.198 live-verbatim (all-tools-live-probe 2026-07-08 §2.1/§3.1/§5.1 — one message for
|
|
372
360
|
* Edit/Write/NotebookEdit: "before writing to it", not the old sema "before editing").
|
|
373
361
|
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
362
|
+
* CC parity (Edit and Write spell the read gate identically, `!p || p.isPartialView`): an
|
|
363
|
+
* AUTO-TRUNCATED read (the model asked for the file by default, no explicit offset/limit, and the token
|
|
364
|
+
* cap cut it anyway — {@link ReadEntry.isPartialView}) must NOT satisfy this gate. With `state.has(key)`
|
|
365
|
+
* alone as the whole check, the model could Write (full overwrite) or Edit a file having genuinely seen
|
|
366
|
+
* only its first page, silently destroying or blind-editing the unseen remainder. CC's own gate treats
|
|
367
|
+
* "never read" and "auto-truncated
|
|
380
368
|
* partial view" identically for BOTH tools — no softer rule for Edit (whose old_string match only touches
|
|
381
369
|
* a small snippet) versus Write (which replaces everything), so this doesn't carve one out either.
|
|
382
370
|
*
|
|
@@ -394,8 +382,7 @@ export declare function requireRead(state: ReadFileState, key: string): FsViolat
|
|
|
394
382
|
*/
|
|
395
383
|
export declare const OVERSIZE_READ_ESCAPE_HINT = "(This file is over the Read tool's whole-file byte cap, so a default Read is refused \u2014 read it in slices with explicit offset/limit to satisfy the read-first rule, or inspect/transform it with bash (e.g. `sed -n`, `grep`) instead.)";
|
|
396
384
|
/**
|
|
397
|
-
*
|
|
398
|
-
* the escape-hatch parenthetical for the OTHER shape of the read-before-edit refusal: the file was read,
|
|
385
|
+
* The escape-hatch parenthetical for the OTHER shape of the read-before-edit refusal: the file was read,
|
|
399
386
|
* but the read was AUTO-PAGINATED by the output token cap ({@link ReadEntry.isPartialView}), so only the
|
|
400
387
|
* first page was ever seen. The oversize branch above has named its escape route since the fidelity
|
|
401
388
|
* sweep; this branch named none — and it is the worse of the two, because the refusal's own advice
|
|
@@ -406,29 +393,29 @@ export declare const OVERSIZE_READ_ESCAPE_HINT = "(This file is over the Read to
|
|
|
406
393
|
*/
|
|
407
394
|
export declare const PARTIAL_VIEW_READ_ESCAPE_HINT = "(Your last Read of this file returned only a PARTIAL view \u2014 the output token cap paginated it, so a default Read will keep returning the same page. Re-read it with explicit offset/limit (start from the page marker's next-page hint) until you have seen the part you are about to change; an explicit slice that fits satisfies the read-first rule. Or inspect/transform it with bash (e.g. `sed -n`, `grep`) instead.)";
|
|
408
395
|
/**
|
|
409
|
-
*
|
|
410
|
-
*
|
|
396
|
+
* The escape-hatch parenthetical for the ONE shape of the read-first deadlock that nothing observable
|
|
397
|
+
* diagnoses: a file the Read tool REFUSES (binary /
|
|
411
398
|
* BOM-less UTF-16 / unknown encoding) can never satisfy read-before-write, and — unlike the oversize
|
|
412
399
|
* and partial-view shapes — leaves no trace the refusal renderer can stat for. It is therefore the
|
|
413
400
|
* renderer's FALLBACK hint (least specific, appended only when no sharper branch fired); appending it
|
|
414
401
|
* beside a sharper one would assert something false, since both of those shapes are files Read handled.
|
|
415
402
|
* Kept byte-identical to the sentence Write rendered inline before it moved here.
|
|
416
403
|
*
|
|
417
|
-
*
|
|
404
|
+
* The deadlock is a property of the READ GATE,
|
|
418
405
|
* not of Write — Edit and NotebookEdit hit the identical dead end and used to render the bare sentence
|
|
419
406
|
* with no way out named. All three now pass this one constant, which is why it is no longer named for
|
|
420
407
|
* the tool that happened to have it first: one sentence, three call sites, nothing to drift apart.
|
|
421
408
|
*/
|
|
422
409
|
export declare const READ_REFUSED_ESCAPE_HINT = "(If the Read tool refuses this file (binary/unknown encoding), overwrite or convert it with bash instead \u2014 e.g. `rm` + rewrite, or `iconv`.)";
|
|
423
410
|
/**
|
|
424
|
-
* Edit "no-op" guard (
|
|
411
|
+
* Edit "no-op" guard (CC FileEditTool parity): refuse an edit whose `old_string`
|
|
425
412
|
* equals `new_string` — without it the edit passes the match check and writes the file back unchanged (a
|
|
426
413
|
* silent no-op that wastes a turn and dirties mtime). Verbatim CC message so a CC-trained model recognizes it.
|
|
427
414
|
*/
|
|
428
415
|
export declare function checkNoChange(oldString: string, newString: string): FsViolation | undefined;
|
|
429
416
|
/** inv 2 (staleness): the file's current content hash must match what was recorded at read time.
|
|
430
417
|
*
|
|
431
|
-
*
|
|
418
|
+
* Takes `ReadEntry | undefined` and treats an ABSENT entry as stale (fail-closed). The three
|
|
432
419
|
* write-side callers prove the entry exists with `requireRead`, then re-read the file — an `await` — and
|
|
433
420
|
* only then reach here, so each was asserting a pre-await invariant across a suspension point with `!`.
|
|
434
421
|
* Nothing deletes read state mid-turn today (the compaction sweep runs at a turn boundary), so behaviour
|
|
@@ -441,7 +428,7 @@ export declare function countOccurrences(haystack: string, needle: string): numb
|
|
|
441
428
|
* Suggest a sibling filename for a missing path (CC `File does not exist… Did you mean X?` self-heal
|
|
442
429
|
* path, bundle :335883/:478419). Honest scope (1.253 双轨终审): this is the FALLBACK tier — an
|
|
443
430
|
* approximation of CC's Cxe same-directory scan plus a sema-added case-insensitive exact-name match
|
|
444
|
-
* (the case-typo class CC misses).
|
|
431
|
+
* (the case-typo class CC misses). Registered delta: CC's FIRST-tier suggester is a GATED
|
|
445
432
|
* sibling-tree REBASE (`join(cwd, relative(dirname(cwd), missing))`, only for a path under the cwd's
|
|
446
433
|
* parent but not under the cwd), NOT the "re-resolve the filename against cwd" this comment used to
|
|
447
434
|
* claim; the full geometry and the sema delta are recorded at the call site (fs-shared.ts
|
|
@@ -460,14 +447,14 @@ export declare function normalizeQuotes(s: string): string;
|
|
|
460
447
|
*/
|
|
461
448
|
export declare function resolveQuoteMatch(content: string, oldString: string): string | undefined;
|
|
462
449
|
/**
|
|
463
|
-
*
|
|
450
|
+
* CC's forgiving-match layers 3+4: resolve `oldString` against `content` across the
|
|
464
451
|
* escape/literal spelling boundary. Returns the ACTUAL span present in `content` (which is what must be
|
|
465
452
|
* replaced), or `undefined` when neither spelling is present. Pure — call only after an exact match and
|
|
466
453
|
* the curly-quote layer have both missed.
|
|
467
454
|
*/
|
|
468
455
|
export declare function resolveEscapeMatch(content: string, oldString: string): string | undefined;
|
|
469
456
|
/**
|
|
470
|
-
*
|
|
457
|
+
* CC `YZu` verbatim: rewrite `newString` into the SAME spelling the matched
|
|
471
458
|
* span uses. Without this, a match found through {@link resolveEscapeMatch} would replace an escaped
|
|
472
459
|
* span with a literal-character replacement (or vice versa) and silently change the file's encoding
|
|
473
460
|
* convention at that spot. Hex case follows what the file itself used, per-character where known and by
|
|
@@ -477,7 +464,7 @@ export declare function adaptNewStringEscapes(oldString: string, matchedOld: str
|
|
|
477
464
|
/** CC `KZu` (@366558): the old_string shapes for which the escape layers above were even attempted —
|
|
478
465
|
* gates the explanatory note appended to a no-match refusal so it is never a false claim. */
|
|
479
466
|
export declare function escapeMatchWasAttempted(oldString: string): boolean;
|
|
480
|
-
/**
|
|
467
|
+
/** CC `KZu`'s note, verbatim. */
|
|
481
468
|
export declare const ESCAPE_MATCH_MISS_NOTE = "\n(note: Edit also tried swapping \\uXXXX escapes and their characters; neither form matched, so the mismatch is likely elsewhere in old_string. Re-read the file and copy the exact surrounding text.)";
|
|
482
469
|
/**
|
|
483
470
|
* When the quote-forgiving match resolved to a curly-quoted file span, convert the straight quotes in
|