@rulvar/core 1.53.0 → 1.55.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/index.d.ts +339 -4
- package/dist/index.js +1041 -28
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { createHash, getRandomValues, randomUUID } from "node:crypto";
|
|
2
|
-
import { appendFileSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join, resolve, sep } from "node:path";
|
|
2
|
+
import { appendFileSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path, { dirname, join, resolve, sep } from "node:path";
|
|
4
4
|
import { Client } from "@modelcontextprotocol/sdk/client";
|
|
5
5
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
6
6
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7
7
|
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
8
8
|
import { ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import { execFile } from "node:child_process";
|
|
10
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
10
|
+
import { mkdtemp, readFile, readdir, realpath, rm, stat } from "node:fs/promises";
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
12
|
import { promisify } from "node:util";
|
|
13
13
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -3297,6 +3297,650 @@ var GitWorktreeProvider = class {
|
|
|
3297
3297
|
}
|
|
3298
3298
|
};
|
|
3299
3299
|
//#endregion
|
|
3300
|
+
//#region src/tools/research.ts
|
|
3301
|
+
/**
|
|
3302
|
+
* The standard repository research toolset (RV-210 remainder): paginated
|
|
3303
|
+
* list/search/read tools over a confined directory root with STABLE
|
|
3304
|
+
* keyset cursors, plus an evidence collector that verifies citations at
|
|
3305
|
+
* collection time. Design contract:
|
|
3306
|
+
*
|
|
3307
|
+
* - Responses are CANONICAL: a page is a pure function of (root
|
|
3308
|
+
* filesystem state, logical window), never of how the window was
|
|
3309
|
+
* addressed, so reading the same page through a cursor and through
|
|
3310
|
+
* fresh arguments returns byte-identical results. That is what makes
|
|
3311
|
+
* the RV-210 `maxNoNewEvidenceCalls` guard measure duplicate-page
|
|
3312
|
+
* reads correctly, and the `maxRepeatedToolSignature` guard already
|
|
3313
|
+
* denies byte-identical repeat calls: deduplication is composition,
|
|
3314
|
+
* not a marker field.
|
|
3315
|
+
* - Cursors are keyset cursors (the last path / line, not an offset), so
|
|
3316
|
+
* a page boundary never shifts when unrelated entries appear or
|
|
3317
|
+
* disappear, and every cursor embeds the query identity: replaying a
|
|
3318
|
+
* cursor against different arguments is a typed error result.
|
|
3319
|
+
* - Ordering is deterministic byte order (UTF-16 code unit sort), never
|
|
3320
|
+
* locale collation.
|
|
3321
|
+
* - The root confines everything: relative paths only, `..` escapes and
|
|
3322
|
+
* symlink escapes are typed error results, symlinked directories are
|
|
3323
|
+
* never walked.
|
|
3324
|
+
* - User-level failures (bad path, binary file, oversized file, invalid
|
|
3325
|
+
* cursor, an unverifiable citation) are RETURNED `{ error }` values,
|
|
3326
|
+
* deterministic and visible to the model; only host misconfiguration
|
|
3327
|
+
* throws (ConfigError at construction).
|
|
3328
|
+
* - Tool results are journaled at execution time, so replay never
|
|
3329
|
+
* touches the filesystem; live pages read the live tree.
|
|
3330
|
+
*
|
|
3331
|
+
* Public docs: https://docs.rulvar.com/guide/tools
|
|
3332
|
+
*/
|
|
3333
|
+
const DEFAULT_PAGE_SIZE = 50;
|
|
3334
|
+
const DEFAULT_READ_PAGE_CHARS = 4e3;
|
|
3335
|
+
const DEFAULT_MAX_FILE_BYTES = 262144;
|
|
3336
|
+
const DEFAULT_MAX_SCANNED_FILES = 2e4;
|
|
3337
|
+
const ALWAYS_IGNORED = [".git", "node_modules"];
|
|
3338
|
+
const SEARCH_SNIPPET_CHARS = 200;
|
|
3339
|
+
const BINARY_SNIFF_BYTES = 8192;
|
|
3340
|
+
const LIST_SCHEMA = {
|
|
3341
|
+
type: "object",
|
|
3342
|
+
additionalProperties: false,
|
|
3343
|
+
properties: {
|
|
3344
|
+
dir: {
|
|
3345
|
+
type: "string",
|
|
3346
|
+
description: "Root-relative directory to list; default the root."
|
|
3347
|
+
},
|
|
3348
|
+
cursor: {
|
|
3349
|
+
type: "string",
|
|
3350
|
+
description: "Opaque cursor from a previous page."
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
};
|
|
3354
|
+
const SEARCH_SCHEMA = {
|
|
3355
|
+
type: "object",
|
|
3356
|
+
additionalProperties: false,
|
|
3357
|
+
required: ["query"],
|
|
3358
|
+
properties: {
|
|
3359
|
+
query: {
|
|
3360
|
+
type: "string",
|
|
3361
|
+
minLength: 1,
|
|
3362
|
+
description: "Literal substring to find."
|
|
3363
|
+
},
|
|
3364
|
+
dir: {
|
|
3365
|
+
type: "string",
|
|
3366
|
+
description: "Root-relative directory to search; default the root."
|
|
3367
|
+
},
|
|
3368
|
+
cursor: {
|
|
3369
|
+
type: "string",
|
|
3370
|
+
description: "Opaque cursor from a previous page."
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
};
|
|
3374
|
+
const READ_SCHEMA = {
|
|
3375
|
+
type: "object",
|
|
3376
|
+
additionalProperties: false,
|
|
3377
|
+
required: ["path"],
|
|
3378
|
+
properties: {
|
|
3379
|
+
path: {
|
|
3380
|
+
type: "string",
|
|
3381
|
+
minLength: 1,
|
|
3382
|
+
description: "Root-relative file path."
|
|
3383
|
+
},
|
|
3384
|
+
cursor: {
|
|
3385
|
+
type: "string",
|
|
3386
|
+
description: "Opaque cursor from a previous page."
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
};
|
|
3390
|
+
const RECORD_EVIDENCE_SCHEMA = {
|
|
3391
|
+
type: "object",
|
|
3392
|
+
additionalProperties: false,
|
|
3393
|
+
required: ["claim", "file"],
|
|
3394
|
+
properties: {
|
|
3395
|
+
claim: {
|
|
3396
|
+
type: "string",
|
|
3397
|
+
minLength: 1,
|
|
3398
|
+
description: "The claim this evidence supports."
|
|
3399
|
+
},
|
|
3400
|
+
file: {
|
|
3401
|
+
type: "string",
|
|
3402
|
+
minLength: 1,
|
|
3403
|
+
description: "Root-relative file the claim cites."
|
|
3404
|
+
},
|
|
3405
|
+
lines: {
|
|
3406
|
+
type: "string",
|
|
3407
|
+
description: "Cited line or range, 1-based: '12' or '12-40'."
|
|
3408
|
+
},
|
|
3409
|
+
quote: {
|
|
3410
|
+
type: "string",
|
|
3411
|
+
minLength: 1,
|
|
3412
|
+
description: "Verbatim quote; verified to appear in the file."
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
};
|
|
3416
|
+
const LIST_EVIDENCE_SCHEMA = {
|
|
3417
|
+
type: "object",
|
|
3418
|
+
additionalProperties: false,
|
|
3419
|
+
properties: { cursor: {
|
|
3420
|
+
type: "string",
|
|
3421
|
+
description: "Opaque cursor from a previous page."
|
|
3422
|
+
} }
|
|
3423
|
+
};
|
|
3424
|
+
function encodeCursor(payload) {
|
|
3425
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
3426
|
+
}
|
|
3427
|
+
function decodeCursor(raw) {
|
|
3428
|
+
try {
|
|
3429
|
+
return JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
|
|
3430
|
+
} catch {
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
function isBinary(buffer) {
|
|
3435
|
+
return buffer.subarray(0, BINARY_SNIFF_BYTES).includes(0);
|
|
3436
|
+
}
|
|
3437
|
+
/** Split into lines on LF; a trailing CR per line is presentation, not content. */
|
|
3438
|
+
function splitLines(text) {
|
|
3439
|
+
return text.split("\n").map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
3440
|
+
}
|
|
3441
|
+
function repositoryResearchToolset(options) {
|
|
3442
|
+
if (typeof options.root !== "string" || options.root.length === 0) throw new ConfigError("repositoryResearchToolset root must be a non-empty string");
|
|
3443
|
+
let realRoot;
|
|
3444
|
+
try {
|
|
3445
|
+
realRoot = realpathSync(path.resolve(options.root));
|
|
3446
|
+
} catch {
|
|
3447
|
+
throw new ConfigError(`repositoryResearchToolset root '${options.root}' does not exist`);
|
|
3448
|
+
}
|
|
3449
|
+
if (!statSync(realRoot).isDirectory()) throw new ConfigError(`repositoryResearchToolset root '${options.root}' is not a directory`);
|
|
3450
|
+
for (const [name, value] of [
|
|
3451
|
+
["pageSize", options.pageSize],
|
|
3452
|
+
["readPageChars", options.readPageChars],
|
|
3453
|
+
["maxFileBytes", options.maxFileBytes],
|
|
3454
|
+
["maxScannedFiles", options.maxScannedFiles]
|
|
3455
|
+
]) if (value !== void 0 && (!Number.isSafeInteger(value) || value < 1)) throw new ConfigError(`repositoryResearchToolset ${name} must be a positive integer; got ${String(value)}`);
|
|
3456
|
+
const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
3457
|
+
const readPageChars = options.readPageChars ?? DEFAULT_READ_PAGE_CHARS;
|
|
3458
|
+
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
3459
|
+
const maxScannedFiles = options.maxScannedFiles ?? DEFAULT_MAX_SCANNED_FILES;
|
|
3460
|
+
const includeHidden = options.includeHidden ?? false;
|
|
3461
|
+
const ignored = /* @__PURE__ */ new Set([...ALWAYS_IGNORED, ...options.ignore ?? []]);
|
|
3462
|
+
const evidence = [];
|
|
3463
|
+
/**
|
|
3464
|
+
* Resolves a root-relative POSIX path and confines it: absolute paths,
|
|
3465
|
+
* `..` escapes, and symlink escapes are error strings, never throws.
|
|
3466
|
+
* `mustExist` additionally resolves symlinks and re-checks containment
|
|
3467
|
+
* of the REAL path (the FileTranscriptStore traversal lesson).
|
|
3468
|
+
*/
|
|
3469
|
+
const resolveWithin = async (rel, kind) => {
|
|
3470
|
+
const normalizedInput = rel.replaceAll("\\", "/");
|
|
3471
|
+
if (path.posix.isAbsolute(normalizedInput) || path.isAbsolute(rel)) return { error: `path must be relative to the research root; got '${rel}'` };
|
|
3472
|
+
const normalized = path.posix.normalize(normalizedInput);
|
|
3473
|
+
if (normalized === ".." || normalized.startsWith("../")) return { error: `path escapes the research root: '${rel}'` };
|
|
3474
|
+
const cleaned = normalized === "." ? "" : normalized;
|
|
3475
|
+
if (kind === "file" && cleaned === "") return { error: "path must name a file inside the research root" };
|
|
3476
|
+
const abs = path.resolve(realRoot, cleaned);
|
|
3477
|
+
let real;
|
|
3478
|
+
try {
|
|
3479
|
+
real = await realpath(abs);
|
|
3480
|
+
} catch {
|
|
3481
|
+
return { error: `no such ${kind} under the research root: '${cleaned === "" ? "." : cleaned}'` };
|
|
3482
|
+
}
|
|
3483
|
+
if (real !== realRoot && !real.startsWith(realRoot + path.sep)) return { error: `path escapes the research root: '${rel}'` };
|
|
3484
|
+
try {
|
|
3485
|
+
const info = await stat(real);
|
|
3486
|
+
if (kind === "file" && !info.isFile()) return { error: `not a regular file: '${cleaned}'` };
|
|
3487
|
+
if (kind === "dir" && !info.isDirectory()) return { error: `not a directory: '${cleaned === "" ? "." : cleaned}'` };
|
|
3488
|
+
} catch {
|
|
3489
|
+
return { error: `no such ${kind} under the research root: '${cleaned === "" ? "." : cleaned}'` };
|
|
3490
|
+
}
|
|
3491
|
+
return {
|
|
3492
|
+
abs: real,
|
|
3493
|
+
rel: cleaned
|
|
3494
|
+
};
|
|
3495
|
+
};
|
|
3496
|
+
/**
|
|
3497
|
+
* Deterministic recursive walk: sorted entries, ignored and hidden
|
|
3498
|
+
* names skipped, symlinks never followed, regular files only. Returns
|
|
3499
|
+
* root-relative POSIX paths in byte order, or an error when the walk
|
|
3500
|
+
* exceeds maxScannedFiles.
|
|
3501
|
+
*/
|
|
3502
|
+
const walkFiles = async (absDir, relDir) => {
|
|
3503
|
+
const files = [];
|
|
3504
|
+
let visited = 0;
|
|
3505
|
+
const recurse = async (dirAbs, dirRel) => {
|
|
3506
|
+
let entries;
|
|
3507
|
+
try {
|
|
3508
|
+
entries = await readdir(dirAbs, { withFileTypes: true });
|
|
3509
|
+
} catch {
|
|
3510
|
+
return `directory disappeared during the walk: '${dirRel === "" ? "." : dirRel}'`;
|
|
3511
|
+
}
|
|
3512
|
+
const sorted = [...entries].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
3513
|
+
for (const entry of sorted) {
|
|
3514
|
+
const name = entry.name;
|
|
3515
|
+
if (ignored.has(name)) continue;
|
|
3516
|
+
if (!includeHidden && name.startsWith(".")) continue;
|
|
3517
|
+
const childRel = dirRel === "" ? name : `${dirRel}/${name}`;
|
|
3518
|
+
if (entry.isDirectory()) {
|
|
3519
|
+
const failure = await recurse(path.join(dirAbs, name), childRel);
|
|
3520
|
+
if (failure !== void 0) return failure;
|
|
3521
|
+
continue;
|
|
3522
|
+
}
|
|
3523
|
+
if (!entry.isFile()) continue;
|
|
3524
|
+
visited += 1;
|
|
3525
|
+
if (visited > maxScannedFiles) return `the walk exceeded maxScannedFiles (${String(maxScannedFiles)}); narrow dir or raise the limit`;
|
|
3526
|
+
files.push(childRel);
|
|
3527
|
+
}
|
|
3528
|
+
};
|
|
3529
|
+
const failure = await recurse(absDir, relDir);
|
|
3530
|
+
if (failure !== void 0) return { error: failure };
|
|
3531
|
+
return { files };
|
|
3532
|
+
};
|
|
3533
|
+
const loadTextFile = async (abs, rel) => {
|
|
3534
|
+
let info;
|
|
3535
|
+
try {
|
|
3536
|
+
info = await stat(abs);
|
|
3537
|
+
} catch {
|
|
3538
|
+
return { error: `no such file under the research root: '${rel}'` };
|
|
3539
|
+
}
|
|
3540
|
+
if (info.size > maxFileBytes) return { error: `file '${rel}' is ${String(info.size)} bytes, over the maxFileBytes limit (${String(maxFileBytes)})` };
|
|
3541
|
+
const buffer = await readFile(abs);
|
|
3542
|
+
if (isBinary(buffer)) return { error: `file '${rel}' is binary` };
|
|
3543
|
+
return { text: buffer.toString("utf8") };
|
|
3544
|
+
};
|
|
3545
|
+
return {
|
|
3546
|
+
tools: [
|
|
3547
|
+
tool({
|
|
3548
|
+
name: "list_files",
|
|
3549
|
+
description: "List files under a directory of the research root, recursively, in deterministic byte order, one page at a time. Returns { files, totalFiles, nextCursor? }; pass cursor to continue where the last page ended (the cursor is stable: unrelated changes never shift the boundary).",
|
|
3550
|
+
parameters: LIST_SCHEMA,
|
|
3551
|
+
risk: "read",
|
|
3552
|
+
execute: async (input) => {
|
|
3553
|
+
const params = input;
|
|
3554
|
+
let dir = params.dir ?? "";
|
|
3555
|
+
let after;
|
|
3556
|
+
if (params.cursor !== void 0) {
|
|
3557
|
+
const payload = decodeCursor(params.cursor);
|
|
3558
|
+
if (payload === void 0 || payload.t !== "list" || typeof payload.dir !== "string" || typeof payload.after !== "string" || params.dir !== void 0 && params.dir !== payload.dir) return { error: "invalid cursor: pass the nextCursor of a previous list_files page" };
|
|
3559
|
+
dir = payload.dir;
|
|
3560
|
+
after = payload.after;
|
|
3561
|
+
}
|
|
3562
|
+
const resolved = await resolveWithin(dir, "dir");
|
|
3563
|
+
if ("error" in resolved) return resolved;
|
|
3564
|
+
const walked = await walkFiles(resolved.abs, resolved.rel);
|
|
3565
|
+
if ("error" in walked) return walked;
|
|
3566
|
+
const remaining = after === void 0 ? walked.files : walked.files.filter((file) => file > after);
|
|
3567
|
+
const page = remaining.slice(0, pageSize);
|
|
3568
|
+
const more = remaining.length > pageSize;
|
|
3569
|
+
return {
|
|
3570
|
+
files: page,
|
|
3571
|
+
totalFiles: walked.files.length,
|
|
3572
|
+
...more ? { nextCursor: encodeCursor({
|
|
3573
|
+
t: "list",
|
|
3574
|
+
dir: resolved.rel,
|
|
3575
|
+
after: page[page.length - 1]
|
|
3576
|
+
}) } : {}
|
|
3577
|
+
};
|
|
3578
|
+
}
|
|
3579
|
+
}),
|
|
3580
|
+
tool({
|
|
3581
|
+
name: "search_files",
|
|
3582
|
+
description: "Search files under the research root for a literal substring (case-sensitive, never a regex), one page of matches at a time in deterministic (path, line) order. Returns { matches: [{ file, line, text }], filesScanned, filesSkipped, nextCursor? }; binary and oversized files are skipped and counted.",
|
|
3583
|
+
parameters: SEARCH_SCHEMA,
|
|
3584
|
+
risk: "read",
|
|
3585
|
+
execute: async (input) => {
|
|
3586
|
+
const params = input;
|
|
3587
|
+
let dir = params.dir ?? "";
|
|
3588
|
+
let query = params.query;
|
|
3589
|
+
let after;
|
|
3590
|
+
if (params.cursor !== void 0) {
|
|
3591
|
+
const payload = decodeCursor(params.cursor);
|
|
3592
|
+
if (payload === void 0 || payload.t !== "search" || typeof payload.query !== "string" || typeof payload.dir !== "string" || typeof payload.file !== "string" || typeof payload.line !== "number" || payload.query !== params.query || params.dir !== void 0 && params.dir !== payload.dir) return { error: "invalid cursor: pass the nextCursor of a previous search_files page with the same query and dir" };
|
|
3593
|
+
dir = payload.dir;
|
|
3594
|
+
query = payload.query;
|
|
3595
|
+
after = {
|
|
3596
|
+
file: payload.file,
|
|
3597
|
+
line: payload.line
|
|
3598
|
+
};
|
|
3599
|
+
}
|
|
3600
|
+
if (query.length === 0) return { error: "query must be a non-empty literal substring" };
|
|
3601
|
+
const resolved = await resolveWithin(dir, "dir");
|
|
3602
|
+
if ("error" in resolved) return resolved;
|
|
3603
|
+
const walked = await walkFiles(resolved.abs, resolved.rel);
|
|
3604
|
+
if ("error" in walked) return walked;
|
|
3605
|
+
const matches = [];
|
|
3606
|
+
let filesScanned = 0;
|
|
3607
|
+
let filesSkipped = 0;
|
|
3608
|
+
for (const file of walked.files) {
|
|
3609
|
+
const loaded = await loadTextFile(path.join(realRoot, file), file);
|
|
3610
|
+
if ("error" in loaded) {
|
|
3611
|
+
filesSkipped += 1;
|
|
3612
|
+
continue;
|
|
3613
|
+
}
|
|
3614
|
+
filesScanned += 1;
|
|
3615
|
+
const lines = splitLines(loaded.text);
|
|
3616
|
+
for (let index = 0; index < lines.length; index += 1) if (lines[index].includes(query)) matches.push({
|
|
3617
|
+
file,
|
|
3618
|
+
line: index + 1,
|
|
3619
|
+
text: lines[index].trim().slice(0, SEARCH_SNIPPET_CHARS)
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3622
|
+
const remaining = after === void 0 ? matches : matches.filter((match) => match.file > after.file || match.file === after.file && match.line > after.line);
|
|
3623
|
+
const page = remaining.slice(0, pageSize);
|
|
3624
|
+
const more = remaining.length > pageSize;
|
|
3625
|
+
const last = page[page.length - 1];
|
|
3626
|
+
return {
|
|
3627
|
+
matches: page,
|
|
3628
|
+
filesScanned,
|
|
3629
|
+
filesSkipped,
|
|
3630
|
+
...more && last !== void 0 ? { nextCursor: encodeCursor({
|
|
3631
|
+
t: "search",
|
|
3632
|
+
query,
|
|
3633
|
+
dir: resolved.rel,
|
|
3634
|
+
file: last.file,
|
|
3635
|
+
line: last.line
|
|
3636
|
+
}) } : {}
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
}),
|
|
3640
|
+
tool({
|
|
3641
|
+
name: "read_file",
|
|
3642
|
+
description: "Read a file of the research root as numbered lines, one page at a time (whole lines up to the page character budget). Returns { path, totalLines, fromLine, toLine, content, nextCursor? }; the same page reads byte-identically however it is addressed, so duplicate reads are visible to the exploration guards.",
|
|
3643
|
+
parameters: READ_SCHEMA,
|
|
3644
|
+
risk: "read",
|
|
3645
|
+
execute: async (input) => {
|
|
3646
|
+
const params = input;
|
|
3647
|
+
let rel = params.path;
|
|
3648
|
+
let fromLine = 1;
|
|
3649
|
+
if (params.cursor !== void 0) {
|
|
3650
|
+
const payload = decodeCursor(params.cursor);
|
|
3651
|
+
if (payload === void 0 || payload.t !== "read" || typeof payload.path !== "string" || typeof payload.after !== "number" || payload.path !== params.path) return { error: "invalid cursor: pass the nextCursor of a previous read_file page for the same path" };
|
|
3652
|
+
rel = payload.path;
|
|
3653
|
+
fromLine = payload.after + 1;
|
|
3654
|
+
}
|
|
3655
|
+
const resolved = await resolveWithin(rel, "file");
|
|
3656
|
+
if ("error" in resolved) return resolved;
|
|
3657
|
+
const loaded = await loadTextFile(resolved.abs, resolved.rel);
|
|
3658
|
+
if ("error" in loaded) return loaded;
|
|
3659
|
+
const lines = splitLines(loaded.text);
|
|
3660
|
+
const totalLines = lines.length;
|
|
3661
|
+
if (fromLine > totalLines) return { error: `fromLine ${String(fromLine)} is past the end of '${resolved.rel}' (${String(totalLines)} lines)` };
|
|
3662
|
+
const rendered = [];
|
|
3663
|
+
let used = 0;
|
|
3664
|
+
let toLine = fromLine - 1;
|
|
3665
|
+
for (let index = fromLine - 1; index < totalLines; index += 1) {
|
|
3666
|
+
const row = `${String(index + 1)}: ${lines[index]}`;
|
|
3667
|
+
if (rendered.length > 0 && used + 1 + row.length > readPageChars) break;
|
|
3668
|
+
rendered.push(row);
|
|
3669
|
+
used += (rendered.length > 1 ? 1 : 0) + row.length;
|
|
3670
|
+
toLine = index + 1;
|
|
3671
|
+
}
|
|
3672
|
+
const more = toLine < totalLines;
|
|
3673
|
+
return {
|
|
3674
|
+
path: resolved.rel,
|
|
3675
|
+
totalLines,
|
|
3676
|
+
fromLine,
|
|
3677
|
+
toLine,
|
|
3678
|
+
content: rendered.join("\n"),
|
|
3679
|
+
...more ? { nextCursor: encodeCursor({
|
|
3680
|
+
t: "read",
|
|
3681
|
+
path: resolved.rel,
|
|
3682
|
+
after: toLine
|
|
3683
|
+
}) } : {}
|
|
3684
|
+
};
|
|
3685
|
+
}
|
|
3686
|
+
}),
|
|
3687
|
+
tool({
|
|
3688
|
+
name: "record_evidence",
|
|
3689
|
+
description: "Record one evidence entry supporting a claim. The citation is VERIFIED at record time: the file must exist under the research root, lines must be a valid 1-based line or range inside it ('12' or '12-40'), and quote (when given) must appear verbatim in the file. Returns { recorded, duplicate, totalEvidence }.",
|
|
3690
|
+
parameters: RECORD_EVIDENCE_SCHEMA,
|
|
3691
|
+
risk: "read",
|
|
3692
|
+
execute: async (input) => {
|
|
3693
|
+
const params = input;
|
|
3694
|
+
if (params.claim.trim().length === 0) return { error: "claim must be a non-empty string" };
|
|
3695
|
+
const resolved = await resolveWithin(params.file, "file");
|
|
3696
|
+
if ("error" in resolved) return resolved;
|
|
3697
|
+
const loaded = await loadTextFile(resolved.abs, resolved.rel);
|
|
3698
|
+
if ("error" in loaded) return loaded;
|
|
3699
|
+
const lines = splitLines(loaded.text);
|
|
3700
|
+
if (params.lines !== void 0) {
|
|
3701
|
+
const match = /^(\d+)(?:-(\d+))?$/u.exec(params.lines);
|
|
3702
|
+
if (match === null) return { error: "lines must be '12' or '12-40' (1-based)" };
|
|
3703
|
+
const from = Number(match[1]);
|
|
3704
|
+
const to = match[2] === void 0 ? from : Number(match[2]);
|
|
3705
|
+
if (from < 1 || to < from || to > lines.length) return { error: `lines '${params.lines}' is outside '${resolved.rel}' (${String(lines.length)} lines)` };
|
|
3706
|
+
}
|
|
3707
|
+
if (params.quote !== void 0 && !loaded.text.includes(params.quote)) return { error: `quote not found verbatim in '${resolved.rel}'; cite what the file actually says` };
|
|
3708
|
+
const entry = {
|
|
3709
|
+
claim: params.claim,
|
|
3710
|
+
file: resolved.rel,
|
|
3711
|
+
...params.lines === void 0 ? {} : { lines: params.lines },
|
|
3712
|
+
...params.quote === void 0 ? {} : { quote: params.quote }
|
|
3713
|
+
};
|
|
3714
|
+
const duplicate = evidence.some((existing) => existing.claim === entry.claim && existing.file === entry.file && existing.lines === entry.lines && existing.quote === entry.quote);
|
|
3715
|
+
if (!duplicate) evidence.push(entry);
|
|
3716
|
+
return {
|
|
3717
|
+
recorded: !duplicate,
|
|
3718
|
+
duplicate,
|
|
3719
|
+
totalEvidence: evidence.length
|
|
3720
|
+
};
|
|
3721
|
+
}
|
|
3722
|
+
}),
|
|
3723
|
+
tool({
|
|
3724
|
+
name: "list_evidence",
|
|
3725
|
+
description: "List the evidence recorded so far, one page at a time in record order. Returns { evidence, totalEvidence, nextCursor? }.",
|
|
3726
|
+
parameters: LIST_EVIDENCE_SCHEMA,
|
|
3727
|
+
risk: "read",
|
|
3728
|
+
execute: (input) => {
|
|
3729
|
+
const params = input;
|
|
3730
|
+
let from = 0;
|
|
3731
|
+
if (params.cursor !== void 0) {
|
|
3732
|
+
const payload = decodeCursor(params.cursor);
|
|
3733
|
+
if (payload === void 0 || payload.t !== "evidence" || typeof payload.after !== "number") return Promise.resolve({ error: "invalid cursor: pass the nextCursor of a previous list_evidence page" });
|
|
3734
|
+
from = payload.after;
|
|
3735
|
+
}
|
|
3736
|
+
const page = evidence.slice(from, from + pageSize);
|
|
3737
|
+
const more = from + pageSize < evidence.length;
|
|
3738
|
+
return Promise.resolve({
|
|
3739
|
+
evidence: page,
|
|
3740
|
+
totalEvidence: evidence.length,
|
|
3741
|
+
...more ? { nextCursor: encodeCursor({
|
|
3742
|
+
t: "evidence",
|
|
3743
|
+
after: from + pageSize
|
|
3744
|
+
}) } : {}
|
|
3745
|
+
});
|
|
3746
|
+
}
|
|
3747
|
+
})
|
|
3748
|
+
],
|
|
3749
|
+
evidence: () => evidence.map((entry) => ({ ...entry }))
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3752
|
+
//#endregion
|
|
3753
|
+
//#region src/tools/progress.ts
|
|
3754
|
+
/** The stock progress tool name the engine scans terminals for. */
|
|
3755
|
+
const PROGRESS_REPORT_TOOL_NAME = "report_progress";
|
|
3756
|
+
const PROGRESS_SCHEMA = {
|
|
3757
|
+
type: "object",
|
|
3758
|
+
additionalProperties: false,
|
|
3759
|
+
required: ["facts"],
|
|
3760
|
+
properties: {
|
|
3761
|
+
facts: {
|
|
3762
|
+
type: "array",
|
|
3763
|
+
items: { type: "string" },
|
|
3764
|
+
description: "New facts established since the last report; may be empty early on."
|
|
3765
|
+
},
|
|
3766
|
+
evidence: {
|
|
3767
|
+
type: "array",
|
|
3768
|
+
items: { type: "string" },
|
|
3769
|
+
description: "Evidence references backing the facts (file:line or recorded evidence ids)."
|
|
3770
|
+
},
|
|
3771
|
+
questions: {
|
|
3772
|
+
type: "array",
|
|
3773
|
+
items: { type: "string" },
|
|
3774
|
+
description: "Remaining unresolved questions."
|
|
3775
|
+
},
|
|
3776
|
+
note: {
|
|
3777
|
+
type: "string",
|
|
3778
|
+
description: "Optional short status note."
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
};
|
|
3782
|
+
/**
|
|
3783
|
+
* The stock progress-report tool. Stateless and deterministic: the
|
|
3784
|
+
* result echoes the counts, so a verbatim repeated report is a
|
|
3785
|
+
* duplicate result digest to the exploration guards. The value is the
|
|
3786
|
+
* side contract: the engine captures the LAST successful call of this
|
|
3787
|
+
* tool as the structured terminal partial of a 'limit' invocation, so
|
|
3788
|
+
* an agent that reports after every batch never loses its collected
|
|
3789
|
+
* work to a budget expiry.
|
|
3790
|
+
*/
|
|
3791
|
+
function progressReportTool() {
|
|
3792
|
+
return tool({
|
|
3793
|
+
name: PROGRESS_REPORT_TOOL_NAME,
|
|
3794
|
+
description: "Report research progress after every batch of tool calls: the new facts you established, the evidence references backing them, and the questions still open. If the invocation ends at a limit, your LAST report is returned to the caller as the structured partial result, so report before the budget runs out.",
|
|
3795
|
+
parameters: PROGRESS_SCHEMA,
|
|
3796
|
+
risk: "read",
|
|
3797
|
+
execute: (input) => {
|
|
3798
|
+
const report = input;
|
|
3799
|
+
return Promise.resolve({
|
|
3800
|
+
recorded: true,
|
|
3801
|
+
facts: report.facts?.length ?? 0,
|
|
3802
|
+
evidence: report.evidence?.length ?? 0,
|
|
3803
|
+
questions: report.questions?.length ?? 0
|
|
3804
|
+
});
|
|
3805
|
+
}
|
|
3806
|
+
});
|
|
3807
|
+
}
|
|
3808
|
+
function stringArray(value) {
|
|
3809
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
3810
|
+
}
|
|
3811
|
+
/**
|
|
3812
|
+
* The deterministic terminal scan: pairs `report_progress` tool calls
|
|
3813
|
+
* with their SUCCESSFUL results by id (a denied or failed call never
|
|
3814
|
+
* counts, mirroring the exploration guard's restore) and normalizes the
|
|
3815
|
+
* last one into a {@link ProgressReport}. Pure over the message window
|
|
3816
|
+
* it is given: the live loop hands its own history, the replay path
|
|
3817
|
+
* hands the terminal checkpoint's messages, and a compaction naturally
|
|
3818
|
+
* narrows the window to what the model itself still sees.
|
|
3819
|
+
*/
|
|
3820
|
+
function latestProgressReport(messages) {
|
|
3821
|
+
const callsById = /* @__PURE__ */ new Map();
|
|
3822
|
+
let latest;
|
|
3823
|
+
for (const msg of messages) for (const part of msg.parts) if (part.type === "tool-call" && part.name === "report_progress") callsById.set(part.id, part.args);
|
|
3824
|
+
else if (part.type === "tool-result" && part.name === "report_progress" && part.isError !== true && callsById.has(part.id)) {
|
|
3825
|
+
const args = callsById.get(part.id);
|
|
3826
|
+
if (typeof args === "object" && args !== null && !Array.isArray(args)) {
|
|
3827
|
+
const record = args;
|
|
3828
|
+
const report = {
|
|
3829
|
+
facts: stringArray(record.facts),
|
|
3830
|
+
evidence: stringArray(record.evidence),
|
|
3831
|
+
questions: stringArray(record.questions)
|
|
3832
|
+
};
|
|
3833
|
+
if (typeof record.note === "string") report.note = record.note;
|
|
3834
|
+
latest = report;
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
return latest;
|
|
3838
|
+
}
|
|
3839
|
+
//#endregion
|
|
3840
|
+
//#region src/engine/profile-templates.ts
|
|
3841
|
+
/**
|
|
3842
|
+
* The research template's stop conditions: a weighted unit budget over
|
|
3843
|
+
* the research tools (bookkeeping tools are free), per-tool caps, both
|
|
3844
|
+
* repetition guards, and soft budget notices. Exported so hosts and
|
|
3845
|
+
* tests can read the exact defaults they are overriding.
|
|
3846
|
+
*/
|
|
3847
|
+
const RESEARCH_PROFILE_LIMITS = {
|
|
3848
|
+
maxTurns: 24,
|
|
3849
|
+
maxToolCalls: 48,
|
|
3850
|
+
toolBudgetNotices: true,
|
|
3851
|
+
maxRepeatedToolSignature: 2,
|
|
3852
|
+
maxNoNewEvidenceCalls: 6,
|
|
3853
|
+
maxCallsPerTool: {
|
|
3854
|
+
list_files: 12,
|
|
3855
|
+
search_files: 20,
|
|
3856
|
+
read_file: 30
|
|
3857
|
+
},
|
|
3858
|
+
toolUnits: {
|
|
3859
|
+
max: 64,
|
|
3860
|
+
costs: {
|
|
3861
|
+
list_files: 1,
|
|
3862
|
+
search_files: 2,
|
|
3863
|
+
read_file: 2,
|
|
3864
|
+
record_evidence: 0,
|
|
3865
|
+
list_evidence: 0,
|
|
3866
|
+
report_progress: 0
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
};
|
|
3870
|
+
/** The implementation template's stop conditions. */
|
|
3871
|
+
const IMPLEMENTATION_PROFILE_LIMITS = {
|
|
3872
|
+
maxTurns: 32,
|
|
3873
|
+
maxToolCalls: 64,
|
|
3874
|
+
toolBudgetNotices: true,
|
|
3875
|
+
maxRepeatedToolSignature: 3,
|
|
3876
|
+
noProgressTurns: 3
|
|
3877
|
+
};
|
|
3878
|
+
/** The review template's stop conditions. */
|
|
3879
|
+
const REVIEW_PROFILE_LIMITS = {
|
|
3880
|
+
maxTurns: 16,
|
|
3881
|
+
maxToolCalls: 32,
|
|
3882
|
+
toolBudgetNotices: true,
|
|
3883
|
+
maxRepeatedToolSignature: 2,
|
|
3884
|
+
maxNoNewEvidenceCalls: 8
|
|
3885
|
+
};
|
|
3886
|
+
function mergeLimits(template, overrides) {
|
|
3887
|
+
return {
|
|
3888
|
+
...template,
|
|
3889
|
+
...overrides ?? {}
|
|
3890
|
+
};
|
|
3891
|
+
}
|
|
3892
|
+
/**
|
|
3893
|
+
* The batteries-included research child: the confined
|
|
3894
|
+
* {@link repositoryResearchToolset} over `root`, the stock
|
|
3895
|
+
* report_progress tool, and {@link RESEARCH_PROFILE_LIMITS} as the stop
|
|
3896
|
+
* conditions. A child spawned from this profile that runs out of budget
|
|
3897
|
+
* settles 'limit' WITH its last progress report as the structured
|
|
3898
|
+
* partial, and the recorded evidence stays readable host-side through
|
|
3899
|
+
* `evidence()`.
|
|
3900
|
+
*/
|
|
3901
|
+
function researchAgentProfile(options) {
|
|
3902
|
+
const { description, limits, extraTools, ...toolsetOptions } = options;
|
|
3903
|
+
const kit = repositoryResearchToolset(toolsetOptions);
|
|
3904
|
+
return {
|
|
3905
|
+
profile: {
|
|
3906
|
+
description: description ?? "Repository research over a confined root: paginated list_files/search_files/read_file with stable cursors, record_evidence verifying every citation, and report_progress after every batch. Stop conditions built in: weighted tool units, per-tool caps, repetition and no-new-evidence guards, budget notices. On limit the last progress report is the structured partial.",
|
|
3907
|
+
tools: [
|
|
3908
|
+
...kit.tools,
|
|
3909
|
+
progressReportTool(),
|
|
3910
|
+
...extraTools ?? []
|
|
3911
|
+
],
|
|
3912
|
+
limits: mergeLimits(RESEARCH_PROFILE_LIMITS, limits)
|
|
3913
|
+
},
|
|
3914
|
+
evidence: () => kit.evidence()
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
3917
|
+
/**
|
|
3918
|
+
* The implementation child template: the caller's task tools plus the
|
|
3919
|
+
* progress contract, with {@link IMPLEMENTATION_PROFILE_LIMITS} as the
|
|
3920
|
+
* stop conditions (a no-progress detector instead of the research
|
|
3921
|
+
* no-new-evidence guard: implementation legitimately re-reads state).
|
|
3922
|
+
*/
|
|
3923
|
+
function implementationAgentProfile(options = {}) {
|
|
3924
|
+
return {
|
|
3925
|
+
description: options.description ?? "Implementation work with built-in stop conditions: tool budget with notices, repeated-call guard, no-progress detector. Report progress with report_progress after every batch; on limit the last report is the structured partial.",
|
|
3926
|
+
tools: [progressReportTool(), ...options.tools ?? []],
|
|
3927
|
+
limits: mergeLimits(IMPLEMENTATION_PROFILE_LIMITS, options.limits)
|
|
3928
|
+
};
|
|
3929
|
+
}
|
|
3930
|
+
/**
|
|
3931
|
+
* The review child template: the caller's task tools plus the progress
|
|
3932
|
+
* contract, with {@link REVIEW_PROFILE_LIMITS} as the stop conditions
|
|
3933
|
+
* (a tighter turn budget and the no-new-evidence guard: a reviewer
|
|
3934
|
+
* circling over the same pages should stop, not spin).
|
|
3935
|
+
*/
|
|
3936
|
+
function reviewAgentProfile(options = {}) {
|
|
3937
|
+
return {
|
|
3938
|
+
description: options.description ?? "Focused review with built-in stop conditions: tight turn and tool budgets with notices, repetition and no-new-evidence guards. Report findings with report_progress after every batch; on limit the last report is the structured partial.",
|
|
3939
|
+
tools: [progressReportTool(), ...options.tools ?? []],
|
|
3940
|
+
limits: mergeLimits(REVIEW_PROFILE_LIMITS, options.limits)
|
|
3941
|
+
};
|
|
3942
|
+
}
|
|
3943
|
+
//#endregion
|
|
3300
3944
|
//#region src/journal/identity.ts
|
|
3301
3945
|
/**
|
|
3302
3946
|
* Content-addressed entry identity (M1-T04): IdentityInput records per
|
|
@@ -8008,6 +8652,10 @@ function mergeUsageLimits(call, profile, engine) {
|
|
|
8008
8652
|
if (maxRepeatedToolSignature !== void 0) merged.maxRepeatedToolSignature = maxRepeatedToolSignature;
|
|
8009
8653
|
const maxNoNewEvidenceCalls = pick("maxNoNewEvidenceCalls");
|
|
8010
8654
|
if (maxNoNewEvidenceCalls !== void 0) merged.maxNoNewEvidenceCalls = maxNoNewEvidenceCalls;
|
|
8655
|
+
const maxCallsPerTool = pick("maxCallsPerTool");
|
|
8656
|
+
if (maxCallsPerTool !== void 0) merged.maxCallsPerTool = maxCallsPerTool;
|
|
8657
|
+
const toolUnits = pick("toolUnits");
|
|
8658
|
+
if (toolUnits !== void 0) merged.toolUnits = toolUnits;
|
|
8011
8659
|
return merged;
|
|
8012
8660
|
}
|
|
8013
8661
|
/**
|
|
@@ -8032,6 +8680,21 @@ function validateUsageLimits(limits, site) {
|
|
|
8032
8680
|
if (limits.toolBudgetNotices !== void 0 && typeof limits.toolBudgetNotices !== "boolean") throw new ConfigError(`${site}.toolBudgetNotices must be a boolean; got ${typeof limits.toolBudgetNotices}`);
|
|
8033
8681
|
if (limits.maxRepeatedToolSignature !== void 0) requirePositiveInteger(limits.maxRepeatedToolSignature, `${site}.maxRepeatedToolSignature`);
|
|
8034
8682
|
if (limits.maxNoNewEvidenceCalls !== void 0) requirePositiveInteger(limits.maxNoNewEvidenceCalls, `${site}.maxNoNewEvidenceCalls`);
|
|
8683
|
+
if (limits.maxCallsPerTool !== void 0) {
|
|
8684
|
+
const caps = limits.maxCallsPerTool;
|
|
8685
|
+
if (typeof caps !== "object" || caps === null || Array.isArray(caps)) throw new ConfigError(`${site}.maxCallsPerTool must be a record of per-tool caps`);
|
|
8686
|
+
for (const [name, cap] of Object.entries(caps)) requireNonNegativeInteger(cap, `${site}.maxCallsPerTool['${name}']`);
|
|
8687
|
+
}
|
|
8688
|
+
if (limits.toolUnits !== void 0) {
|
|
8689
|
+
const units = limits.toolUnits;
|
|
8690
|
+
if (typeof units !== "object" || units === null || Array.isArray(units)) throw new ConfigError(`${site}.toolUnits must be { max, costs? }`);
|
|
8691
|
+
const { max, costs } = units;
|
|
8692
|
+
requirePositiveInteger(max, `${site}.toolUnits.max`);
|
|
8693
|
+
if (costs !== void 0) {
|
|
8694
|
+
if (typeof costs !== "object" || costs === null || Array.isArray(costs)) throw new ConfigError(`${site}.toolUnits.costs must be a record of per-tool costs`);
|
|
8695
|
+
for (const [name, cost] of Object.entries(costs)) requireNonNegativeInteger(cost, `${site}.toolUnits.costs['${name}']`);
|
|
8696
|
+
}
|
|
8697
|
+
}
|
|
8035
8698
|
}
|
|
8036
8699
|
//#endregion
|
|
8037
8700
|
//#region src/runtime/model-retry.ts
|
|
@@ -8566,7 +9229,7 @@ function formatRePrompt(issues, attempt, maxAttempts) {
|
|
|
8566
9229
|
const GUARD_DOCS_URL = "https://docs.rulvar.com/guide/agents#exploration-guards";
|
|
8567
9230
|
/** True when any exploration guard field asks for tracking. */
|
|
8568
9231
|
function explorationTrackingEnabled(limits) {
|
|
8569
|
-
return limits.maxRepeatedToolSignature !== void 0 || limits.maxNoNewEvidenceCalls !== void 0 || limits.toolBudgetNotices === true;
|
|
9232
|
+
return limits.maxRepeatedToolSignature !== void 0 || limits.maxNoNewEvidenceCalls !== void 0 || limits.toolBudgetNotices === true || limits.maxCallsPerTool !== void 0 || limits.toolUnits !== void 0;
|
|
8570
9233
|
}
|
|
8571
9234
|
function digestOf$1(value) {
|
|
8572
9235
|
try {
|
|
@@ -8585,6 +9248,8 @@ var ExplorationGuard = class {
|
|
|
8585
9248
|
repeated = 0;
|
|
8586
9249
|
duplicateResults = 0;
|
|
8587
9250
|
denied = 0;
|
|
9251
|
+
deniedToolCap = 0;
|
|
9252
|
+
unitsUsed = 0;
|
|
8588
9253
|
unserializableSeq = 0;
|
|
8589
9254
|
constructor(config) {
|
|
8590
9255
|
this.config = config;
|
|
@@ -8622,10 +9287,25 @@ var ExplorationGuard = class {
|
|
|
8622
9287
|
}
|
|
8623
9288
|
}
|
|
8624
9289
|
/**
|
|
8625
|
-
* The pre-dispatch verdict: denies the call that would exceed
|
|
8626
|
-
*
|
|
9290
|
+
* The pre-dispatch verdict: denies the call that would exceed its
|
|
9291
|
+
* tool's maxCallsPerTool cap, then the call that would exceed
|
|
9292
|
+
* maxRepeatedToolSignature executions of the same signature. A denial
|
|
9293
|
+
* never consumes maxToolCalls or tool units.
|
|
8627
9294
|
*/
|
|
8628
9295
|
beforeExecute(name, args) {
|
|
9296
|
+
const cap = this.config.maxCallsPerTool?.[name];
|
|
9297
|
+
if (cap !== void 0) {
|
|
9298
|
+
const executions = this.byTool.get(name) ?? 0;
|
|
9299
|
+
if (executions >= cap) {
|
|
9300
|
+
this.deniedToolCap += 1;
|
|
9301
|
+
return {
|
|
9302
|
+
deny: true,
|
|
9303
|
+
guard: "per-tool-cap",
|
|
9304
|
+
executions,
|
|
9305
|
+
reason: `exploration guard: '${name}' already executed ${String(executions)} time(s) this invocation (maxCallsPerTool ${String(cap)}). Use what you have or a different tool (${GUARD_DOCS_URL}).`
|
|
9306
|
+
};
|
|
9307
|
+
}
|
|
9308
|
+
}
|
|
8629
9309
|
const max = this.config.maxRepeatedToolSignature;
|
|
8630
9310
|
if (max === void 0) return { deny: false };
|
|
8631
9311
|
const executions = this.signatureExecutions.get(this.signatureOf(name, args)) ?? 0;
|
|
@@ -8633,6 +9313,7 @@ var ExplorationGuard = class {
|
|
|
8633
9313
|
this.denied += 1;
|
|
8634
9314
|
return {
|
|
8635
9315
|
deny: true,
|
|
9316
|
+
guard: "repeated-signature",
|
|
8636
9317
|
executions,
|
|
8637
9318
|
reason: `exploration guard: this exact '${name}' call already executed ${String(executions)} time(s) this invocation (maxRepeatedToolSignature ${String(max)}). Reuse the earlier result or change the arguments (${GUARD_DOCS_URL}).`
|
|
8638
9319
|
};
|
|
@@ -8650,6 +9331,7 @@ var ExplorationGuard = class {
|
|
|
8650
9331
|
recordExecution(name, args, result, successful) {
|
|
8651
9332
|
this.executed += 1;
|
|
8652
9333
|
this.byTool.set(name, (this.byTool.get(name) ?? 0) + 1);
|
|
9334
|
+
if (this.config.toolUnits !== void 0) this.unitsUsed += this.config.toolUnits.costs?.[name] ?? 1;
|
|
8653
9335
|
const signature = this.signatureOf(name, args);
|
|
8654
9336
|
const prior = this.signatureExecutions.get(signature) ?? 0;
|
|
8655
9337
|
if (prior > 0) this.repeated += 1;
|
|
@@ -8666,6 +9348,14 @@ var ExplorationGuard = class {
|
|
|
8666
9348
|
const max = this.config.maxNoNewEvidenceCalls;
|
|
8667
9349
|
return max !== void 0 && this.noNewEvidenceStreak >= max;
|
|
8668
9350
|
}
|
|
9351
|
+
/**
|
|
9352
|
+
* True once the spent tool units reached the weighted budget: the
|
|
9353
|
+
* loop's pre-dispatch check, mirroring maxToolCalls (terminal 'limit',
|
|
9354
|
+
* paid partial work). Never true without toolUnits configured.
|
|
9355
|
+
*/
|
|
9356
|
+
unitsExhausted() {
|
|
9357
|
+
return this.config.toolUnits !== void 0 && this.unitsUsed >= this.config.toolUnits.max;
|
|
9358
|
+
}
|
|
8669
9359
|
/** The abort message for a tripped no-new-evidence guard. */
|
|
8670
9360
|
describeTrip() {
|
|
8671
9361
|
return `exploration guard: ${String(this.noNewEvidenceStreak)} consecutive tool calls returned no new evidence (maxNoNewEvidenceCalls ${String(this.config.maxNoNewEvidenceCalls ?? this.noNewEvidenceStreak)}; every result was already seen this invocation). The executed work is kept; narrow the scope, vary the queries, or raise the limit (${GUARD_DOCS_URL}).`;
|
|
@@ -8680,7 +9370,9 @@ var ExplorationGuard = class {
|
|
|
8680
9370
|
repeatedCalls: this.repeated,
|
|
8681
9371
|
duplicateResultCalls: this.duplicateResults,
|
|
8682
9372
|
deniedRepeats: this.denied,
|
|
8683
|
-
byTool
|
|
9373
|
+
byTool,
|
|
9374
|
+
...this.config.maxCallsPerTool === void 0 ? {} : { deniedToolCap: this.deniedToolCap },
|
|
9375
|
+
...this.config.toolUnits === void 0 ? {} : { toolUnitsUsed: this.unitsUsed }
|
|
8684
9376
|
};
|
|
8685
9377
|
}
|
|
8686
9378
|
};
|
|
@@ -9290,6 +9982,10 @@ async function runAgent(options) {
|
|
|
9290
9982
|
parts,
|
|
9291
9983
|
limitHit: true
|
|
9292
9984
|
};
|
|
9985
|
+
if (guard !== void 0 && guard.unitsExhausted()) return {
|
|
9986
|
+
parts,
|
|
9987
|
+
limitHit: true
|
|
9988
|
+
};
|
|
9293
9989
|
const def = runtime.defs.find((candidate) => candidate.name === call.name);
|
|
9294
9990
|
events?.emit({
|
|
9295
9991
|
type: "tool:start",
|
|
@@ -9445,11 +10141,11 @@ async function runAgent(options) {
|
|
|
9445
10141
|
toolName: gatedCall.name,
|
|
9446
10142
|
outcome: "denied",
|
|
9447
10143
|
durationMs: now() - gateStartedAt,
|
|
9448
|
-
guard:
|
|
10144
|
+
guard: guardVerdict.guard
|
|
9449
10145
|
});
|
|
9450
10146
|
parts.push(errorPart(call, {
|
|
9451
10147
|
error: guardVerdict.reason,
|
|
9452
|
-
guard:
|
|
10148
|
+
guard: guardVerdict.guard
|
|
9453
10149
|
}));
|
|
9454
10150
|
continue;
|
|
9455
10151
|
}
|
|
@@ -10298,6 +10994,8 @@ async function runAgent(options) {
|
|
|
10298
10994
|
}
|
|
10299
10995
|
endPhase(extractPhase, phaseOutcome(), extractServed);
|
|
10300
10996
|
}
|
|
10997
|
+
const limitPartial = status === "limit" ? latestProgressReport(messages) : void 0;
|
|
10998
|
+
if (limitPartial !== void 0) await saveBoundary();
|
|
10301
10999
|
let transcriptRef = "";
|
|
10302
11000
|
if (options.transcript !== void 0) {
|
|
10303
11001
|
transcriptRef = options.transcript.mintRef();
|
|
@@ -10320,6 +11018,7 @@ async function runAgent(options) {
|
|
|
10320
11018
|
if (abortClass !== void 0) result.abortClass = abortClass;
|
|
10321
11019
|
if (errorMessage !== void 0) result.errorMessage = errorMessage;
|
|
10322
11020
|
if (guard !== void 0) result.exploration = guard.summary(toolCallsUsed);
|
|
11021
|
+
if (limitPartial !== void 0) result.partial = limitPartial;
|
|
10323
11022
|
if (usageApprox) result.usageApprox = true;
|
|
10324
11023
|
if (transportRetries > 0) result.transportRetries = transportRetries;
|
|
10325
11024
|
return result;
|
|
@@ -11291,7 +11990,13 @@ const WAKE_SUMMARY_RENDER_BUDGET_CHARS = 400;
|
|
|
11291
11990
|
* spawn ordinal; the LLM distillation upgrade is M7 territory).
|
|
11292
11991
|
*/
|
|
11293
11992
|
function summarizeOutput(result) {
|
|
11294
|
-
|
|
11993
|
+
let raw;
|
|
11994
|
+
if (result.status === "ok") raw = typeof result.output === "string" ? result.output : JSON.stringify(result.output ?? null);
|
|
11995
|
+
else {
|
|
11996
|
+
raw = result.errorMessage ?? `terminal status ${result.status}`;
|
|
11997
|
+
if (result.partial !== void 0) raw = `${raw}; partial: ${JSON.stringify(result.partial)}`;
|
|
11998
|
+
}
|
|
11999
|
+
return truncateToBudget(raw, 400);
|
|
11295
12000
|
}
|
|
11296
12001
|
/** Folds one settled child into its digest (spawn-ordinal ordering is the caller's). */
|
|
11297
12002
|
function digestOf(record, result) {
|
|
@@ -12164,6 +12869,10 @@ function createCtx(internals, rootWorkflow) {
|
|
|
12164
12869
|
const checkpoint = blob === null ? void 0 : decodeCheckpoint(blob);
|
|
12165
12870
|
if (checkpoint !== void 0) {
|
|
12166
12871
|
result.turns = checkpoint.turns;
|
|
12872
|
+
if (result.status === "limit") {
|
|
12873
|
+
const partialReport = latestProgressReport(checkpoint.messages);
|
|
12874
|
+
if (partialReport !== void 0) result.partial = partialReport;
|
|
12875
|
+
}
|
|
12167
12876
|
replayedToolResults = checkpoint.messages.filter((msg) => msg.role === "tool").flatMap((msg) => msg.parts).filter((part) => part.type === "tool-result").map((part) => ({
|
|
12168
12877
|
name: part.name,
|
|
12169
12878
|
isError: part.isError === true
|
|
@@ -13265,6 +13974,52 @@ async function executeWorkflow(internals, wf, args) {
|
|
|
13265
13974
|
}
|
|
13266
13975
|
}
|
|
13267
13976
|
//#endregion
|
|
13977
|
+
//#region src/orchestrator/claims.ts
|
|
13978
|
+
/** The conservative matching key: trim plus inner-whitespace collapse. */
|
|
13979
|
+
function claimKey(line) {
|
|
13980
|
+
return line.trim().replace(/\s+/gu, " ");
|
|
13981
|
+
}
|
|
13982
|
+
/**
|
|
13983
|
+
* Removes later occurrences of repeated claim lines across the rows and
|
|
13984
|
+
* indexes each repeated claim with its reporters. Deterministic: output
|
|
13985
|
+
* depends only on the input order and bytes.
|
|
13986
|
+
*/
|
|
13987
|
+
function dedupeRepeatedClaims(rows) {
|
|
13988
|
+
const seen = /* @__PURE__ */ new Map();
|
|
13989
|
+
const order = [];
|
|
13990
|
+
return {
|
|
13991
|
+
rows: rows.map((row) => {
|
|
13992
|
+
const kept = [];
|
|
13993
|
+
for (const line of row.text.split("\n")) {
|
|
13994
|
+
const key = claimKey(line);
|
|
13995
|
+
if (key === "") {
|
|
13996
|
+
kept.push(line);
|
|
13997
|
+
continue;
|
|
13998
|
+
}
|
|
13999
|
+
const prior = seen.get(key);
|
|
14000
|
+
if (prior === void 0) {
|
|
14001
|
+
const entry = {
|
|
14002
|
+
claim: line,
|
|
14003
|
+
nodeIds: [row.nodeId],
|
|
14004
|
+
count: 1
|
|
14005
|
+
};
|
|
14006
|
+
seen.set(key, entry);
|
|
14007
|
+
order.push(entry);
|
|
14008
|
+
kept.push(line);
|
|
14009
|
+
continue;
|
|
14010
|
+
}
|
|
14011
|
+
prior.count += 1;
|
|
14012
|
+
if (!prior.nodeIds.includes(row.nodeId)) prior.nodeIds.push(row.nodeId);
|
|
14013
|
+
}
|
|
14014
|
+
return {
|
|
14015
|
+
nodeId: row.nodeId,
|
|
14016
|
+
text: kept.join("\n")
|
|
14017
|
+
};
|
|
14018
|
+
}),
|
|
14019
|
+
repeated: order.filter((entry) => entry.count > 1)
|
|
14020
|
+
};
|
|
14021
|
+
}
|
|
14022
|
+
//#endregion
|
|
13268
14023
|
//#region src/orchestrator/orchestrate.ts
|
|
13269
14024
|
/**
|
|
13270
14025
|
* The mode (c) dynamic orchestrator (M6-T07/T08).
|
|
@@ -13291,6 +14046,12 @@ const DEFAULT_FINISH_MAX_REPAIRS = 1;
|
|
|
13291
14046
|
* call plus headroom for one validator repair exchange.
|
|
13292
14047
|
*/
|
|
13293
14048
|
const DEFAULT_SYNTHESIS_MAX_TURNS = 4;
|
|
14049
|
+
/**
|
|
14050
|
+
* Default maxTurns of ONE incremental synthesis note (RV-211 remainder):
|
|
14051
|
+
* a note summarizes a single settled child into a bounded finish call,
|
|
14052
|
+
* so it needs less headroom than the full synthesis invocation.
|
|
14053
|
+
*/
|
|
14054
|
+
const DEFAULT_SYNTHESIS_NOTE_MAX_TURNS = 2;
|
|
13294
14055
|
const ORCHESTRATE_WORKFLOW_NAME = "rulvar-orchestrate";
|
|
13295
14056
|
/**
|
|
13296
14057
|
* One page of a string, for the child result evidence tools: maxChars is
|
|
@@ -13312,7 +14073,14 @@ function pageOf(content, rawOffset, rawMaxChars) {
|
|
|
13312
14073
|
}
|
|
13313
14074
|
/** The serialized full result of a settled child: the raw string, or JSON. */
|
|
13314
14075
|
function serializeChildOutput(result) {
|
|
13315
|
-
if (result.status !== "ok")
|
|
14076
|
+
if (result.status !== "ok") {
|
|
14077
|
+
const base = result.errorMessage ?? `terminal status ${result.status}`;
|
|
14078
|
+
if (result.partial !== void 0) return JSON.stringify({
|
|
14079
|
+
error: base,
|
|
14080
|
+
partial: result.partial
|
|
14081
|
+
});
|
|
14082
|
+
return base;
|
|
14083
|
+
}
|
|
13316
14084
|
return typeof result.output === "string" ? result.output : JSON.stringify(result.output ?? null);
|
|
13317
14085
|
}
|
|
13318
14086
|
/**
|
|
@@ -13334,6 +14102,8 @@ function validateOrchestrateOptions(opts) {
|
|
|
13334
14102
|
const minSuccessful = typeof policy === "object" && policy !== null && !Array.isArray(policy) ? policy.minSuccessful : void 0;
|
|
13335
14103
|
if (policy !== "all-ok" && minSuccessful === void 0) throw new ConfigError(`orchestrate acceptance.childPolicy must be 'all-ok' or { minSuccessful: N }; got ${JSON.stringify(policy)}`);
|
|
13336
14104
|
if (policy !== "all-ok") requirePositiveInteger(minSuccessful, "orchestrate acceptance.childPolicy.minSuccessful");
|
|
14105
|
+
const acceptPartial = opts.acceptance.acceptPartialChildren;
|
|
14106
|
+
if (acceptPartial !== void 0 && typeof acceptPartial !== "boolean") throw new ConfigError(`orchestrate acceptance.acceptPartialChildren must be a boolean; got ${typeof acceptPartial}`);
|
|
13337
14107
|
}
|
|
13338
14108
|
if (opts.finishValidation !== void 0) {
|
|
13339
14109
|
const fv = opts.finishValidation;
|
|
@@ -13350,6 +14120,10 @@ function validateOrchestrateOptions(opts) {
|
|
|
13350
14120
|
}
|
|
13351
14121
|
if (opts.synthesis !== void 0) {
|
|
13352
14122
|
const synthesis = opts.synthesis;
|
|
14123
|
+
if (synthesis.mode !== void 0 && synthesis.mode !== "single" && synthesis.mode !== "incremental") throw new ConfigError("orchestrate synthesis.mode must be 'single' or 'incremental'; got " + JSON.stringify(synthesis.mode));
|
|
14124
|
+
if (synthesis.mode === "incremental" && opts.finishValidation !== void 0) throw new ConfigError("orchestrate synthesis.mode 'incremental' reconciles deterministically and has no model-composed final finish for finishValidation to bind; configure validators with mode 'single', or drop them");
|
|
14125
|
+
if (synthesis.dedupeClaims !== void 0 && typeof synthesis.dedupeClaims !== "boolean") throw new ConfigError("orchestrate synthesis.dedupeClaims must be a boolean; got " + typeof synthesis.dedupeClaims);
|
|
14126
|
+
if (synthesis.noteLimits !== void 0) validateUsageLimits(synthesis.noteLimits, "orchestrate synthesis.noteLimits");
|
|
13353
14127
|
if (synthesis.effort !== void 0 && ![
|
|
13354
14128
|
"low",
|
|
13355
14129
|
"medium",
|
|
@@ -13394,6 +14168,16 @@ function finishValidationPromptLines(spec) {
|
|
|
13394
14168
|
return [`The host validates every finish({ result }) with deterministic validators: ${names}.`, "A rejected finish returns the failure reasons as the tool error result; repair the result and call finish again. " + (repairs === 0 ? "No repair attempt is granted: the first rejected finish fails the run." : repairs === 1 ? "At most one repair attempt is granted before the run fails." : `At most ${String(repairs)} repair attempts are granted before the run fails.`)];
|
|
13395
14169
|
}
|
|
13396
14170
|
/**
|
|
14171
|
+
* The partial-salvage contract rides the PROMPT exactly like finish
|
|
14172
|
+
* validation (RV-210 close-out): present only when
|
|
14173
|
+
* acceptance.acceptPartialChildren is set, so every other configuration
|
|
14174
|
+
* keeps byte-identical coordination prompts.
|
|
14175
|
+
*/
|
|
14176
|
+
function acceptancePromptLines(acceptance) {
|
|
14177
|
+
if (acceptance?.acceptPartialChildren !== true) return [];
|
|
14178
|
+
return ["Partial salvage is on: a child that ends at its limit AFTER recording progress with report_progress counts as a partial success for acceptance; its digest carries the partial and get_child_result (when enabled) pages the full report. When the gap matters, respawn a NARROWED child carrying the partial instead of repeating the task."];
|
|
14179
|
+
}
|
|
14180
|
+
/**
|
|
13397
14181
|
* Resolves per-spawn dispatch options against the engine registries
|
|
13398
14182
|
* (registered SchemaSpec and tool profile names; M7-T05). An
|
|
13399
14183
|
* unknown ref is a typed ConfigError, surfaced as a tool error to the
|
|
@@ -13551,6 +14335,22 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
13551
14335
|
const recoveryDone = new Promise((resolve) => {
|
|
13552
14336
|
releaseRecovery = resolve;
|
|
13553
14337
|
});
|
|
14338
|
+
/**
|
|
14339
|
+
* Incremental synthesis notes (RV-211 remainder): one bounded
|
|
14340
|
+
* synthesize-role invocation per settled child, keyed by nodeId so a
|
|
14341
|
+
* note can never double-dispatch. The settle hook fires the note the
|
|
14342
|
+
* moment its child settles (overlapping the still-running fan-out);
|
|
14343
|
+
* the deterministic reconciliation is the completeness backstop and
|
|
14344
|
+
* dispatches any note the hook missed. The dispatcher installs right
|
|
14345
|
+
* before the coordination loop because it closes over runtime pieces
|
|
14346
|
+
* built below; these bindings are declared HERE, before
|
|
14347
|
+
* dispatchChild, so a recovered child's settle hook (which can fire
|
|
14348
|
+
* during the recovery scan) never touches a binding in its temporal
|
|
14349
|
+
* dead zone.
|
|
14350
|
+
*/
|
|
14351
|
+
const synthesisNotes = /* @__PURE__ */ new Map();
|
|
14352
|
+
let synthesisNoteDispatcher;
|
|
14353
|
+
let synthesisSettleFrozen = false;
|
|
13554
14354
|
let activityChain = Promise.resolve();
|
|
13555
14355
|
const childScopeOf = () => {
|
|
13556
14356
|
if (orchSeq === void 0) throw new ConfigError("orchestrator dispatch seq unknown before the loop started");
|
|
@@ -13636,6 +14436,7 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
13636
14436
|
};
|
|
13637
14437
|
settledResult.then(async (settled) => {
|
|
13638
14438
|
record.settled = settled;
|
|
14439
|
+
if (!synthesisSettleFrozen) synthesisNoteDispatcher?.(record);
|
|
13639
14440
|
await runExtensionActivity();
|
|
13640
14441
|
for (const listener of [...settleListeners]) listener();
|
|
13641
14442
|
});
|
|
@@ -14425,33 +15226,198 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
14425
15226
|
};
|
|
14426
15227
|
};
|
|
14427
15228
|
/**
|
|
15229
|
+
* One incremental synthesis note (RV-211 remainder): a FRESH agent
|
|
15230
|
+
* entry with role 'synthesize' on the finish-only toolset whose
|
|
15231
|
+
* prompt derives deterministically from the goal and the ONE settled
|
|
15232
|
+
* child's digest, so a resume replays it by identity with zero paid
|
|
15233
|
+
* calls. The invocation itself never throws out of here: an infra
|
|
15234
|
+
* failure settles as a synthesized error result and the
|
|
15235
|
+
* reconciliation falls back to the raw digest summary.
|
|
15236
|
+
*/
|
|
15237
|
+
const runSynthesisNote = async (record) => {
|
|
15238
|
+
const spec = opts?.synthesis;
|
|
15239
|
+
const finishOnly = buildOrchestratorTools(orchestratorRuntime, fullCardText).filter((tool) => tool.name === FINISH_TOOL_NAME);
|
|
15240
|
+
const digest = digestOf(record, record.settled);
|
|
15241
|
+
const prompt = [
|
|
15242
|
+
"You are an incremental synthesis note of an orchestrated run. Digest the SINGLE settled child below into a self-contained note for the final deterministic reconciliation by calling finish({ result }) EXACTLY once, where result is a STRING. Preserve concrete evidence and citations; do not invent findings. No other tool exists.",
|
|
15243
|
+
...spec.instructions === void 0 ? [] : [spec.instructions],
|
|
15244
|
+
`GOAL: ${goal}`,
|
|
15245
|
+
`CHILD: ${JSON.stringify(digest)}`
|
|
15246
|
+
].join("\n");
|
|
15247
|
+
const noteState = { ...callingState };
|
|
15248
|
+
if (orchestratorAccount !== void 0) noteState.budgetScope = orchestratorAccount;
|
|
15249
|
+
const noteOpts = {
|
|
15250
|
+
role: "synthesize",
|
|
15251
|
+
result: "full",
|
|
15252
|
+
tools: finishOnly,
|
|
15253
|
+
limits: spec.noteLimits ?? { maxTurns: 2 },
|
|
15254
|
+
...spec.model === void 0 ? {} : { model: spec.model },
|
|
15255
|
+
...spec.effort === void 0 ? {} : { effort: spec.effort },
|
|
15256
|
+
...spec.estCost === void 0 ? {} : { estCost: spec.estCost },
|
|
15257
|
+
[kTerminalTool]: { name: FINISH_TOOL_NAME }
|
|
15258
|
+
};
|
|
15259
|
+
return runtime.runInScope(noteState, () => ctx.agent(prompt, noteOpts));
|
|
15260
|
+
};
|
|
15261
|
+
/**
|
|
15262
|
+
* Note dispatch is idempotent per child: the settle hook and the
|
|
15263
|
+
* reconciliation both come through here, and the map guarantees one
|
|
15264
|
+
* dispatch per nodeId (a concurrent identical dispatch would mint a
|
|
15265
|
+
* second occurrence and PAY twice: the v1.32.0 lesson).
|
|
15266
|
+
*/
|
|
15267
|
+
const ensureSynthesisNote = (record) => {
|
|
15268
|
+
const existing = synthesisNotes.get(record.nodeId);
|
|
15269
|
+
if (existing !== void 0) return existing;
|
|
15270
|
+
const note = runSynthesisNote(record).catch((thrown) => ({
|
|
15271
|
+
status: "error",
|
|
15272
|
+
output: null,
|
|
15273
|
+
usage: {
|
|
15274
|
+
inputTokens: 0,
|
|
15275
|
+
outputTokens: 0,
|
|
15276
|
+
cacheReadTokens: 0,
|
|
15277
|
+
cacheWriteTokens: 0
|
|
15278
|
+
},
|
|
15279
|
+
costUsd: 0,
|
|
15280
|
+
turns: 0,
|
|
15281
|
+
servedBy: "unknown:unknown",
|
|
15282
|
+
transcriptRef: "",
|
|
15283
|
+
errorMessage: thrown instanceof Error ? thrown.message : String(thrown)
|
|
15284
|
+
}));
|
|
15285
|
+
synthesisNotes.set(record.nodeId, note);
|
|
15286
|
+
return note;
|
|
15287
|
+
};
|
|
15288
|
+
if (opts?.synthesis?.mode === "incremental" && capDecisionRef === void 0) synthesisNoteDispatcher = ensureSynthesisNote;
|
|
15289
|
+
/**
|
|
15290
|
+
* The deterministic reconciliation of 'incremental' synthesis: the
|
|
15291
|
+
* final result is a PURE fold of the journaled draft and the note
|
|
15292
|
+
* results in spawn order, never another model call. A note that died
|
|
15293
|
+
* falls back to the child's raw digest summary under a journaled
|
|
15294
|
+
* per-child decision and a warn log. With dedupeClaims, repeated
|
|
15295
|
+
* claim lines keep their first occurrence and the envelope carries
|
|
15296
|
+
* the repeatedClaims index.
|
|
15297
|
+
*/
|
|
15298
|
+
const reconcileIncremental = async (draft, spec) => {
|
|
15299
|
+
synthesisSettleFrozen = true;
|
|
15300
|
+
const settledRecords = [...records.values()].filter((record) => record.settled !== void 0).sort((a, b) => a.spawnOrdinal - b.spawnOrdinal);
|
|
15301
|
+
const sections = [];
|
|
15302
|
+
for (const record of settledRecords) {
|
|
15303
|
+
const settled = record.settled;
|
|
15304
|
+
const note = await ensureSynthesisNote(record);
|
|
15305
|
+
let noteText;
|
|
15306
|
+
if (note.status === "ok") noteText = typeof note.output === "string" ? note.output : JSON.stringify(note.output ?? null);
|
|
15307
|
+
else {
|
|
15308
|
+
const fallbackKey = deriverV2.deriveKey({
|
|
15309
|
+
kind: "orchestrator-synthesis-note-fallback",
|
|
15310
|
+
nodeId: record.nodeId
|
|
15311
|
+
});
|
|
15312
|
+
if (!internals.replayer.snapshot().some((entry) => entry.kind === "decision" && entry.key === fallbackKey)) await internals.replayer.appendSinglePhase({
|
|
15313
|
+
scope: callingState.scope,
|
|
15314
|
+
key: fallbackKey,
|
|
15315
|
+
kind: "decision",
|
|
15316
|
+
status: "ok",
|
|
15317
|
+
spanId: internals.spans.mint(callingState.spanId),
|
|
15318
|
+
site: "orchestrator-synthesis",
|
|
15319
|
+
value: {
|
|
15320
|
+
decisionType: "orchestrator_synthesis_note_fallback",
|
|
15321
|
+
nodeId: record.nodeId,
|
|
15322
|
+
status: note.status,
|
|
15323
|
+
turnsUsed: note.turns
|
|
15324
|
+
}
|
|
15325
|
+
});
|
|
15326
|
+
internals.events.emit({
|
|
15327
|
+
type: "log",
|
|
15328
|
+
level: "warn",
|
|
15329
|
+
msg: `the synthesis note for child '${record.nodeId}' terminated with status '${note.status}'; falling back to the raw digest summary (journaled decision 'orchestrator_synthesis_note_fallback')`
|
|
15330
|
+
}, callingState.spanId);
|
|
15331
|
+
noteText = digestOf(record, settled).outputSummary;
|
|
15332
|
+
}
|
|
15333
|
+
sections.push({
|
|
15334
|
+
nodeId: record.nodeId,
|
|
15335
|
+
logicalTaskId: record.logicalTaskId,
|
|
15336
|
+
status: settled.status,
|
|
15337
|
+
noteStatus: note.status,
|
|
15338
|
+
note: noteText
|
|
15339
|
+
});
|
|
15340
|
+
}
|
|
15341
|
+
let repeatedClaims;
|
|
15342
|
+
if (spec.dedupeClaims === true) {
|
|
15343
|
+
const deduped = dedupeRepeatedClaims(sections.map((section) => ({
|
|
15344
|
+
nodeId: section.nodeId,
|
|
15345
|
+
text: section.note
|
|
15346
|
+
})));
|
|
15347
|
+
const textByNode = new Map(deduped.rows.map((row) => [row.nodeId, row.text]));
|
|
15348
|
+
for (const section of sections) section.note = textByNode.get(section.nodeId) ?? section.note;
|
|
15349
|
+
repeatedClaims = deduped.repeated;
|
|
15350
|
+
}
|
|
15351
|
+
const draftJson = JSON.stringify(draft ?? null);
|
|
15352
|
+
internals.events.emit({
|
|
15353
|
+
type: "log",
|
|
15354
|
+
level: "debug",
|
|
15355
|
+
msg: "orchestrator synthesis reconciliation",
|
|
15356
|
+
data: {
|
|
15357
|
+
children: sections.length,
|
|
15358
|
+
draftChars: draftJson.length,
|
|
15359
|
+
notesChars: sections.reduce((sum, section) => sum + section.note.length, 0),
|
|
15360
|
+
perChild: sections.map((section) => ({
|
|
15361
|
+
nodeId: section.nodeId,
|
|
15362
|
+
chars: section.note.length
|
|
15363
|
+
})),
|
|
15364
|
+
...repeatedClaims === void 0 ? {} : { repeatedClaims: repeatedClaims.length }
|
|
15365
|
+
}
|
|
15366
|
+
}, callingState.spanId);
|
|
15367
|
+
return {
|
|
15368
|
+
synthesis: "incremental",
|
|
15369
|
+
draft,
|
|
15370
|
+
sections,
|
|
15371
|
+
...repeatedClaims === void 0 ? {} : { repeatedClaims }
|
|
15372
|
+
};
|
|
15373
|
+
};
|
|
15374
|
+
/**
|
|
14428
15375
|
* The post-fan-in synthesis invocation (RV-211): a FRESH agent entry
|
|
14429
15376
|
* with role 'synthesize' on the finish-only toolset (a distinct
|
|
14430
15377
|
* toolsetHash, the reserved-finalizer precedent), its prompt derived
|
|
14431
15378
|
* deterministically from the goal, the journaled coordination draft,
|
|
14432
15379
|
* and the settled child digest, so a resume replays it by identity
|
|
14433
15380
|
* with zero paid calls. Runs strictly AFTER the acceptance verdict
|
|
14434
|
-
* (a rejected run never pays for synthesis
|
|
14435
|
-
*
|
|
14436
|
-
*
|
|
14437
|
-
*
|
|
14438
|
-
*
|
|
15381
|
+
* (a rejected run never pays for synthesis; in 'incremental' mode
|
|
15382
|
+
* the per-child notes are paid DURING the run, so only the
|
|
15383
|
+
* reconciliation itself is deferred) and owns the finish validators
|
|
15384
|
+
* when they are configured. Failure posture: with validators the run
|
|
15385
|
+
* fails typed (the validated path is mandatory); without them the
|
|
15386
|
+
* run falls back to the draft under a journaled decision and a warn
|
|
15387
|
+
* log, never silently.
|
|
14439
15388
|
*/
|
|
14440
15389
|
const runSynthesis = async (draft) => {
|
|
14441
15390
|
const spec = opts?.synthesis;
|
|
14442
15391
|
if (spec === void 0) return draft;
|
|
14443
15392
|
await recoveryDone;
|
|
15393
|
+
if (spec.mode === "incremental") return await reconcileIncremental(draft, spec);
|
|
14444
15394
|
const finishOnly = buildOrchestratorTools(orchestratorRuntime, fullCardText).filter((tool) => tool.name === FINISH_TOOL_NAME);
|
|
14445
15395
|
const settledDigests = [...records.values()].filter((record) => record.settled !== void 0).sort((a, b) => a.spawnOrdinal - b.spawnOrdinal).map((record) => digestOf(record, record.settled));
|
|
15396
|
+
let digestRows = settledDigests;
|
|
15397
|
+
let repeatedClaims;
|
|
15398
|
+
if (spec.dedupeClaims === true) {
|
|
15399
|
+
const deduped = dedupeRepeatedClaims(settledDigests.map((row) => ({
|
|
15400
|
+
nodeId: row.nodeId,
|
|
15401
|
+
text: row.outputSummary
|
|
15402
|
+
})));
|
|
15403
|
+
const textByNode = new Map(deduped.rows.map((row) => [row.nodeId, row.text]));
|
|
15404
|
+
digestRows = settledDigests.map((row) => ({
|
|
15405
|
+
...row,
|
|
15406
|
+
outputSummary: textByNode.get(row.nodeId) ?? row.outputSummary
|
|
15407
|
+
}));
|
|
15408
|
+
repeatedClaims = deduped.repeated;
|
|
15409
|
+
}
|
|
14446
15410
|
const draftJson = JSON.stringify(draft ?? null);
|
|
14447
|
-
const digestJson = JSON.stringify(
|
|
15411
|
+
const digestJson = JSON.stringify(digestRows);
|
|
14448
15412
|
const prompt = [
|
|
14449
15413
|
"You are the synthesis invocation of an orchestrated run. Compose the FINAL result of the run from the goal, the coordination draft, and the settled child evidence below by calling finish({ result }) EXACTLY once. Preserve the evidence and citations the draft relies on; do not invent findings. No other tool exists.",
|
|
15414
|
+
...repeatedClaims === void 0 ? [] : ["Repeated claims across children were deduplicated before this prompt: only the first occurrence of each repeated line remains in the digest, and the REPEATED CLAIMS index below lists each one with its reporters."],
|
|
14450
15415
|
...spec.instructions === void 0 ? [] : [spec.instructions],
|
|
14451
15416
|
...finishValidationPromptLines(validationSpec),
|
|
14452
15417
|
`GOAL: ${goal}`,
|
|
14453
15418
|
`DRAFT: ${draftJson}`,
|
|
14454
|
-
`DIGEST: ${digestJson}
|
|
15419
|
+
`DIGEST: ${digestJson}`,
|
|
15420
|
+
...repeatedClaims === void 0 ? [] : [`REPEATED CLAIMS: ${JSON.stringify(repeatedClaims)}`]
|
|
14455
15421
|
].join("\n");
|
|
14456
15422
|
internals.events.emit({
|
|
14457
15423
|
type: "log",
|
|
@@ -14462,10 +15428,11 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
14462
15428
|
draftChars: draftJson.length,
|
|
14463
15429
|
digestChars: digestJson.length,
|
|
14464
15430
|
promptChars: prompt.length,
|
|
14465
|
-
perChild:
|
|
15431
|
+
perChild: digestRows.map((entry) => ({
|
|
14466
15432
|
nodeId: entry.nodeId,
|
|
14467
15433
|
chars: JSON.stringify(entry).length
|
|
14468
|
-
}))
|
|
15434
|
+
})),
|
|
15435
|
+
...repeatedClaims === void 0 ? {} : { repeatedClaims: repeatedClaims.length }
|
|
14469
15436
|
}
|
|
14470
15437
|
}, callingState.spanId);
|
|
14471
15438
|
const synthesisState = { ...callingState };
|
|
@@ -14540,7 +15507,11 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
14540
15507
|
const priorRejection = validationDecisions().find((decision) => decision.verdict === "rejected");
|
|
14541
15508
|
if (priorRejection !== void 0) throw finishValidationError(priorRejection);
|
|
14542
15509
|
}
|
|
14543
|
-
const promptLines = [
|
|
15510
|
+
const promptLines = [
|
|
15511
|
+
...extension?.promptLines?.() ?? [],
|
|
15512
|
+
...finishValidationPromptLines(validationSpec),
|
|
15513
|
+
...acceptancePromptLines(opts?.acceptance)
|
|
15514
|
+
];
|
|
14544
15515
|
const result = await runtime.runInScope(orchestratorState, () => ctx.agent(orchestratorPrompt(goal, opts?.maxSpawns, promptLines.length === 0 ? void 0 : promptLines), agentOpts));
|
|
14545
15516
|
const liveTermination = extensionTermination;
|
|
14546
15517
|
if (liveTermination !== void 0) throw liveTermination;
|
|
@@ -14556,21 +15527,32 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
14556
15527
|
else {
|
|
14557
15528
|
const childStatusCounts = {};
|
|
14558
15529
|
const degradedReasons = [];
|
|
15530
|
+
const salvaged = [];
|
|
15531
|
+
let hardDegraded = 0;
|
|
15532
|
+
const acceptPartial = opts.acceptance.acceptPartialChildren === true;
|
|
14559
15533
|
const sortedRecords = [...records.values()].sort((a, b) => a.spawnOrdinal - b.spawnOrdinal);
|
|
14560
15534
|
for (const record of sortedRecords) {
|
|
14561
15535
|
const status = record.settled?.status ?? "running";
|
|
14562
15536
|
childStatusCounts[status] = (childStatusCounts[status] ?? 0) + 1;
|
|
14563
|
-
if (status
|
|
15537
|
+
if (status === "ok") continue;
|
|
15538
|
+
if (acceptPartial && status === "limit" && record.settled?.partial !== void 0) {
|
|
15539
|
+
salvaged.push(record.nodeId);
|
|
15540
|
+
degradedReasons.push(`child ${record.nodeId} accepted as partial (settled 'limit' with a structured partial)`);
|
|
15541
|
+
continue;
|
|
15542
|
+
}
|
|
15543
|
+
hardDegraded += 1;
|
|
15544
|
+
degradedReasons.push(status === "running" ? `child ${record.nodeId} was still running when finish validated` : `child ${record.nodeId} settled '${status}'`);
|
|
14564
15545
|
}
|
|
14565
15546
|
const childPolicy = opts.acceptance.childPolicy;
|
|
14566
|
-
const accepted = childPolicy === "all-ok" ?
|
|
15547
|
+
const accepted = childPolicy === "all-ok" ? hardDegraded === 0 : (childStatusCounts.ok ?? 0) + salvaged.length >= childPolicy.minSuccessful;
|
|
14567
15548
|
decision = {
|
|
14568
15549
|
decisionType: "orchestrator_acceptance",
|
|
14569
15550
|
verdict: accepted ? "accepted" : "rejected",
|
|
14570
15551
|
completion: !accepted ? "rejected" : degradedReasons.length === 0 ? "complete" : "partial",
|
|
14571
15552
|
childPolicy,
|
|
14572
15553
|
childStatusCounts,
|
|
14573
|
-
degradedReasons
|
|
15554
|
+
degradedReasons,
|
|
15555
|
+
...salvaged.length === 0 ? {} : { salvagedPartialChildren: salvaged }
|
|
14574
15556
|
};
|
|
14575
15557
|
await internals.replayer.appendSinglePhase({
|
|
14576
15558
|
scope: callingState.scope,
|
|
@@ -14586,16 +15568,19 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
14586
15568
|
const required = decision.childPolicy === "all-ok" ? "every child ok" : `at least ${String(decision.childPolicy.minSuccessful)} children ok`;
|
|
14587
15569
|
throw new FailRunError(`the orchestrator acceptance policy rejected the finish: ${String(decision.childStatusCounts.ok ?? 0)} children settled 'ok' but the policy requires ${required}; degraded: ${decision.degradedReasons.join("; ")}`, { data: {
|
|
14588
15570
|
source: "orchestrator_acceptance",
|
|
15571
|
+
completion: "rejected",
|
|
14589
15572
|
childPolicy: decision.childPolicy,
|
|
14590
15573
|
childStatusCounts: decision.childStatusCounts,
|
|
14591
|
-
degradedReasons: decision.degradedReasons
|
|
15574
|
+
degradedReasons: decision.degradedReasons,
|
|
15575
|
+
...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren }
|
|
14592
15576
|
} });
|
|
14593
15577
|
}
|
|
14594
15578
|
return {
|
|
14595
15579
|
result: await runSynthesis(result.output),
|
|
14596
15580
|
completion: decision.completion,
|
|
14597
15581
|
childStatusCounts: decision.childStatusCounts,
|
|
14598
|
-
degradedReasons: decision.degradedReasons
|
|
15582
|
+
degradedReasons: decision.degradedReasons,
|
|
15583
|
+
...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren }
|
|
14599
15584
|
};
|
|
14600
15585
|
});
|
|
14601
15586
|
}
|
|
@@ -15243,6 +16228,32 @@ function workflowSourceRef(runId) {
|
|
|
15243
16228
|
return `${runId}/workflow-source`;
|
|
15244
16229
|
}
|
|
15245
16230
|
/**
|
|
16231
|
+
* The completion envelope contract (RV-207 tail): a workflow reports
|
|
16232
|
+
* SEMANTIC completion by returning an object result carrying a
|
|
16233
|
+
* `completion` literal (and optionally `childStatusCounts`), or by
|
|
16234
|
+
* throwing a typed RulvarError whose `data` carries them; the engine
|
|
16235
|
+
* lifts the validated fields onto the `run:end` event so telemetry
|
|
16236
|
+
* consumers read completeness without parsing workflow-specific result
|
|
16237
|
+
* shapes. The orchestrator acceptance path emits this envelope. Pure
|
|
16238
|
+
* shape validation: anything malformed is silently absent (the event is
|
|
16239
|
+
* telemetry, never authority), and an invalid counts record drops the
|
|
16240
|
+
* counts while keeping a valid completion.
|
|
16241
|
+
*/
|
|
16242
|
+
function liftRunCompletion(candidate) {
|
|
16243
|
+
if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) return;
|
|
16244
|
+
const completion = candidate.completion;
|
|
16245
|
+
if (completion !== "complete" && completion !== "partial" && completion !== "rejected") return;
|
|
16246
|
+
const counts = candidate.childStatusCounts;
|
|
16247
|
+
if (typeof counts === "object" && counts !== null && !Array.isArray(counts)) {
|
|
16248
|
+
const entries = Object.entries(counts);
|
|
16249
|
+
if (entries.every(([, value]) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0)) return {
|
|
16250
|
+
completion,
|
|
16251
|
+
childStatusCounts: Object.fromEntries(entries)
|
|
16252
|
+
};
|
|
16253
|
+
}
|
|
16254
|
+
return { completion };
|
|
16255
|
+
}
|
|
16256
|
+
/**
|
|
15246
16257
|
* sha256 hex over the JCS canonical serialization of a run's args: the
|
|
15247
16258
|
* value the engine records as `RunMeta.argsHash` at genesis, exposed so
|
|
15248
16259
|
* hosts can verify re-supplied resume args against the recorded hash
|
|
@@ -15613,11 +16624,13 @@ function createEngine(options) {
|
|
|
15613
16624
|
}
|
|
15614
16625
|
}
|
|
15615
16626
|
await putMeta(status).catch(() => void 0);
|
|
16627
|
+
const lifted = liftRunCompletion(status === "ok" || status === "exhausted" ? outcome.value : status === "error" ? wireError?.data : void 0);
|
|
15616
16628
|
bus.emit({
|
|
15617
16629
|
type: "run:end",
|
|
15618
16630
|
status,
|
|
15619
16631
|
totalUsd: ledger.usd,
|
|
15620
|
-
...outcome.cost.usageApprox === true ? { usageApprox: true } : {}
|
|
16632
|
+
...outcome.cost.usageApprox === true ? { usageApprox: true } : {},
|
|
16633
|
+
...lifted === void 0 ? {} : lifted
|
|
15621
16634
|
}, rootSpanId);
|
|
15622
16635
|
bus.end();
|
|
15623
16636
|
resumeCtx?.previewResolve({
|
|
@@ -16089,4 +17102,4 @@ function createSandboxBridge(ctx, options) {
|
|
|
16089
17102
|
};
|
|
16090
17103
|
}
|
|
16091
17104
|
//#endregion
|
|
16092
|
-
export { AWAIT_SCHEMA, AdmissionController, AdmissionRejectedError, AgentCallError, BUDGET_ABORT_REASON, BudgetExhaustedError, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, ConfigError, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_CHILD_RESULT_PAGE_CHARS, DEFAULT_CITATION_PATTERN, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_EVIDENCE_MIN_SHARE, DEFAULT_FINISH_MAX_REPAIRS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_SYNTHESIS_MAX_TURNS, DedupIndex, DeterminismError, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EscalationDecisionAbortedError, EventBus, ExternalRegistry, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailRunError, FileModelKnowledgeStore, FileTranscriptStore, GET_CHILD_RESULT_SCHEMA, GET_CHILD_RESULT_TOOL_NAME, GitWorktreeProvider, INBOX_PROPOSAL_TTL_DAYS, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, JournalCompatibilityError, JournalMatcher, JournalMissError, JournalOrderViolation, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, KeyedLimiter, KnowledgeCasError, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LeaseHeldError, LineageIndex, MASKED_SECRET, MAX_CHILD_RESULT_PAGE_CHARS, MAX_DEPTH_CEILING, ModelRetry, NoProgressDetector, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OrchestratorCapConfigError, PARALLEL_AGENTS_SCHEMA, ParallelSiteCounter, PlanInvariantError, READ_CHILD_ARTIFACT_SCHEMA, READ_CHILD_ARTIFACT_TOOL_NAME, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RUN_SETTLE_DECISION_TYPE, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionFold, RulvarError, RunBudget, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxError, ScriptRejected, Semaphore, SpanRegistry, TOOL_NAME_PATTERN, TerminationAccount, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, assertFencedWrites, atCompactionThreshold, auditRun, auditRuns, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, evidencePreservedValidator, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hasFencedWrites, hasMetaLookup, hashRunArgs, hashRunOutput, hashWorkflowBody, hashWorkflowSource, identityJcs, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lastRunSettle, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, metaMatchesFilter, minMatchesValidator, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readRunMeta, readTerminationInit, reconcileRunMeta, reduceCriticalPath, reduceInvocationTable, registryKeyRing, remeasureQueue, replayDisposition, requiredFieldsValidator, requiredSectionsValidator, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, snapshotUsage, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, validateUsageLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
|
|
17105
|
+
export { AWAIT_SCHEMA, AdmissionController, AdmissionRejectedError, AgentCallError, BUDGET_ABORT_REASON, BudgetExhaustedError, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, ConfigError, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_CHILD_RESULT_PAGE_CHARS, DEFAULT_CITATION_PATTERN, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_EVIDENCE_MIN_SHARE, DEFAULT_FINISH_MAX_REPAIRS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_SYNTHESIS_MAX_TURNS, DEFAULT_SYNTHESIS_NOTE_MAX_TURNS, DedupIndex, DeterminismError, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EscalationDecisionAbortedError, EventBus, ExternalRegistry, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailRunError, FileModelKnowledgeStore, FileTranscriptStore, GET_CHILD_RESULT_SCHEMA, GET_CHILD_RESULT_TOOL_NAME, GitWorktreeProvider, IMPLEMENTATION_PROFILE_LIMITS, INBOX_PROPOSAL_TTL_DAYS, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, JournalCompatibilityError, JournalMatcher, JournalMissError, JournalOrderViolation, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, KeyedLimiter, KnowledgeCasError, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LeaseHeldError, LineageIndex, MASKED_SECRET, MAX_CHILD_RESULT_PAGE_CHARS, MAX_DEPTH_CEILING, ModelRetry, NoProgressDetector, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OrchestratorCapConfigError, PARALLEL_AGENTS_SCHEMA, PROGRESS_REPORT_TOOL_NAME, ParallelSiteCounter, PlanInvariantError, READ_CHILD_ARTIFACT_SCHEMA, READ_CHILD_ARTIFACT_TOOL_NAME, RESEARCH_PROFILE_LIMITS, REVIEW_PROFILE_LIMITS, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RUN_SETTLE_DECISION_TYPE, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionFold, RulvarError, RunBudget, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxError, ScriptRejected, Semaphore, SpanRegistry, TOOL_NAME_PATTERN, TerminationAccount, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, assertFencedWrites, atCompactionThreshold, auditRun, auditRuns, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, dedupeRepeatedClaims, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, evidencePreservedValidator, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hasFencedWrites, hasMetaLookup, hashRunArgs, hashRunOutput, hashWorkflowBody, hashWorkflowSource, identityJcs, implementationAgentProfile, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lastRunSettle, latestProgressReport, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, metaMatchesFilter, minMatchesValidator, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, progressReportTool, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readRunMeta, readTerminationInit, reconcileRunMeta, reduceCriticalPath, reduceInvocationTable, registryKeyRing, remeasureQueue, replayDisposition, repositoryResearchToolset, requiredFieldsValidator, requiredSectionsValidator, researchAgentProfile, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, reviewAgentProfile, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, snapshotUsage, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, validateUsageLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
|