@sema-agent/core 2.12.0 → 3.0.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/dist/agents/agent-transcript-tool.d.ts +1 -0
- package/dist/agents/agent-transcript-tool.js +1 -1
- package/dist/agents/cascade.d.ts +4 -0
- package/dist/agents/cascade.js +54 -9
- package/dist/agents/repair-loop.d.ts +2 -0
- package/dist/agents/repair-loop.js +101 -2
- package/dist/agents/retain-ledger.d.ts +18 -2
- package/dist/agents/retain-ledger.js +37 -8
- package/dist/agents/roster-store.d.ts +4 -0
- package/dist/agents/roster-store.js +22 -1
- package/dist/agents/teacher.js +4 -2
- package/dist/agents/team.js +7 -4
- package/dist/agents/verify.d.ts +15 -2
- package/dist/agents/verify.js +36 -1
- package/dist/bin/sema-tb.js +2 -2
- package/dist/brain/circuit-breaker.d.ts +1 -0
- package/dist/brain/circuit-breaker.js +26 -9
- package/dist/brain/degrading.js +13 -1
- package/dist/brain/failover.js +2 -0
- package/dist/brain/repetition.js +10 -1
- package/dist/brain/routing.js +5 -1
- package/dist/brain/status-sink.js +5 -1
- package/dist/brain/stream-engine.js +4 -1
- package/dist/brain/tool-call-repair.js +32 -14
- package/dist/core/auto-compaction.js +17 -7
- package/dist/core/auto-promote.js +2 -1
- package/dist/core/checkpoint-store.d.ts +1 -0
- package/dist/core/checkpoint-store.js +28 -10
- package/dist/core/consolidate-scope.js +4 -3
- package/dist/core/context-edit.js +3 -0
- package/dist/core/context-guard.js +3 -0
- package/dist/core/git-worktree-env.d.ts +6 -1
- package/dist/core/git-worktree-env.js +17 -1
- package/dist/core/hooks.d.ts +1 -0
- package/dist/core/hooks.js +40 -2
- package/dist/core/lsp-diagnostics.js +13 -7
- package/dist/core/lsp-protocol.d.ts +1 -1
- package/dist/core/lsp-protocol.js +20 -8
- package/dist/core/lsp-session.d.ts +12 -2
- package/dist/core/lsp-session.js +114 -49
- package/dist/core/lsp.d.ts +13 -1
- package/dist/core/lsp.js +64 -9
- package/dist/core/mcp.d.ts +1 -0
- package/dist/core/mcp.js +4 -3
- package/dist/core/memory-engine/data-plane.js +4 -0
- package/dist/core/memory-engine/engine.d.ts +2 -1
- package/dist/core/memory-engine/engine.js +82 -7
- package/dist/core/memory-engine/file-backend.d.ts +1 -0
- package/dist/core/memory-engine/file-backend.js +13 -3
- package/dist/core/memory-engine/layout.js +12 -1
- package/dist/core/memory.js +3 -0
- package/dist/core/permission-rules.d.ts +2 -1
- package/dist/core/permission-rules.js +24 -3
- package/dist/core/runner/active-skill-scope.js +34 -6
- package/dist/core/runner/assemble-result.d.ts +1 -1
- package/dist/core/runner/assemble-result.js +0 -1
- package/dist/core/runner/prepare-task.js +23 -3
- package/dist/core/runner/runtask.js +48 -30
- package/dist/core/runner/tool-disclosure.d.ts +1 -0
- package/dist/core/runner/tool-disclosure.js +26 -7
- package/dist/core/runner/usage-accounting.d.ts +6 -0
- package/dist/core/runner/usage-accounting.js +9 -8
- package/dist/core/secret-env.js +5 -1
- package/dist/core/sensitive-path-policy.js +9 -3
- package/dist/core/session-store.js +15 -4
- package/dist/core/skill-tool-specifier.d.ts +8 -0
- package/dist/core/skill-tool-specifier.js +58 -0
- package/dist/core/skills-directory.d.ts +1 -1
- package/dist/core/skills-directory.js +16 -4
- package/dist/core/store-contracts/session-repo-contract.js +40 -0
- package/dist/core/task-registry-monitor.js +7 -1
- package/dist/core/tool-policy.js +65 -17
- package/dist/core/trace.d.ts +1 -0
- package/dist/core/types.d.ts +3 -5
- package/dist/core/with-retry.js +0 -1
- package/dist/engine/compaction/compaction.d.ts +1 -0
- package/dist/engine/compaction/compaction.js +14 -5
- package/dist/engine/lsp/frame-decoder.js +6 -3
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
- package/dist/engine/lsp/node-lsp-manager.js +103 -26
- package/dist/engine/lsp/stdio-lsp-transport.js +16 -7
- package/dist/index.d.ts +1 -1
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/internal/llm.d.ts +1 -1
- package/dist/orchestration/workflow-governance.js +43 -9
- package/dist/orchestration/workflow-sandbox-conformance.js +85 -51
- package/dist/orchestration/workflow-script-store.js +34 -4
- package/dist/orchestration/workflow.js +10 -6
- package/dist/prompt-assembly/artifact-store.d.ts +2 -0
- package/dist/prompt-assembly/artifact-store.js +39 -24
- package/dist/prompt-assembly/assemble.js +38 -7
- package/dist/prompt-assembly/epoch.js +19 -2
- package/dist/prompt-assembly/event-registry.js +3 -3
- package/dist/prompt-assembly/explain.js +1 -1
- package/dist/prompt-assembly/tool-catalog.js +16 -1
- package/dist/prompt-assembly/turn-snapshot.js +4 -1
- package/dist/stores/file/fs-atomic.d.ts +3 -0
- package/dist/stores/file/fs-atomic.js +53 -8
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +7 -0
- package/dist/stores/file/session-store.d.ts +2 -0
- package/dist/stores/file/session-store.js +28 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
- package/dist/tools/fs/bash-readonly-classifier.js +57 -8
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +93 -5
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/monitor.js +55 -9
- package/dist/tools/scheduler-tools.js +9 -3
- package/dist/tools/web.d.ts +8 -2
- package/dist/tools/web.js +46 -17
- package/dist/tools/worktree.d.ts +2 -0
- package/dist/tools/worktree.js +233 -143
- package/package.json +5 -2
|
@@ -2,6 +2,7 @@ import { uuidv7 } from "../../internal/harness.js";
|
|
|
2
2
|
import { firstSentence } from "../memory.js";
|
|
3
3
|
import { computeEntryRev, parseEntryFile, serializeEntryFile } from "./frontmatter.js";
|
|
4
4
|
import { scanMemoryFileName, scanMemoryWrite } from "./scan.js";
|
|
5
|
+
import { MEMORY_INDEX_FILENAME } from "./file-backend.js";
|
|
5
6
|
import { scopeDirName } from "./layout.js";
|
|
6
7
|
export const REMOTE_HARVEST_PER_FILE_BYTES = 100 * 1024;
|
|
7
8
|
export const REMOTE_MASS_DELETION_FUSE_RATIO = 0.5;
|
|
@@ -97,6 +98,9 @@ export function harvestFilesToPatches(baseline, files, opts = {}) {
|
|
|
97
98
|
out.rejections.push({ path: f.relPath, code: "outside_root", reason: "file path escapes the memory root (containment gate, fail-closed)" });
|
|
98
99
|
continue;
|
|
99
100
|
}
|
|
101
|
+
const baseName = f.relPath.split("/").pop() ?? f.relPath;
|
|
102
|
+
if (baseName === MEMORY_INDEX_FILENAME || !baseName.endsWith(".md") || baseName.startsWith("."))
|
|
103
|
+
continue;
|
|
100
104
|
if (inReadonlyDir(f.relPath)) {
|
|
101
105
|
if (base === undefined) {
|
|
102
106
|
out.rejections.push({ path: f.relPath, code: "readonly_layer", reason: "a file inside a read-only inherited layer's directory is not writable from this session" });
|
|
@@ -56,9 +56,10 @@ export declare class MemoryEngine {
|
|
|
56
56
|
};
|
|
57
57
|
harvest(handle: MemorySessionHandle): Promise<HarvestReport>;
|
|
58
58
|
private harvestCore;
|
|
59
|
-
rebaseline(handle: MemorySessionHandle): Promise<void>;
|
|
59
|
+
rebaseline(handle: MemorySessionHandle, keepBaseline?: ReadonlySet<string>): Promise<void>;
|
|
60
60
|
private rebuildIndex;
|
|
61
61
|
private committedContentFor;
|
|
62
|
+
private gateDerivedIndex;
|
|
62
63
|
private quarantineFile;
|
|
63
64
|
private readonlyDirNamesUnderRoot;
|
|
64
65
|
private writeIfChanged;
|
|
@@ -147,6 +147,18 @@ export class MemoryEngine {
|
|
|
147
147
|
handle.baseIds.set(path, entry.id);
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
+
const indexGate = this.gateDerivedIndex(handle);
|
|
151
|
+
if (indexGate !== undefined) {
|
|
152
|
+
try {
|
|
153
|
+
enqueueMemoryAnnouncement(this.controlDir, {
|
|
154
|
+
kind: "gate",
|
|
155
|
+
at: this.now(),
|
|
156
|
+
items: [`memory index rejected and rebuilt: ${inlineUntrusted(indexGate.reason)}`],
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
}
|
|
161
|
+
}
|
|
150
162
|
const indexText = this.rebuildIndex(handle, headers, { write: writeScope !== null });
|
|
151
163
|
handle.indexBaselineLines = countIndexLines(indexText);
|
|
152
164
|
handle.indexText = indexText;
|
|
@@ -364,6 +376,7 @@ export class MemoryEngine {
|
|
|
364
376
|
basePathById.set(id, p);
|
|
365
377
|
const patches = [];
|
|
366
378
|
const pendingProjections = [];
|
|
379
|
+
const idsAddedThisHarvest = new Set();
|
|
367
380
|
let processed = 0;
|
|
368
381
|
for (let i = 0; i < kept.length; i++) {
|
|
369
382
|
const f = kept[i];
|
|
@@ -465,7 +478,28 @@ export class MemoryEngine {
|
|
|
465
478
|
patches.push({ op: "update", id: parsed.id, entry: { ...entry, id: parsed.id, rev: computeEntryRev({ id: parsed.id, frontmatter: fm, body: parsed.body }) }, ...(revByBasePath.get(renamedFrom) !== undefined ? { baseRev: revByBasePath.get(renamedFrom) } : {}) });
|
|
466
479
|
continue;
|
|
467
480
|
}
|
|
468
|
-
|
|
481
|
+
let reboundId;
|
|
482
|
+
if (parsed.id !== undefined && (renamedFrom !== undefined || idsAddedThisHarvest.has(parsed.id))) {
|
|
483
|
+
reboundId = parsed.id;
|
|
484
|
+
const idless = serializeEntryFile({ id: "", frontmatter: fm, body: parsed.body }).replace(/^id:[ \t]*$\r?\n/m, "");
|
|
485
|
+
try {
|
|
486
|
+
writeFileSync(f.canonical, idless, "utf8");
|
|
487
|
+
}
|
|
488
|
+
catch (err) {
|
|
489
|
+
report.rejections.push({
|
|
490
|
+
path: rel,
|
|
491
|
+
code: "filename",
|
|
492
|
+
reason: `memory write blocked: this file carries the entry id of another memory file (a copy is not a rename) and its frontmatter could not be rewritten (${err instanceof Error ? err.message : String(err)}) — remove the "id:" line so it can be committed as a new entry`,
|
|
493
|
+
});
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
id = uuidv7();
|
|
497
|
+
entry.id = id;
|
|
498
|
+
entry.rev = computeEntryRev(entry);
|
|
499
|
+
report.warnings.push(`${rel}: frontmatter id ${reboundId} already has a projection (${renamedFrom !== undefined ? relative(handle.memoryDir, renamedFrom) : "another new file in this harvest"}) — a copy is not a rename, so this file was committed as a NEW entry with a fresh id; the existing entry was left untouched`);
|
|
500
|
+
}
|
|
501
|
+
pendingProjections.push({ path: f.canonical, entry, needed: minted || reboundId !== undefined || needsCompletion(parsed, fm) });
|
|
502
|
+
idsAddedThisHarvest.add(entry.id);
|
|
469
503
|
patches.push({ op: "add", id: entry.id, entry });
|
|
470
504
|
}
|
|
471
505
|
let patchReport;
|
|
@@ -503,12 +537,15 @@ export class MemoryEngine {
|
|
|
503
537
|
const drained = this.backend.drainInboundFindings?.();
|
|
504
538
|
if (drained !== undefined && drained.length > 0)
|
|
505
539
|
report.inboundFindings = drained;
|
|
540
|
+
const indexRejection = this.gateDerivedIndex(handle);
|
|
541
|
+
if (indexRejection !== undefined)
|
|
542
|
+
report.rejections.push(indexRejection);
|
|
506
543
|
const headers = await this.backend.listHeaders([...new Set([...handle.scopes, writeScope])]);
|
|
507
544
|
handle.indexText = this.rebuildIndex(handle, headers, { write: true }, report.warnings);
|
|
508
|
-
await this.rebaseline(handle);
|
|
545
|
+
await this.rebaseline(handle, new Set(report.degraded?.pending ?? []));
|
|
509
546
|
return report;
|
|
510
547
|
}
|
|
511
|
-
async rebaseline(handle) {
|
|
548
|
+
async rebaseline(handle, keepBaseline = new Set()) {
|
|
512
549
|
const writeScope = handle.writeScope;
|
|
513
550
|
if (writeScope === null)
|
|
514
551
|
return;
|
|
@@ -535,12 +572,17 @@ export class MemoryEngine {
|
|
|
535
572
|
if (id === undefined)
|
|
536
573
|
continue;
|
|
537
574
|
const stub = stubs.get(path);
|
|
538
|
-
const
|
|
575
|
+
const relPath = relative(handle.memoryDir, path);
|
|
576
|
+
const deferred = keepBaseline.has(relPath);
|
|
577
|
+
const priorRev = deferred ? handle.baseRevs.get(path) : undefined;
|
|
578
|
+
const priorId = deferred ? handle.baseIds.get(path) : undefined;
|
|
579
|
+
const rev = priorRev ?? revOfText(text, priorId ?? id);
|
|
580
|
+
const baselineId = priorId ?? id;
|
|
539
581
|
next.push({
|
|
540
582
|
path,
|
|
541
|
-
relPath
|
|
583
|
+
relPath,
|
|
542
584
|
scope: writeScope,
|
|
543
|
-
id,
|
|
585
|
+
id: baselineId,
|
|
544
586
|
slug: f.slug,
|
|
545
587
|
rev,
|
|
546
588
|
stub: stub !== undefined && stub.rev === rev,
|
|
@@ -548,7 +590,7 @@ export class MemoryEngine {
|
|
|
548
590
|
});
|
|
549
591
|
fileToScope.set(path, writeScope);
|
|
550
592
|
baseRevs.set(path, rev);
|
|
551
|
-
baseIds.set(path,
|
|
593
|
+
baseIds.set(path, baselineId);
|
|
552
594
|
}
|
|
553
595
|
handle.materialized = next;
|
|
554
596
|
handle.fileToScope = fileToScope;
|
|
@@ -650,6 +692,39 @@ export class MemoryEngine {
|
|
|
650
692
|
return viaBackend;
|
|
651
693
|
return readSafe(join(this.controlDir, "shadow", `${id}.md`));
|
|
652
694
|
}
|
|
695
|
+
gateDerivedIndex(handle) {
|
|
696
|
+
if (handle.writeScope === null)
|
|
697
|
+
return undefined;
|
|
698
|
+
const indexPath = join(handle.writableRoot, MEMORY_INDEX_FILENAME);
|
|
699
|
+
const text = readSafe(indexPath);
|
|
700
|
+
if (text === undefined || text === "")
|
|
701
|
+
return undefined;
|
|
702
|
+
const finding = scanMemoryWrite(text).find((f) => f.code === "secret");
|
|
703
|
+
if (finding === undefined)
|
|
704
|
+
return undefined;
|
|
705
|
+
let detail = "";
|
|
706
|
+
try {
|
|
707
|
+
const dest = join(this.controlDir, "quarantine", `${this.now()}-${MEMORY_INDEX_FILENAME}`);
|
|
708
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
709
|
+
writeFileSync(dest, text, "utf8");
|
|
710
|
+
}
|
|
711
|
+
catch (err) {
|
|
712
|
+
detail = `; quarantine capture failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
713
|
+
}
|
|
714
|
+
let cleared = false;
|
|
715
|
+
try {
|
|
716
|
+
writeFileSync(indexPath, "", "utf8");
|
|
717
|
+
cleared = true;
|
|
718
|
+
}
|
|
719
|
+
catch (err) {
|
|
720
|
+
detail = `${detail}; the derived index could NOT be cleared: ${err instanceof Error ? err.message : String(err)}`;
|
|
721
|
+
}
|
|
722
|
+
return {
|
|
723
|
+
path: MEMORY_INDEX_FILENAME,
|
|
724
|
+
code: finding.code,
|
|
725
|
+
reason: `memory index blocked: ${finding.reason} (${cleared ? "index cleared and rebuilt from entry frontmatter; the offending prose was captured to quarantine" : "index still carries the offending content"})${detail}`,
|
|
726
|
+
};
|
|
727
|
+
}
|
|
653
728
|
quarantineFile(path, content) {
|
|
654
729
|
const dest = join(this.controlDir, "quarantine", `${this.now()}-${basename(path)}`);
|
|
655
730
|
let captured;
|
|
@@ -79,10 +79,10 @@ export class FileMemoryEngineBackend {
|
|
|
79
79
|
this.controlPlaneRoot = opts.controlDir ?? deriveControlPlaneDir(resolveMemoryEngineRoot(), dir);
|
|
80
80
|
ensureDirExists(dir);
|
|
81
81
|
ensureDirExists(this.controlPlaneRoot);
|
|
82
|
-
this.recoverJournal();
|
|
82
|
+
this.recoverJournal({ unlocked: true });
|
|
83
83
|
}
|
|
84
84
|
checkControlPlane() {
|
|
85
|
-
this.recoverJournal();
|
|
85
|
+
this.recoverJournal({ unlocked: true });
|
|
86
86
|
this.ledger = undefined;
|
|
87
87
|
this.loadLedger();
|
|
88
88
|
registeredScopes(this.controlPlaneRoot);
|
|
@@ -164,8 +164,10 @@ export class FileMemoryEngineBackend {
|
|
|
164
164
|
return;
|
|
165
165
|
atomicWriteFileSync(join(this.controlPlaneRoot, LEDGER_FILE), `${JSON.stringify(this.ledger, null, 2)}\n`);
|
|
166
166
|
}
|
|
167
|
-
recoverJournal() {
|
|
167
|
+
recoverJournal(opts = {}) {
|
|
168
168
|
const jp = join(this.controlPlaneRoot, JOURNAL_FILE);
|
|
169
|
+
if (opts.unlocked && this.txnInFlight())
|
|
170
|
+
return;
|
|
169
171
|
const raw = readSafe(jp);
|
|
170
172
|
if (raw !== undefined) {
|
|
171
173
|
let journal;
|
|
@@ -205,6 +207,14 @@ export class FileMemoryEngineBackend {
|
|
|
205
207
|
}
|
|
206
208
|
this.sweepStagedFiles();
|
|
207
209
|
}
|
|
210
|
+
txnInFlight() {
|
|
211
|
+
try {
|
|
212
|
+
return this.now() - statSync(this.txnLockDir()).mtimeMs < TXN_LOCK_STALE_MS;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
208
218
|
sweepStagedFiles() {
|
|
209
219
|
const walk = (d, depth) => {
|
|
210
220
|
let names;
|
|
@@ -216,11 +216,22 @@ function acquireSidecarLock(lockDir, opts = {}) {
|
|
|
216
216
|
try {
|
|
217
217
|
mkdirSync(lockDir);
|
|
218
218
|
const token = `${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
|
|
219
|
+
let fd;
|
|
219
220
|
try {
|
|
220
|
-
|
|
221
|
+
fd = openSync(join(lockDir, "owner"), "w");
|
|
222
|
+
writeSync(fd, token);
|
|
221
223
|
}
|
|
222
224
|
catch {
|
|
223
225
|
}
|
|
226
|
+
finally {
|
|
227
|
+
if (fd !== undefined) {
|
|
228
|
+
try {
|
|
229
|
+
closeSync(fd);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
224
235
|
return token;
|
|
225
236
|
}
|
|
226
237
|
catch {
|
package/dist/core/memory.js
CHANGED
|
@@ -32,6 +32,9 @@ const SECRET_PATTERNS = [
|
|
|
32
32
|
{ label: "slack_token", re: /\bxox[baprs]-(?:[A-Za-z0-9]+-){1,3}[A-Za-z0-9]{16,}\b/ },
|
|
33
33
|
{ label: "google_api_key", re: /\bAIza[0-9A-Za-z_-]{35}\b/ },
|
|
34
34
|
{ label: "private_key", re: /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----/ },
|
|
35
|
+
{ label: "jwt", re: /\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]{16,}\b/ },
|
|
36
|
+
{ label: "azure_storage_account_key", re: /AccountKey=[A-Za-z0-9+/]{40,}={0,2}/ },
|
|
37
|
+
{ label: "bearer_token", re: /\bAuthorization:\s*Bearer\s+(?=[A-Za-z0-9._~+/-]*[0-9])[A-Za-z0-9._~+/-]{20,}=*/ },
|
|
35
38
|
];
|
|
36
39
|
export function detectSecret(text) {
|
|
37
40
|
if (!text)
|
|
@@ -11,7 +11,7 @@ export interface ParsedPermissionRule {
|
|
|
11
11
|
}
|
|
12
12
|
export interface PermissionRuleIssue {
|
|
13
13
|
rule: string;
|
|
14
|
-
code: "invalid.empty_tool" | "invalid.paren" | "invalid.param_split" | "invalid.allow_param_rule" | "invalid.primary_field" | "invalid.cap_exceeded" | "unsupported.bash_prefix" | "unsupported.file_glob" | "unsupported.mcp_paren";
|
|
14
|
+
code: "invalid.empty_tool" | "invalid.paren" | "invalid.param_split" | "invalid.allow_param_rule" | "invalid.primary_field" | "invalid.cap_exceeded" | "unsupported.bash_prefix" | "unsupported.file_glob" | "unsupported.mcp_paren" | "unsupported.unknown_param";
|
|
15
15
|
message: string;
|
|
16
16
|
}
|
|
17
17
|
export interface PermissionRuleCaps {
|
|
@@ -30,5 +30,6 @@ export interface PermissionRulePolicyOptions {
|
|
|
30
30
|
}
|
|
31
31
|
export declare function parsePermissionRule(rule: string): ParsedPermissionRule;
|
|
32
32
|
export declare function wildcardMatch(pattern: string, value: string): boolean;
|
|
33
|
+
export declare const fileToolParamVocabulary: (canonicalTool: string) => ReadonlySet<string> | undefined;
|
|
33
34
|
export declare function validatePermissionRules(rules: PermissionRule[], opts?: Pick<PermissionRulePolicyOptions, "primaryFieldGeneric" | "caps">): PermissionRuleIssue[];
|
|
34
35
|
export declare function createPermissionRulePolicy(rules: PermissionRule[], opts?: PermissionRulePolicyOptions): ToolPolicy;
|
|
@@ -90,6 +90,18 @@ const PRIMARY_FIELDS = {
|
|
|
90
90
|
WebSearch: new Set(["query"]),
|
|
91
91
|
};
|
|
92
92
|
const FILE_TOOLS = new Set(["Read", "Edit", "Write", "MultiEdit", "NotebookEdit", "Grep", "Glob"]);
|
|
93
|
+
const FILE_TOOL_PARAMS = {
|
|
94
|
+
Read: new Set(["file_path", "path", "offset", "limit", "pages"]),
|
|
95
|
+
Edit: new Set(["file_path", "path", "old_string", "new_string", "replace_all"]),
|
|
96
|
+
Write: new Set(["file_path", "path", "content"]),
|
|
97
|
+
NotebookEdit: new Set(["notebook_path", "path", "cell_id", "cell_type", "edit_mode", "new_source"]),
|
|
98
|
+
Grep: new Set([
|
|
99
|
+
"pattern", "path", "glob", "type", "output_mode", "head_limit", "max_results", "offset",
|
|
100
|
+
"multiline", "context", "ignore_case", "-i", "-n", "-o", "-A", "-B", "-C",
|
|
101
|
+
]),
|
|
102
|
+
Glob: new Set(["pattern", "path", "max_results"]),
|
|
103
|
+
};
|
|
104
|
+
export const fileToolParamVocabulary = (canonicalTool) => FILE_TOOL_PARAMS[canonicalTool];
|
|
93
105
|
function countStars(s) {
|
|
94
106
|
let n = 0;
|
|
95
107
|
for (const c of s)
|
|
@@ -151,9 +163,18 @@ function compile(rules, caps, primaryFieldGeneric) {
|
|
|
151
163
|
continue;
|
|
152
164
|
}
|
|
153
165
|
}
|
|
154
|
-
else if (FILE_TOOLS.has(toolName)
|
|
155
|
-
|
|
156
|
-
|
|
166
|
+
else if (FILE_TOOLS.has(toolName)) {
|
|
167
|
+
if (colon <= 0) {
|
|
168
|
+
bad(text, "unsupported.file_glob", "file-tool glob rules (`Edit(src/**)`) are the tool-specific lane — not supported by the generic param DSL");
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const paramPart = content.slice(0, colon).trim();
|
|
172
|
+
const vocabulary = FILE_TOOL_PARAMS[toolName];
|
|
173
|
+
if (vocabulary !== undefined && !vocabulary.has(paramPart)) {
|
|
174
|
+
bad(text, "unsupported.unknown_param", `"${paramPart}" is not a parameter of ${toolName} (it carries ${[...vocabulary].sort().join(", ")}) — the rule would compile and never match. ` +
|
|
175
|
+
`For a path glob use the tool-specific lane (\`${toolName}(src/**)\` is not the generic param DSL); a Windows path such as \`${toolName}(C:/foo)\` parses as the param "C".`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
157
178
|
}
|
|
158
179
|
if (colon <= 0) {
|
|
159
180
|
bad(text, "invalid.param_split", "param-level ruleContent must be `param:pattern`");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { canonicalizeTarget, fileArgPath } from "../../tools/fs/safety.js";
|
|
2
2
|
import { canonicalToolName } from "../tool-name-aliases.js";
|
|
3
|
+
import { parseSkillToolEntry, skillSpecifierRejection } from "../skill-tool-specifier.js";
|
|
3
4
|
export class ActiveSkillScope {
|
|
4
5
|
frames = [];
|
|
5
6
|
push(frame) {
|
|
@@ -47,12 +48,19 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
const manifests = frames.flatMap((f) => (f.kind === "manifest" ? [f.manifest] : []));
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
const perFrame = manifests.map((m) => {
|
|
52
|
+
const byName = new Map();
|
|
53
|
+
for (const raw of m.allowTools) {
|
|
54
|
+
const entry = parseSkillToolEntry(raw);
|
|
55
|
+
const bucket = byName.get(entry.name);
|
|
56
|
+
if (bucket)
|
|
57
|
+
bucket.push(entry);
|
|
58
|
+
else
|
|
59
|
+
byName.set(entry.name, [entry]);
|
|
60
|
+
}
|
|
61
|
+
return { manifest: m, byName };
|
|
62
|
+
});
|
|
63
|
+
if (perFrame.length === 0 || perFrame.some((f) => !f.byName.has(toolName))) {
|
|
56
64
|
const ids = manifests.map((m) => m.lineageId).join(", ");
|
|
57
65
|
return {
|
|
58
66
|
action: "deny",
|
|
@@ -60,6 +68,26 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
60
68
|
decisionReason: "safety",
|
|
61
69
|
};
|
|
62
70
|
}
|
|
71
|
+
for (const frame of perFrame) {
|
|
72
|
+
const alternatives = frame.byName.get(toolName) ?? [];
|
|
73
|
+
const rejections = [];
|
|
74
|
+
let admitted = false;
|
|
75
|
+
for (const entry of alternatives) {
|
|
76
|
+
const rejection = skillSpecifierRejection(entry, req.args);
|
|
77
|
+
if (rejection === undefined) {
|
|
78
|
+
admitted = true;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
rejections.push(rejection);
|
|
82
|
+
}
|
|
83
|
+
if (!admitted) {
|
|
84
|
+
return {
|
|
85
|
+
action: "deny",
|
|
86
|
+
reason: `tool "${req.toolName}" is narrowed by skill manifest "${frame.manifest.lineageId}": ${rejections.join("; ")}`,
|
|
87
|
+
decisionReason: "safety",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
63
91
|
const pathConstrainingActive = manifests.some((m) => m.allowPaths && m.allowPaths.length > 0);
|
|
64
92
|
if (pathConstrainingActive && !PATH_WRITE_TOOLS.has(toolName)) {
|
|
65
93
|
const eff = toolEffects?.get(toolName) ?? "write";
|
|
@@ -35,7 +35,6 @@ function assistantText(msg) {
|
|
|
35
35
|
export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
36
36
|
const taskId = spec.taskId ?? sessionId;
|
|
37
37
|
const text = final ? assistantText(final) : "";
|
|
38
|
-
stats.totalInputTokens = stats.promptTokens;
|
|
39
38
|
if (!flags.unpricedSpend) {
|
|
40
39
|
const compactionMicroUsd = stats.compactionMicroUsd ?? 0;
|
|
41
40
|
const nestedSubagentMicroUsd = stats.nested?.costMicroUsd ?? 0;
|
|
@@ -432,6 +432,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
432
432
|
}
|
|
433
433
|
}
|
|
434
434
|
const taskScope = internals?.registryScope ?? spec.principal ?? "default";
|
|
435
|
+
const offloadScope = spec.principal ?? "default";
|
|
436
|
+
if (internals?.registryScope !== undefined && internals.registryScope !== offloadScope) {
|
|
437
|
+
deps.onError?.(new Error(`offload/tool-result namespace note: this run mounts in registry domain "${internals.registryScope}" but its offloaded tool results live under the TRUST identity "${offloadScope}" (principal${spec.principal === undefined ? " absent → default" : ""}). Before 2.13.0 the registry domain named this namespace too; if a deployment used registryScope as its ref-tenancy boundary, that boundary is now the principal — pass one, or read refs under "${offloadScope}".`), { phase: "config", sessionId });
|
|
438
|
+
}
|
|
435
439
|
defaultTaskRegistry.maybeGc();
|
|
436
440
|
const forgetOnThrow = () => forgetQuietly(sessions, sessionId);
|
|
437
441
|
const extraBodyCollisions = reservedCollisions(model.extraBody, reservedFor(model.api));
|
|
@@ -456,7 +460,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
456
460
|
const offloadEnabled = Number.isFinite(offloadThreshold) && offloadThreshold > 0;
|
|
457
461
|
const rawOffloadStore = offloadEnabled ? (deps.toolResultStore ?? new InMemoryToolResultStore()) : undefined;
|
|
458
462
|
const offloadStore = rawOffloadStore instanceof RunnerSharedToolResultStore
|
|
459
|
-
? new ScopedToolResultStore(rawOffloadStore,
|
|
463
|
+
? new ScopedToolResultStore(rawOffloadStore, offloadScope)
|
|
460
464
|
: rawOffloadStore;
|
|
461
465
|
const offloadReachableToolsRef = {};
|
|
462
466
|
const maybeOffload = (tool, perTool) => {
|
|
@@ -1384,6 +1388,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1384
1388
|
owner: hostTaskId,
|
|
1385
1389
|
scope: taskScope,
|
|
1386
1390
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
1391
|
+
enrichCtx: enrichSpecToolCtx,
|
|
1387
1392
|
})));
|
|
1388
1393
|
}
|
|
1389
1394
|
}
|
|
@@ -1915,6 +1920,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1915
1920
|
throw e;
|
|
1916
1921
|
}
|
|
1917
1922
|
const registry = buildDeferredRegistry(deferred, tools);
|
|
1923
|
+
let activationChain = Promise.resolve();
|
|
1924
|
+
const serializeActivation = (section) => {
|
|
1925
|
+
const p = activationChain.then(section);
|
|
1926
|
+
activationChain = p.then(() => undefined, () => undefined);
|
|
1927
|
+
return p;
|
|
1928
|
+
};
|
|
1918
1929
|
const directCallFor = (name) => {
|
|
1919
1930
|
if (spec.deferSelfResolve === false)
|
|
1920
1931
|
return undefined;
|
|
@@ -1930,7 +1941,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1930
1941
|
};
|
|
1931
1942
|
},
|
|
1932
1943
|
...(executionMode !== undefined ? { executionMode } : {}),
|
|
1933
|
-
activate: async () => {
|
|
1944
|
+
activate: async () => serializeActivation(async () => {
|
|
1934
1945
|
if (activeTools.has(name))
|
|
1935
1946
|
return undefined;
|
|
1936
1947
|
activeTools.add(name);
|
|
@@ -1942,7 +1953,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1942
1953
|
throw e;
|
|
1943
1954
|
}
|
|
1944
1955
|
return listingRideRef.current?.([name]);
|
|
1945
|
-
},
|
|
1956
|
+
}),
|
|
1946
1957
|
};
|
|
1947
1958
|
};
|
|
1948
1959
|
const placeholders = new Map([...registry.values()].map((i) => [i.name, createPlaceholderTool(i, directCallFor(i.name))]));
|
|
@@ -2016,6 +2027,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2016
2027
|
listingRide: (newly) => listingRideRef.current?.(newly),
|
|
2017
2028
|
mountedNames: callableToolNames,
|
|
2018
2029
|
directCallEnabled: spec.deferSelfResolve !== false,
|
|
2030
|
+
serializeActivation,
|
|
2019
2031
|
});
|
|
2020
2032
|
harnessTools = buildToolList(activeTools);
|
|
2021
2033
|
}
|
|
@@ -2734,6 +2746,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2734
2746
|
}
|
|
2735
2747
|
}
|
|
2736
2748
|
: undefined;
|
|
2749
|
+
const notifyHookError = (err) => {
|
|
2750
|
+
try {
|
|
2751
|
+
deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId });
|
|
2752
|
+
}
|
|
2753
|
+
catch {
|
|
2754
|
+
}
|
|
2755
|
+
};
|
|
2737
2756
|
const checkpointStore = spec.checkpointStore ?? deps.checkpointStore;
|
|
2738
2757
|
const durableApproval = spec.durableApproval ??
|
|
2739
2758
|
(runtimeCaps?.forceDurableGate ? { scope: spec.principal || DEFAULT_IRREVERSIBLE_SCOPE } : undefined);
|
|
@@ -3174,6 +3193,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3174
3193
|
reversibilityProbe: reversibilityProbes.get(e.toolName),
|
|
3175
3194
|
abortSignal: abortController.signal,
|
|
3176
3195
|
...(notifyPermissionDenied ? { permissionDenied: notifyPermissionDenied } : {}),
|
|
3196
|
+
onHookError: notifyHookError,
|
|
3177
3197
|
shellGated: (e.toolName === "Bash" && shellGatedBash) || (e.toolName === "Monitor" && shellGatedMonitor),
|
|
3178
3198
|
...(autoModeDecider ? { autoMode: { decider: autoModeDecider } } : {}),
|
|
3179
3199
|
isMarkedUnresolvable: (toolCallId) => inheritedUnavailableAsks.has(toolCallId),
|