@sema-agent/core 7.8.0 → 7.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -1
- package/dist/agents/subagent.d.ts +1 -1
- package/dist/core/ask-origin.d.ts +16 -1
- package/dist/core/ask-origin.js +5 -1
- package/dist/core/checkpoint-store.d.ts +36 -35
- package/dist/core/effective-path-target.d.ts +43 -0
- package/dist/core/effective-path-target.js +56 -0
- package/dist/core/engine-notice.d.ts +8 -0
- package/dist/core/fs-write-gate-policy.js +2 -1
- package/dist/core/gate-lanes.d.ts +1 -0
- package/dist/core/gate-lanes.js +60 -28
- package/dist/core/gate-outcome.d.ts +8 -4
- package/dist/core/gate-outcome.js +2 -1
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +66 -32
- package/dist/core/mcp-injection-drop.d.ts +74 -0
- package/dist/core/mcp-injection-drop.js +27 -0
- package/dist/core/permission-rule-consent.d.ts +29 -36
- package/dist/core/permission-rule-consent.js +110 -60
- package/dist/core/permission-rule-model.d.ts +311 -96
- package/dist/core/permission-rule-model.js +309 -50
- package/dist/core/permission-rule-org.d.ts +19 -12
- package/dist/core/permission-rule-org.js +13 -16
- package/dist/core/permission-rule-provider.d.ts +10 -6
- package/dist/core/permission-rule-provider.js +12 -8
- package/dist/core/permission-rule-session.d.ts +7 -6
- package/dist/core/permission-rule-session.js +29 -10
- package/dist/core/permission-rule-store.d.ts +40 -18
- package/dist/core/permission-rule-store.js +68 -42
- package/dist/core/permission-rule-sync.d.ts +9 -3
- package/dist/core/permission-rule-sync.js +29 -25
- package/dist/core/permission-rule-syntax.d.ts +30 -0
- package/dist/core/permission-rule-syntax.js +44 -0
- package/dist/core/permission-rules.d.ts +56 -33
- package/dist/core/permission-rules.js +88 -55
- package/dist/core/persisted-rule-arms.d.ts +61 -0
- package/dist/core/persisted-rule-arms.js +50 -0
- package/dist/core/runner/active-skill-scope.js +2 -1
- package/dist/core/runner/contracts.d.ts +21 -2
- package/dist/core/runner/permission-rule-lanes.d.ts +42 -27
- package/dist/core/runner/permission-rule-lanes.js +25 -21
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-safety-scan.js +8 -2
- package/dist/core/runner/prepare-task.js +1 -1
- package/dist/core/runner/resume-admission.d.ts +53 -0
- package/dist/core/runner/resume-admission.js +83 -0
- package/dist/core/runner/resume-apply.d.ts +50 -0
- package/dist/core/runner/resume-apply.js +184 -0
- package/dist/core/runner/resume-checkpoint-screen.d.ts +18 -0
- package/dist/core/runner/resume-checkpoint-screen.js +108 -0
- package/dist/core/runner/resume-claim.d.ts +32 -0
- package/dist/core/runner/resume-claim.js +27 -0
- package/dist/core/runner/resume-internals-and-config.d.ts +33 -0
- package/dist/core/runner/resume-internals-and-config.js +50 -0
- package/dist/core/runner/resume-policy-outcome.d.ts +31 -0
- package/dist/core/runner/resume-policy-outcome.js +127 -0
- package/dist/core/runner/resume-preflight.d.ts +40 -0
- package/dist/core/runner/resume-preflight.js +122 -0
- package/dist/core/runner/resume-review-outcome.d.ts +30 -0
- package/dist/core/runner/resume-review-outcome.js +88 -0
- package/dist/core/runner/run-harness-handlers.js +1 -1
- package/dist/core/runner/runtask.d.ts +20 -0
- package/dist/core/runner/runtask.js +85 -734
- package/dist/core/runner/session-rule-policy.js +2 -1
- package/dist/core/runner/tool-end-body.d.ts +12 -5
- package/dist/core/runner/tool-end-body.js +5 -5
- package/dist/core/runner/tool-face-overlay.js +22 -3
- package/dist/core/runner/tool-output-projection.d.ts +9 -6
- package/dist/core/runner/tool-output-projection.js +1 -18
- package/dist/core/runner/turn-attachments.d.ts +2 -2
- package/dist/core/sensitive-path-policy.js +5 -3
- package/dist/core/shell-lexer.d.ts +47 -0
- package/dist/core/shell-lexer.js +478 -0
- package/dist/core/shell-scan.d.ts +60 -0
- package/dist/core/shell-scan.js +183 -0
- package/dist/core/shell-wrapper-table.d.ts +297 -0
- package/dist/core/shell-wrapper-table.js +58 -0
- package/dist/core/store-contracts/permission-rule-sync-contract.js +29 -10
- package/dist/core/tool-catalog-entries.js +6 -6
- package/dist/core/tool-face.d.ts +80 -4
- package/dist/core/tool-face.js +10 -0
- package/dist/core/tool-policy.d.ts +1 -6
- package/dist/core/tool-registry.d.ts +8 -11
- package/dist/core/tool-registry.js +5 -2
- package/dist/core/tool-roster.d.ts +20 -2
- package/dist/core/tool-roster.js +36 -8
- package/dist/index.d.ts +7 -4
- package/dist/index.js +6 -3
- package/dist/stores/file/checkpoint-store.js +1 -1
- package/dist/stores/file/permission-rule-store.d.ts +28 -23
- package/dist/stores/file/permission-rule-store.js +64 -16
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
- package/dist/tools/fs/fs-pdf.d.ts +1 -1
- package/dist/tools/fs/fs-search-tools.d.ts +3 -2
- package/dist/tools/fs/fs-search-tools.js +17 -9
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/fs/notebook.d.ts +1 -1
- package/dist/tools/fs/safety.d.ts +1 -1
- package/dist/tools/fs/search.d.ts +0 -8
- package/dist/tools/fs/search.js +0 -23
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +99 -11
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
* design/179 §5/§8 / design/389 — the file backend of the permission-rule store's DURABLE partition (one
|
|
3
3
|
* bucket file per owner), plus the file home of the ORG partition's durable state (`org.json`).
|
|
4
4
|
*
|
|
5
|
-
* ## Schema
|
|
5
|
+
* ## Schema 4 and the one-time migration
|
|
6
6
|
*
|
|
7
7
|
* Pre-389 files (schema 1/2) kept the org last-known-good snapshot as an `org` block INSIDE each
|
|
8
|
-
* principal's bucket file — deployment-wide policy stored per person. Schema 3
|
|
9
|
-
* state lives in one `org.json` beside the buckets. `
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
8
|
+
* principal's bucket file — deployment-wide policy stored per person. Schema 3 dropped that block; the org
|
|
9
|
+
* state lives in one `org.json` beside the buckets. Schema 4 adds the `behavior` member to every rule row,
|
|
10
|
+
* tombstone and quarantine row (the rule identity is the (behavior, text, scope) triple): a schema-3 row has
|
|
11
|
+
* none, and every row a schema-3 engine wrote was an allow, so the migration stamps `"allow"` on each — the
|
|
12
|
+
* ONE reading of the pre-field row (`readRowBehavior`), applied once, here, never at a read face.
|
|
13
|
+
* `FileDurableRulePartitionProvider` migrates every legacy bucket file (1, 2 or 3) ONCE at construction
|
|
14
|
+
* (under the writer lock: the migration is a write, and a live older writer over the same directory is
|
|
15
|
+
* refused rather than raced), folding a 1/2 file's `org` block into `org.json` by the anti-rollback rule
|
|
16
|
+
* (the highest high-water wins; an equal revision with different content keeps what is already installed
|
|
17
|
+
* and discloses). After that the reader accepts schema 4 ONLY — a 1/2/3/5 file is refused whole, loudly
|
|
18
|
+
* (zero rules + disclosure). There is no second read path for the old shapes: the migrator is the only
|
|
19
|
+
* code that reads them, and it runs once.
|
|
16
20
|
*
|
|
17
21
|
* ## Why this store builds its own symlink refusal
|
|
18
22
|
*
|
|
@@ -55,23 +59,23 @@
|
|
|
55
59
|
* and is not: it detects corruption, and it does not detect an editor, because an editor recomputes it.
|
|
56
60
|
* For this backend the trust is host = user, exactly as for the settings file it mirrors.
|
|
57
61
|
*/
|
|
58
|
-
import type {
|
|
59
|
-
import type { DurableRulePartition, DurableRulePartitionProvider, PermissionRuleWriter, QuarantinedRuleAdd, RuleOwner, RuleSyncFrontier,
|
|
62
|
+
import type { PersistedRule, RuleTombstone } from "../../core/permission-rule-model.js";
|
|
63
|
+
import type { DurableRulePartition, DurableRulePartitionProvider, PermissionRuleWriter, QuarantinedRuleAdd, RuleOwner, RuleSyncFrontier, StoredRules, WritableDurableRulePartition } from "../../core/permission-rule-store.js";
|
|
60
64
|
import { PERMISSION_RULE_WRITER } from "../../core/permission-rule-store.js";
|
|
61
65
|
import type { OrgRuleStatePersistence, PersistedOrgRuleState } from "../../core/permission-rule-org.js";
|
|
62
66
|
import type { StoreDurability, StoreFidelity } from "../../core/checkpoint-store.js";
|
|
63
67
|
/**
|
|
64
68
|
* The on-disk shape of one bucket. `schemaVersion` is the only field a future reader may rely on before
|
|
65
|
-
* validating. Schema
|
|
66
|
-
*
|
|
67
|
-
* The org state is NOT here — see {@link OrgStateFile}.
|
|
69
|
+
* validating. Schema 4: the five core fields plus the OPTIONAL `sync` block (observation vector + round
|
|
70
|
+
* timestamp) and `quarantined` area (rows moved out of the live view, bytes preserved); every rule,
|
|
71
|
+
* tombstone and quarantine row carries its `behavior`. The org state is NOT here — see {@link OrgStateFile}.
|
|
68
72
|
*/
|
|
69
73
|
interface RuleFile {
|
|
70
|
-
schemaVersion:
|
|
74
|
+
schemaVersion: 4;
|
|
71
75
|
actor: string;
|
|
72
76
|
counter: number;
|
|
73
77
|
rev: number;
|
|
74
|
-
rules:
|
|
78
|
+
rules: PersistedRule[];
|
|
75
79
|
tombstones: RuleTombstone[];
|
|
76
80
|
sync?: {
|
|
77
81
|
observedVector?: RuleSyncFrontier;
|
|
@@ -88,10 +92,10 @@ interface OrgStateFile {
|
|
|
88
92
|
}
|
|
89
93
|
/** The org partition's durable home in a rule directory. */
|
|
90
94
|
export declare const ORG_STATE_FILE = "org.json";
|
|
91
|
-
/** Written once the directory is known to hold only schema-
|
|
92
|
-
* scan for good: a schema 1/2 bucket that appears AFTERWARDS (an older engine writing after the
|
|
95
|
+
/** Written once the directory is known to hold only schema-4 buckets. Its presence ends the legacy
|
|
96
|
+
* scan for good: a schema 1/2/3 bucket that appears AFTERWARDS (an older engine writing after the
|
|
93
97
|
* cutover) is refused by the reader on every later construction too, never re-admitted by a restart. */
|
|
94
|
-
export declare const
|
|
98
|
+
export declare const SCHEMA_MARKER_FILE = ".rule-schema-4";
|
|
95
99
|
/** The anti-rollback contract's DESIGNED refusal (a lower high-water mark, or different content under
|
|
96
100
|
* the same revision) — distinguished by class from every other install failure, because the two are
|
|
97
101
|
* handled in opposite directions by the migration: a refusal means the block is superseded and may be
|
|
@@ -148,7 +152,7 @@ declare class FileDurableRulePartition implements WritableDurableRulePartition {
|
|
|
148
152
|
*/
|
|
149
153
|
private screenDurableRows;
|
|
150
154
|
private write;
|
|
151
|
-
list(): Promise<
|
|
155
|
+
list(): Promise<StoredRules>;
|
|
152
156
|
/** The quarantine area (design/182 §5.2/§8.3): introspection only — never part of `list()`. */
|
|
153
157
|
quarantined(): Promise<QuarantinedRuleAdd[]>;
|
|
154
158
|
/**
|
|
@@ -209,7 +213,8 @@ export declare class FileDurableRulePartitionProvider implements DurableRulePart
|
|
|
209
213
|
dataRoot?: string;
|
|
210
214
|
});
|
|
211
215
|
/**
|
|
212
|
-
* Fold every legacy bucket file in `dir` into schema
|
|
216
|
+
* Fold every legacy bucket file in `dir` into schema 4 (stamping `behavior: "allow"` on every pre-field
|
|
217
|
+
* row — the one pre-field reading) and a 1/2 file's `org` block into `org.json`. A no-op on a
|
|
213
218
|
* directory with no legacy file (zero writes, the lock untouched — the second construction over a
|
|
214
219
|
* migrated directory is byte-silent). A legacy file whose checksum or shape does not hold is NOT
|
|
215
220
|
* migrated: its bytes stay where they are for a person to look at, and the reader keeps refusing it
|
|
@@ -220,10 +225,10 @@ export declare class FileDurableRulePartitionProvider implements DurableRulePart
|
|
|
220
225
|
* shape — the old engine's buckets are refused whole until they are migrated.
|
|
221
226
|
*/
|
|
222
227
|
private migrateLegacyBuckets;
|
|
223
|
-
/** Publish the schema-
|
|
228
|
+
/** Publish the schema-4 completion marker (idempotent, atomic, no lock: its content never changes and
|
|
224
229
|
* a concurrent writer of the same bytes is harmless). A failure to write it is disclosed, not thrown —
|
|
225
230
|
* the next construction simply scans again, which is the safe direction. */
|
|
226
|
-
private
|
|
231
|
+
private writeSchemaMarker;
|
|
227
232
|
/** The org partition's durable home in this directory (design/182 §7.4 contract; `org.json`). Shares
|
|
228
233
|
* this provider's writer lock — an install is a write. */
|
|
229
234
|
orgStatePersistence(): OrgRuleStatePersistence;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { closeSync, constants as FS, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { createHash, randomBytes } from "node:crypto";
|
|
4
|
+
import { isRuleBehavior, readRowBehavior } from "../../core/permission-rule-model.js";
|
|
4
5
|
import { PERMISSION_RULE_WRITER, applySyncJoin, applyTombstones, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, foldDelta, } from "../../core/permission-rule-store.js";
|
|
5
6
|
import { canonicalize } from "../../core/canonical-json.js";
|
|
6
7
|
import { BootLock } from "./fs-atomic.js";
|
|
7
8
|
import { assertAdoptionBootGate } from "./adoption/marker.js";
|
|
8
9
|
function isRuleFile(v) {
|
|
9
10
|
const p = v;
|
|
10
|
-
return typeof p === "object" && p !== null && p.schemaVersion ===
|
|
11
|
+
return typeof p === "object" && p !== null && p.schemaVersion === 4 && Array.isArray(p.rules) && Array.isArray(p.tombstones) && typeof p.rev === "number" && (p.quarantined === undefined || Array.isArray(p.quarantined));
|
|
11
12
|
}
|
|
12
|
-
function
|
|
13
|
+
function isOlderRuleFile(v) {
|
|
13
14
|
const p = v;
|
|
14
|
-
return typeof p === "object" && p !== null && (p.schemaVersion === 1 || p.schemaVersion === 2) && Array.isArray(p.rules) && Array.isArray(p.tombstones) && typeof p.rev === "number";
|
|
15
|
+
return typeof p === "object" && p !== null && (p.schemaVersion === 1 || p.schemaVersion === 2 || p.schemaVersion === 3) && Array.isArray(p.rules) && Array.isArray(p.tombstones) && typeof p.rev === "number";
|
|
15
16
|
}
|
|
16
17
|
function isOrgStateFile(v) {
|
|
17
18
|
const p = v;
|
|
@@ -24,7 +25,7 @@ function jsonForm(v) {
|
|
|
24
25
|
return JSON.parse(JSON.stringify(v));
|
|
25
26
|
}
|
|
26
27
|
export const ORG_STATE_FILE = "org.json";
|
|
27
|
-
export const
|
|
28
|
+
export const SCHEMA_MARKER_FILE = ".rule-schema-4";
|
|
28
29
|
export class OrgRevisionRefusal extends Error {
|
|
29
30
|
}
|
|
30
31
|
const LOCAL_OWNER_FILE = "local-owner.json";
|
|
@@ -158,8 +159,8 @@ class FileDurableRulePartition {
|
|
|
158
159
|
}
|
|
159
160
|
if (!isRuleFile(parsed)) {
|
|
160
161
|
const sv = schemaVersionWord(parsed);
|
|
161
|
-
const why = sv === 1 || sv === 2
|
|
162
|
-
? `${this.file} is a schema ${String(sv)} bucket that was not migrated to schema
|
|
162
|
+
const why = sv === 1 || sv === 2 || sv === 3
|
|
163
|
+
? `${this.file} is a schema ${String(sv)} bucket that was not migrated to schema 4 (written by an older engine after this directory was migrated?); refusing the whole file and loading zero rules`
|
|
163
164
|
: `${this.file} does not carry a readable rule-file shape; refusing the whole file and loading zero rules`;
|
|
164
165
|
this.disclose(why);
|
|
165
166
|
return { unreadable: why };
|
|
@@ -170,6 +171,19 @@ class FileDurableRulePartition {
|
|
|
170
171
|
this.disclose(why);
|
|
171
172
|
return { unreadable: why };
|
|
172
173
|
}
|
|
174
|
+
const rowKinds = [
|
|
175
|
+
["rule", parsed.rules],
|
|
176
|
+
["tombstone", parsed.tombstones],
|
|
177
|
+
["quarantined", parsed.quarantined ?? []],
|
|
178
|
+
];
|
|
179
|
+
for (const [kind, rows] of rowKinds) {
|
|
180
|
+
const bad = rows.findIndex((r) => !isRuleBehavior(r?.behavior));
|
|
181
|
+
if (bad !== -1) {
|
|
182
|
+
const why = `${this.file} carries a ${kind} row (#${bad}) whose behavior word is outside deny/ask/allow — a schema 4 row must name its behavior; refusing the whole file and loading zero rules`;
|
|
183
|
+
this.disclose(why);
|
|
184
|
+
return { unreadable: why };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
173
187
|
return { file: parsed };
|
|
174
188
|
}
|
|
175
189
|
disclose(message) {
|
|
@@ -218,7 +232,7 @@ class FileDurableRulePartition {
|
|
|
218
232
|
throw new Error(`refusing to write the permission-rule store while it cannot be read: ${r.unreadable}`);
|
|
219
233
|
}
|
|
220
234
|
if ("absent" in r) {
|
|
221
|
-
return { schemaVersion:
|
|
235
|
+
return { schemaVersion: 4, actor: `file-${randomBytes(6).toString("hex")}`, counter: 0, rev: 0, rules: [], tombstones: [] };
|
|
222
236
|
}
|
|
223
237
|
return {
|
|
224
238
|
schemaVersion: r.file.schemaVersion,
|
|
@@ -324,7 +338,7 @@ export class FileDurableRulePartitionProvider {
|
|
|
324
338
|
migrateLegacyBuckets() {
|
|
325
339
|
if (!existsSync(this.dir))
|
|
326
340
|
return;
|
|
327
|
-
if (existsSync(join(this.dir,
|
|
341
|
+
if (existsSync(join(this.dir, SCHEMA_MARKER_FILE)))
|
|
328
342
|
return;
|
|
329
343
|
try {
|
|
330
344
|
assertSafeDir(this.dir);
|
|
@@ -359,7 +373,7 @@ export class FileDurableRulePartitionProvider {
|
|
|
359
373
|
legacy.push({ name, file: read });
|
|
360
374
|
}
|
|
361
375
|
if (legacy.length === 0) {
|
|
362
|
-
this.
|
|
376
|
+
this.writeSchemaMarker();
|
|
363
377
|
return;
|
|
364
378
|
}
|
|
365
379
|
const orgHome = this.fileOrgPersistence();
|
|
@@ -380,12 +394,18 @@ export class FileDurableRulePartitionProvider {
|
|
|
380
394
|
}
|
|
381
395
|
}
|
|
382
396
|
}
|
|
383
|
-
const
|
|
397
|
+
const stamped = stampRowBehaviors(rest);
|
|
398
|
+
if (stamped === undefined) {
|
|
399
|
+
complete = false;
|
|
400
|
+
this.onError?.(`bucket ${name} left untouched: a row carries a behavior word outside deny/ask/allow, which no engine version wrote; the file is not migrated`);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const next = { ...stamped, schemaVersion: 4 };
|
|
384
404
|
atomicPublish(this.dir, join(this.dir, name), JSON.stringify({ ...next, checksum: checksumOf(next) }, null, 2));
|
|
385
|
-
this.onError?.(`migrated ${name} to rule-file schema
|
|
405
|
+
this.onError?.(`migrated ${name} to rule-file schema 4${org !== undefined ? ` (its org block moved to ${ORG_STATE_FILE})` : ""}`);
|
|
386
406
|
}
|
|
387
407
|
if (complete)
|
|
388
|
-
this.
|
|
408
|
+
this.writeSchemaMarker();
|
|
389
409
|
}
|
|
390
410
|
finally {
|
|
391
411
|
if (!held) {
|
|
@@ -394,12 +414,12 @@ export class FileDurableRulePartitionProvider {
|
|
|
394
414
|
}
|
|
395
415
|
}
|
|
396
416
|
}
|
|
397
|
-
|
|
417
|
+
writeSchemaMarker() {
|
|
398
418
|
try {
|
|
399
|
-
atomicPublish(this.dir, join(this.dir,
|
|
419
|
+
atomicPublish(this.dir, join(this.dir, SCHEMA_MARKER_FILE), JSON.stringify({ schemaVersion: 4 }));
|
|
400
420
|
}
|
|
401
421
|
catch (err) {
|
|
402
|
-
this.onError?.(`could not write ${
|
|
422
|
+
this.onError?.(`could not write ${SCHEMA_MARKER_FILE}: ${err.message}; the legacy scan will run again next construction`);
|
|
403
423
|
}
|
|
404
424
|
}
|
|
405
425
|
orgStatePersistence() {
|
|
@@ -449,6 +469,34 @@ export class FileDurableRulePartitionProvider {
|
|
|
449
469
|
this.lock = undefined;
|
|
450
470
|
}
|
|
451
471
|
}
|
|
472
|
+
function stampRowBehaviors(body) {
|
|
473
|
+
const rules = [];
|
|
474
|
+
for (const r of body.rules) {
|
|
475
|
+
const behavior = readRowBehavior(r.behavior);
|
|
476
|
+
if (behavior === undefined)
|
|
477
|
+
return undefined;
|
|
478
|
+
rules.push({ ...r, behavior });
|
|
479
|
+
}
|
|
480
|
+
const tombstones = [];
|
|
481
|
+
for (const t of body.tombstones) {
|
|
482
|
+
const behavior = readRowBehavior(t.behavior);
|
|
483
|
+
if (behavior === undefined)
|
|
484
|
+
return undefined;
|
|
485
|
+
tombstones.push({ ...t, behavior });
|
|
486
|
+
}
|
|
487
|
+
let quarantined;
|
|
488
|
+
if (body.quarantined !== undefined) {
|
|
489
|
+
quarantined = [];
|
|
490
|
+
for (const q of body.quarantined) {
|
|
491
|
+
const behavior = readRowBehavior(q.behavior);
|
|
492
|
+
if (behavior === undefined)
|
|
493
|
+
return undefined;
|
|
494
|
+
quarantined.push({ ...q, behavior });
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
const { rules: _r, tombstones: _t, quarantined: _q, ...rest } = body;
|
|
498
|
+
return { ...rest, rules, tombstones, ...(quarantined !== undefined ? { quarantined } : {}) };
|
|
499
|
+
}
|
|
452
500
|
function readLegacyBucket(file) {
|
|
453
501
|
let raw;
|
|
454
502
|
try {
|
|
@@ -467,7 +515,7 @@ function readLegacyBucket(file) {
|
|
|
467
515
|
catch {
|
|
468
516
|
return undefined;
|
|
469
517
|
}
|
|
470
|
-
if (!
|
|
518
|
+
if (!isOlderRuleFile(parsed))
|
|
471
519
|
return undefined;
|
|
472
520
|
const { checksum, ...body } = parsed;
|
|
473
521
|
return checksum === checksumOf(body) ? parsed : undefined;
|
|
@@ -80,7 +80,7 @@ export interface LeadingCommandNameOptions {
|
|
|
80
80
|
* name instead of refusing it.
|
|
81
81
|
*
|
|
82
82
|
* OPT-IN, and used by ONE family of callers: the persisted permission-RULE lane
|
|
83
|
-
* ({@link import("../../core/permission-rule-model.js").
|
|
83
|
+
* ({@link import("../../core/permission-rule-model.js").parseRuleText} and its matcher), where
|
|
84
84
|
* both sides of the comparison are literal text a person read on an approval card. The refusal this
|
|
85
85
|
* option lifts exists for the argv[0]-NAME filters (the read-only allowlist, the coarse
|
|
86
86
|
* command-name policy, the skill specifier): those compare a bare token against a name set, and a
|
|
@@ -29,7 +29,7 @@ type ReadPdfReturn = string | {
|
|
|
29
29
|
* downsampler seam then applies the same pipeline as the Read image branch). poppler absent → HONEST
|
|
30
30
|
* degradation with the working alternative, never a fake success.
|
|
31
31
|
* Wire discipline: the base64 payload rides `details.file` only while ≤48K chars (same OMIT rule as the
|
|
32
|
-
* image variant — a bigger card would be dropped whole by the
|
|
32
|
+
* image variant — a bigger card would be dropped whole by the card projector's 4×cap and blind the shell).
|
|
33
33
|
* structured-card audit item B (2026-08-05): that budget management assumed `"document"` was on `CC_DETAIL_TYPES`, which
|
|
34
34
|
* it was not — every `document` card, sized or not, was dropped at the gate, making the 48K rule dead
|
|
35
35
|
* code and the pages/fallback-level disclosure invisible to a host. The word is registered now.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
2
|
import type { ReadDenyMatcher } from "./read-deny.js";
|
|
3
3
|
import type { ReadFace } from "./read-face.js";
|
|
4
|
+
import type { CwdRef } from "./fs-shared.js";
|
|
4
5
|
/**
|
|
5
6
|
* The Grep card's details assembly, PURE over the engine text ({mode, offset} from the request) —
|
|
6
7
|
* exported so the text→structured mapping is pinnable with synthetic texts (the byte-truncation and
|
|
@@ -10,5 +11,5 @@ import type { ReadFace } from "./read-face.js";
|
|
|
10
11
|
* this seam the honesty boundary.
|
|
11
12
|
*/
|
|
12
13
|
export declare function grepDetailFields(text: string, mode: "files_with_matches" | "content" | "count", offset?: number, structuredRows?: readonly import("./search.js").GrepRow[]): Record<string, unknown>;
|
|
13
|
-
export declare function createGrepTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ReadFace): AgentTool;
|
|
14
|
-
export declare function createGlobTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ReadFace): AgentTool;
|
|
14
|
+
export declare function createGrepTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ReadFace, cwdRef?: CwdRef): AgentTool;
|
|
15
|
+
export declare function createGlobTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ReadFace, cwdRef?: CwdRef): AgentTool;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { defineTool, errorResult } from "../../core/tools.js";
|
|
3
3
|
import { resolveKey, violationText, violationDetails } from "./safety.js";
|
|
4
|
-
import { runGrepDetailed, runGlobDetailed,
|
|
4
|
+
import { runGrepDetailed, runGlobDetailed, invalidGlobTokens } from "./search.js";
|
|
5
5
|
import { toolFace } from "../../core/tool-catalog-entries.js";
|
|
6
|
+
import { pathTargetBaseOf, splitAbsoluteGlobPattern } from "../../core/effective-path-target.js";
|
|
7
|
+
import { pathTargetValue } from "../../core/tool-registry.js";
|
|
6
8
|
export function grepDetailFields(text, mode, offset, structuredRows) {
|
|
7
9
|
const structuredIdentityComplete = structuredRows !== undefined && structuredRows.every((r) => r.path !== undefined || r.text === "--");
|
|
8
10
|
const structuredPaths = structuredIdentityComplete && structuredRows !== undefined ? [...new Set(structuredRows.filter((r) => r.path !== undefined).map((r) => r.path))] : undefined;
|
|
@@ -72,7 +74,8 @@ export function grepDetailFields(text, mode, offset, structuredRows) {
|
|
|
72
74
|
}
|
|
73
75
|
return detailFields;
|
|
74
76
|
}
|
|
75
|
-
export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, readFace) {
|
|
77
|
+
export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, readFace, cwdRef) {
|
|
78
|
+
const declaredTarget = toolFace("grep").pathTarget;
|
|
76
79
|
return defineTool({
|
|
77
80
|
name: "Grep",
|
|
78
81
|
...toolFace("grep"),
|
|
@@ -136,9 +139,11 @@ export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, re
|
|
|
136
139
|
`Close the bracket/brace, or drop \`glob\` and filter with \`path\`/\`type\` instead.`);
|
|
137
140
|
}
|
|
138
141
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
const baseCwd = pathTargetBaseOf(declaredTarget, { root: rootCanonical, cwd: cwdRef?.current });
|
|
143
|
+
const scopeSpelling = declaredTarget === undefined ? undefined : pathTargetValue(a, declaredTarget);
|
|
144
|
+
let scoped = scopeSpelling;
|
|
145
|
+
if (scopeSpelling !== undefined) {
|
|
146
|
+
const r = await resolveKey(env, rootCanonical, scopeSpelling, ctx.signal, baseCwd, additionalRoots, undefined, readDeny, readFace);
|
|
142
147
|
if (!r.ok)
|
|
143
148
|
return errorResult(violationText("Grep", r.violation), violationDetails(r.violation));
|
|
144
149
|
scoped = r.key;
|
|
@@ -146,7 +151,7 @@ export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, re
|
|
|
146
151
|
if (!info.ok) {
|
|
147
152
|
const ex = await env.exists(scoped, ctx.signal);
|
|
148
153
|
if (!(ex.ok && !ex.value)) {
|
|
149
|
-
return errorResult(`Error (Grep): cannot determine the type of "${
|
|
154
|
+
return errorResult(`Error (Grep): cannot determine the type of "${scopeSpelling.slice(0, 300)}" (stat failed: ${info.error.message.slice(0, 200)}); refusing to search it — if it is a FIFO/socket/device, reading it would hang. Retry if this was transient.`);
|
|
150
155
|
}
|
|
151
156
|
}
|
|
152
157
|
}
|
|
@@ -172,7 +177,8 @@ export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, re
|
|
|
172
177
|
},
|
|
173
178
|
});
|
|
174
179
|
}
|
|
175
|
-
export function createGlobTool(env, rootCanonical, additionalRoots, readDeny, readFace) {
|
|
180
|
+
export function createGlobTool(env, rootCanonical, additionalRoots, readDeny, readFace, cwdRef) {
|
|
181
|
+
const declaredTarget = toolFace("glob").pathTarget;
|
|
176
182
|
return defineTool({
|
|
177
183
|
name: "Glob",
|
|
178
184
|
...toolFace("glob"),
|
|
@@ -195,7 +201,8 @@ export function createGlobTool(env, rootCanonical, additionalRoots, readDeny, re
|
|
|
195
201
|
max_results: Type.Optional(Type.Number({ description: "Cap results (default 500)." })),
|
|
196
202
|
}),
|
|
197
203
|
execute: async (args, ctx) => {
|
|
198
|
-
let
|
|
204
|
+
let pattern = args.pattern;
|
|
205
|
+
let path = declaredTarget === undefined ? undefined : pathTargetValue(args, declaredTarget);
|
|
199
206
|
const { max_results } = args;
|
|
200
207
|
if (path === undefined) {
|
|
201
208
|
const split = splitAbsoluteGlobPattern(pattern);
|
|
@@ -204,9 +211,10 @@ export function createGlobTool(env, rootCanonical, additionalRoots, readDeny, re
|
|
|
204
211
|
pattern = split.relativePattern;
|
|
205
212
|
}
|
|
206
213
|
}
|
|
214
|
+
const baseCwd = pathTargetBaseOf(declaredTarget, { root: rootCanonical, cwd: cwdRef?.current });
|
|
207
215
|
let scoped = path;
|
|
208
216
|
if (path !== undefined) {
|
|
209
|
-
const r = await resolveKey(env, rootCanonical, path, ctx.signal,
|
|
217
|
+
const r = await resolveKey(env, rootCanonical, path, ctx.signal, baseCwd, additionalRoots, undefined, readDeny, readFace);
|
|
210
218
|
if (!r.ok)
|
|
211
219
|
return errorResult(violationText("Glob", r.violation), violationDetails(r.violation));
|
|
212
220
|
scoped = r.key;
|
package/dist/tools/fs/index.js
CHANGED
|
@@ -52,7 +52,7 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
|
|
|
52
52
|
if (!readOnly) {
|
|
53
53
|
tools.push(createEditFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited), createWriteFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited), createNotebookEditTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited));
|
|
54
54
|
}
|
|
55
|
-
tools.push(createGrepTool(env, rootCanonical, readFaceRoots, readDeny, readFace), createGlobTool(env, rootCanonical, readFaceRoots, readDeny, readFace), createRepoMapTool(env, rootCanonical, readFaceRoots, readDeny, readFace));
|
|
55
|
+
tools.push(createGrepTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef), createGlobTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef), createRepoMapTool(env, rootCanonical, readFaceRoots, readDeny, readFace));
|
|
56
56
|
if (includeShell) {
|
|
57
57
|
tools.push(readOnly
|
|
58
58
|
? createBashReadonlyTool(env, rootCanonical, new Set(bashReadonlyAllow ?? BASH_READONLY_DEFAULT_ALLOW), {
|
|
@@ -92,5 +92,5 @@ export interface NotebookRender {
|
|
|
92
92
|
export declare function renderNotebookCells(cells: readonly NotebookCell[], imageBase64Budget: number): NotebookRender;
|
|
93
93
|
/** Wire-card projection of a cell: base64 replaced by a length marker so the `details` card never
|
|
94
94
|
* carries raw image bytes (they ride the model-facing content blocks; an oversized card would be
|
|
95
|
-
* omitted whole by the
|
|
95
|
+
* omitted whole by the card projector's 4×cap and blind the shell). */
|
|
96
96
|
export declare function stripNotebookImageData(cell: NotebookCell): NotebookCell;
|
|
@@ -321,7 +321,7 @@ export declare function violationText(toolName: string, v: FsViolation): string;
|
|
|
321
321
|
* every renderer of a {@link resolveKey} violation — `undefined` keeps the pre-existing detail-less
|
|
322
322
|
* shape for all other violation codes, so nothing else changes byte-wise.
|
|
323
323
|
*
|
|
324
|
-
* RB-397-c (回溯复查批): `type` is the WIRE discriminator —
|
|
324
|
+
* RB-397-c (回溯复查批): `type` is the WIRE discriminator — the leg's card projector (`createStructuredProjector`, design/388 L4)
|
|
325
325
|
* (tool-output-projection.ts) keys on `details.type`, so without it the card never reached
|
|
326
326
|
* `tool_end.structured` and the "count from fields" promise above held for in-process readers only.
|
|
327
327
|
* `code` stays as-is for the existing RB-371 consumers — the new key is strictly additive.
|
|
@@ -51,14 +51,6 @@ export interface GrepParams {
|
|
|
51
51
|
* (`"*.ts,*.tsx"` → two globs), while a brace token (`*.{ts,tsx}`) is kept whole (its commas are
|
|
52
52
|
* alternation syntax, not separators). CC-verbatim splitting rules. */
|
|
53
53
|
export declare function splitGlobParam(glob: string): string[];
|
|
54
|
-
/** 批④ #8 (CC 2.1.198 RWp, bundle :332987): decompose an ABSOLUTE glob pattern into a base directory +
|
|
55
|
-
* relative pattern — cut at the last separator before the first glob metachar (`* ? [ {`); a pattern
|
|
56
|
-
* with no metachar splits dirname/basename. Returns undefined for a relative pattern (caller keeps it
|
|
57
|
-
* as-is) or when no usable base can be derived. */
|
|
58
|
-
export declare function splitAbsoluteGlobPattern(pattern: string): {
|
|
59
|
-
baseDir: string;
|
|
60
|
-
relativePattern: string;
|
|
61
|
-
} | undefined;
|
|
62
54
|
/**
|
|
63
55
|
* POSIX-safe single-quote so an arbitrary model-supplied string can't break out of the shell command that
|
|
64
56
|
* runs ripgrep. Wraps in single quotes (inside which the shell expands nothing — no `$()`, backtick, `;`, or
|
package/dist/tools/fs/search.js
CHANGED
|
@@ -71,29 +71,6 @@ export function splitGlobParam(glob) {
|
|
|
71
71
|
}
|
|
72
72
|
return out;
|
|
73
73
|
}
|
|
74
|
-
export function splitAbsoluteGlobPattern(pattern) {
|
|
75
|
-
if (!isAbsolutePathForm(pattern))
|
|
76
|
-
return undefined;
|
|
77
|
-
const m = /[*?[{]/.exec(pattern);
|
|
78
|
-
if (!m) {
|
|
79
|
-
const i = Math.max(pattern.lastIndexOf("/"), pattern.lastIndexOf("\\"));
|
|
80
|
-
if (i <= 0)
|
|
81
|
-
return undefined;
|
|
82
|
-
return { baseDir: pattern.slice(0, i), relativePattern: pattern.slice(i + 1) };
|
|
83
|
-
}
|
|
84
|
-
const prefix = pattern.slice(0, m.index);
|
|
85
|
-
const cut = Math.max(prefix.lastIndexOf("/"), prefix.lastIndexOf("\\"));
|
|
86
|
-
if (cut === -1)
|
|
87
|
-
return undefined;
|
|
88
|
-
let baseDir = pattern.slice(0, cut);
|
|
89
|
-
if (baseDir === "" && cut === 0)
|
|
90
|
-
baseDir = "/";
|
|
91
|
-
if (/^[A-Za-z]:$/.test(baseDir))
|
|
92
|
-
baseDir = baseDir + pattern[cut];
|
|
93
|
-
if (!baseDir)
|
|
94
|
-
return undefined;
|
|
95
|
-
return { baseDir, relativePattern: pattern.slice(cut + 1) };
|
|
96
|
-
}
|
|
97
74
|
export function shellQuote(s) {
|
|
98
75
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
99
76
|
}
|