github-router 0.3.212 → 0.3.229
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/browser-ext/manifest.json +1 -1
- package/dist/engine-v9-pEKoD.js +6 -0
- package/dist/lib/tree-sitter-pool/worker.js +30 -23
- package/dist/main.js +22 -19
- package/dist/main.js.map +1 -1
- package/dist/{peer-mcp-personas-C1RegCRs.js → peer-mcp-personas-BKwMCOsl.js} +1744 -1101
- package/dist/peer-mcp-personas-BKwMCOsl.js.map +1 -0
- package/package.json +4 -2
- package/dist/engine-A3oKQtu-.js +0 -6
- package/dist/peer-mcp-personas-C1RegCRs.js.map +0 -1
|
@@ -23,12 +23,13 @@ import { Worker } from "node:worker_threads";
|
|
|
23
23
|
import { gunzipSync, inflateRawSync } from "node:zlib";
|
|
24
24
|
import WebSocket from "ws";
|
|
25
25
|
import { events } from "fetch-event-stream";
|
|
26
|
-
import { Type } from "typebox";
|
|
26
|
+
import { Type, Type as Type$1 } from "typebox";
|
|
27
27
|
import "partial-json";
|
|
28
28
|
import { Compile } from "typebox/compile";
|
|
29
29
|
import { Value } from "typebox/value";
|
|
30
30
|
import "yaml";
|
|
31
31
|
import "ignore";
|
|
32
|
+
import "diff";
|
|
32
33
|
|
|
33
34
|
//#region rolldown:runtime
|
|
34
35
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
@@ -637,9 +638,9 @@ function resolveModel(modelId) {
|
|
|
637
638
|
if (oneMMatch) {
|
|
638
639
|
const stripped = oneMMatch[1];
|
|
639
640
|
const resolved = resolveModel(stripped);
|
|
640
|
-
if (
|
|
641
|
+
if (!(/-1m(?:$|-)/.test(resolved) || (models.find((m) => m.id === resolved)?.capabilities?.limits?.max_context_window_tokens ?? 0) >= 1e6) && !warnedOneMDowngrade.has(modelId)) {
|
|
641
642
|
warnedOneMDowngrade.add(modelId);
|
|
642
|
-
consola.warn(`Model "${modelId}" requested 1M context but no
|
|
643
|
+
consola.warn(`Model "${modelId}" requested 1M context but no 1M backend is in Copilot's catalog for this tier/family; downgrading upstream to "${resolved}" (200K). Claude Code's local context accounting will still assume 1M — expect premature auto-compact. Drop the [1m] suffix (or unset CLAUDE_CODE_DISABLE_1M_CONTEXT if you set it) to silence.`);
|
|
643
644
|
}
|
|
644
645
|
return resolved;
|
|
645
646
|
}
|
|
@@ -649,8 +650,8 @@ function resolveModel(modelId) {
|
|
|
649
650
|
if (ciMatch) return ciMatch.id;
|
|
650
651
|
if (lower.includes("opus")) {
|
|
651
652
|
const oneMs = models.filter((m) => m.id.includes("opus") && /-1m(?:$|-)/.test(m.id));
|
|
652
|
-
const versionMatch = lower.match(/opus-(\d+)[.-](\d+)
|
|
653
|
-
const requestedVersion = versionMatch ? `${versionMatch[1]}.${versionMatch[2]}` : void 0;
|
|
653
|
+
const versionMatch = lower.match(/opus-(\d+)(?:[.-](\d+))?/);
|
|
654
|
+
const requestedVersion = versionMatch ? versionMatch[2] ? `${versionMatch[1]}.${versionMatch[2]}` : versionMatch[1] : void 0;
|
|
654
655
|
const oneM = (requestedVersion ? oneMs.find((m) => m.id.includes(`opus-${requestedVersion}-`)) : void 0) ?? (requestedVersion ? void 0 : oneMs[0]);
|
|
655
656
|
if (oneM) return oneM.id;
|
|
656
657
|
}
|
|
@@ -1005,15 +1006,15 @@ function getPackageVersion() {
|
|
|
1005
1006
|
//#region src/lib/port.ts
|
|
1006
1007
|
const DEFAULT_PORT = 8787;
|
|
1007
1008
|
const DEFAULT_CLAUDE_MODEL_FALLBACKS = [
|
|
1009
|
+
"claude-opus-4-8",
|
|
1008
1010
|
"claude-opus-4-7",
|
|
1009
|
-
"claude-opus-4-6"
|
|
1010
|
-
"claude-opus-4-5"
|
|
1011
|
+
"claude-opus-4-6"
|
|
1011
1012
|
];
|
|
1012
1013
|
/**
|
|
1013
1014
|
* Cap-aware default picker for `ANTHROPIC_MODEL` on the implicit-default
|
|
1014
1015
|
* path. Returns `claude-opus-${family}[1m]` when the live Copilot catalog
|
|
1015
1016
|
* shows the family is 1M-capable, else the bare `claude-opus-${family}`
|
|
1016
|
-
* slug. `family` defaults to `"
|
|
1017
|
+
* slug. `family` defaults to `"5"` so the no-arg call selects the
|
|
1017
1018
|
* current default; explicit values like `"4.7"` or `"4.6"` are used to
|
|
1018
1019
|
* honor the `github-router claude -m <version>` family shorthand.
|
|
1019
1020
|
*
|
|
@@ -1027,8 +1028,8 @@ const DEFAULT_CLAUDE_MODEL_FALLBACKS = [
|
|
|
1027
1028
|
* 2. **Base-slug capability signal** — the catalog entry whose id IS
|
|
1028
1029
|
* the base `opus-${family}` slug advertises
|
|
1029
1030
|
* `capabilities.limits.max_context_window_tokens >= 1_000_000`. This
|
|
1030
|
-
* is how 4.8
|
|
1031
|
-
* `claude-opus-4.8` id is the 1M variant.
|
|
1031
|
+
* is how 4.8 and 5 ship — there is no `-1m` sibling; the single
|
|
1032
|
+
* `claude-opus-4.8` / `claude-opus-5` id is itself the 1M variant.
|
|
1032
1033
|
* Either signal flips on the `[1m]` decoration. Both signals together
|
|
1033
1034
|
* also flip it on (no double-counting). The breadcrumb log names which
|
|
1034
1035
|
* signal fired so users can spot catalog shape changes.
|
|
@@ -1058,7 +1059,7 @@ const DEFAULT_CLAUDE_MODEL_FALLBACKS = [
|
|
|
1058
1059
|
* can't tell the difference between "no catalog yet" and "no 1M
|
|
1059
1060
|
* variant" — defaulting safe-side preserves the pre-change behavior).
|
|
1060
1061
|
*/
|
|
1061
|
-
const DEFAULT_OPUS_FAMILY = "
|
|
1062
|
+
const DEFAULT_OPUS_FAMILY = "5";
|
|
1062
1063
|
const ONE_M_TOKENS = 1e6;
|
|
1063
1064
|
function pickClaudeDefault(opusFamily = DEFAULT_OPUS_FAMILY) {
|
|
1064
1065
|
const dotted = opusFamily.replace(/-/g, ".");
|
|
@@ -6187,19 +6188,19 @@ function resolveTierModel(tier$1) {
|
|
|
6187
6188
|
|
|
6188
6189
|
//#endregion
|
|
6189
6190
|
//#region src/lib/first-mate/classifier.ts
|
|
6190
|
-
function isRecord$
|
|
6191
|
+
function isRecord$1(value) {
|
|
6191
6192
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6192
6193
|
}
|
|
6193
6194
|
function firstMessageContent$1(value) {
|
|
6194
|
-
if (!isRecord$
|
|
6195
|
+
if (!isRecord$1(value) || !Array.isArray(value.choices)) return null;
|
|
6195
6196
|
const first = value.choices[0];
|
|
6196
|
-
if (!isRecord$
|
|
6197
|
+
if (!isRecord$1(first) || !isRecord$1(first.message)) return null;
|
|
6197
6198
|
return typeof first.message.content === "string" ? first.message.content : null;
|
|
6198
6199
|
}
|
|
6199
6200
|
function parseJsonObject(value) {
|
|
6200
6201
|
try {
|
|
6201
6202
|
const parsed = JSON.parse(value);
|
|
6202
|
-
return isRecord$
|
|
6203
|
+
return isRecord$1(parsed) ? parsed : null;
|
|
6203
6204
|
} catch {
|
|
6204
6205
|
return null;
|
|
6205
6206
|
}
|
|
@@ -6269,7 +6270,7 @@ async function classifyPlanReady(logExcerpt) {
|
|
|
6269
6270
|
schemaHint: "{\"planReady\":boolean,\"planExcerpt\":\"<=1200 chars from the completed plan, or empty\",\"confidence\":number}",
|
|
6270
6271
|
maxTokens: 500,
|
|
6271
6272
|
validate(value) {
|
|
6272
|
-
if (!isRecord$
|
|
6273
|
+
if (!isRecord$1(value)) return null;
|
|
6273
6274
|
const planReady = booleanValue$1(value.planReady);
|
|
6274
6275
|
const planExcerpt = stringValue$1(value.planExcerpt);
|
|
6275
6276
|
if (planReady === null || planExcerpt === null) return null;
|
|
@@ -6286,7 +6287,7 @@ async function classifyQuestionAnswerable(question, acceptanceCriteria) {
|
|
|
6286
6287
|
user: `Acceptance criteria:\n${acceptanceCriteria}\n\nAgent question:\n${question}`,
|
|
6287
6288
|
schemaHint: "{\"answerable\":boolean,\"answer\":\"present only when answerable\",\"confidence\":number}",
|
|
6288
6289
|
validate(value) {
|
|
6289
|
-
if (!isRecord$
|
|
6290
|
+
if (!isRecord$1(value)) return null;
|
|
6290
6291
|
const answerable = booleanValue$1(value.answerable);
|
|
6291
6292
|
if (answerable === null) return null;
|
|
6292
6293
|
const answer = stringValue$1(value.answer);
|
|
@@ -6304,7 +6305,7 @@ async function classifyFixAddressed(failureSummary, latestLogExcerpt) {
|
|
|
6304
6305
|
user: `Failure summary:\n${failureSummary}\n\nLatest log excerpt:\n${latestLogExcerpt}`,
|
|
6305
6306
|
schemaHint: "{\"addressed\":boolean,\"confidence\":number}",
|
|
6306
6307
|
validate(value) {
|
|
6307
|
-
if (!isRecord$
|
|
6308
|
+
if (!isRecord$1(value)) return null;
|
|
6308
6309
|
const addressed = booleanValue$1(value.addressed);
|
|
6309
6310
|
return addressed === null ? null : { addressed };
|
|
6310
6311
|
}
|
|
@@ -6316,7 +6317,7 @@ async function classifyStuck(logExcerpt) {
|
|
|
6316
6317
|
user: `Log excerpt:\n${logExcerpt}`,
|
|
6317
6318
|
schemaHint: "{\"stuck\":boolean,\"confidence\":number}",
|
|
6318
6319
|
validate(value) {
|
|
6319
|
-
if (!isRecord$
|
|
6320
|
+
if (!isRecord$1(value)) return null;
|
|
6320
6321
|
const stuck = booleanValue$1(value.stuck);
|
|
6321
6322
|
return stuck === null ? null : { stuck };
|
|
6322
6323
|
}
|
|
@@ -12715,7 +12716,7 @@ function parsePackageJson(text) {
|
|
|
12715
12716
|
if (text === void 0) return void 0;
|
|
12716
12717
|
try {
|
|
12717
12718
|
const parsed = JSON.parse(text);
|
|
12718
|
-
if (!isRecord
|
|
12719
|
+
if (!isRecord(parsed)) return void 0;
|
|
12719
12720
|
return {
|
|
12720
12721
|
scripts: stringRecord(parsed.scripts),
|
|
12721
12722
|
dependencies: stringRecord(parsed.dependencies),
|
|
@@ -12727,9 +12728,9 @@ function parsePackageJson(text) {
|
|
|
12727
12728
|
return;
|
|
12728
12729
|
}
|
|
12729
12730
|
}
|
|
12730
|
-
function detectCommands(packageJson, overrides) {
|
|
12731
|
+
function detectCommands(packageJson, overrides$1) {
|
|
12731
12732
|
const commands = {};
|
|
12732
|
-
const pm = overrides?.package_manager ?? packageJson?.packageManager?.split("@")[0];
|
|
12733
|
+
const pm = overrides$1?.package_manager ?? packageJson?.packageManager?.split("@")[0];
|
|
12733
12734
|
const runner = pm === "bun" || pm === "pnpm" || pm === "yarn" || pm === "npm" ? pm : "npm";
|
|
12734
12735
|
const scripts = packageJson?.scripts ?? {};
|
|
12735
12736
|
if (packageJson !== void 0) {
|
|
@@ -12742,11 +12743,11 @@ function detectCommands(packageJson, overrides) {
|
|
|
12742
12743
|
"dev"
|
|
12743
12744
|
]) if (scripts[key] !== void 0) commands[key] = runScriptCommand(runner, key);
|
|
12744
12745
|
}
|
|
12745
|
-
if (overrides?.build_command !== void 0) commands.build = overrides.build_command;
|
|
12746
|
-
if (overrides?.typecheck_command !== void 0) commands.typecheck = overrides.typecheck_command;
|
|
12747
|
-
if (overrides?.lint_command !== void 0) commands.lint = overrides.lint_command;
|
|
12748
|
-
if (overrides?.test_command !== void 0) commands.test = overrides.test_command;
|
|
12749
|
-
if (overrides?.dev_command !== void 0) commands.dev = overrides.dev_command;
|
|
12746
|
+
if (overrides$1?.build_command !== void 0) commands.build = overrides$1.build_command;
|
|
12747
|
+
if (overrides$1?.typecheck_command !== void 0) commands.typecheck = overrides$1.typecheck_command;
|
|
12748
|
+
if (overrides$1?.lint_command !== void 0) commands.lint = overrides$1.lint_command;
|
|
12749
|
+
if (overrides$1?.test_command !== void 0) commands.test = overrides$1.test_command;
|
|
12750
|
+
if (overrides$1?.dev_command !== void 0) commands.dev = overrides$1.dev_command;
|
|
12750
12751
|
return commands;
|
|
12751
12752
|
}
|
|
12752
12753
|
function detectPackageManager(rootNames, packageJsonText) {
|
|
@@ -12915,11 +12916,11 @@ function buildScaffoldPrBody(reports) {
|
|
|
12915
12916
|
"No factory protocol files are seeded; orchestration remains external to the repository."
|
|
12916
12917
|
].join("\n");
|
|
12917
12918
|
}
|
|
12918
|
-
function isRecord
|
|
12919
|
+
function isRecord(value) {
|
|
12919
12920
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12920
12921
|
}
|
|
12921
12922
|
function stringRecord(value) {
|
|
12922
|
-
if (!isRecord
|
|
12923
|
+
if (!isRecord(value)) return {};
|
|
12923
12924
|
const result = {};
|
|
12924
12925
|
for (const [key, entry] of Object.entries(value)) if (typeof entry === "string") result[key] = entry;
|
|
12925
12926
|
return result;
|
|
@@ -13334,40 +13335,47 @@ function deriveDefinitionName(node) {
|
|
|
13334
13335
|
return null;
|
|
13335
13336
|
}
|
|
13336
13337
|
/**
|
|
13337
|
-
* Collect
|
|
13338
|
-
*
|
|
13339
|
-
*
|
|
13340
|
-
*
|
|
13341
|
-
*
|
|
13342
|
-
|
|
13343
|
-
|
|
13344
|
-
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
|
|
13348
|
-
|
|
13349
|
-
|
|
13338
|
+
* Collect declarations that help a model navigate a file: top-level symbols
|
|
13339
|
+
* and members of class-like containers. Function-local variables and nested
|
|
13340
|
+
* helpers are deliberately omitted because they inflate the summary without
|
|
13341
|
+
* identifying useful read targets. Non-definition wrappers (exports,
|
|
13342
|
+
* decorators, templates) preserve the surrounding depth.
|
|
13343
|
+
*/
|
|
13344
|
+
const OUTLINE_MEMBER_CONTAINERS = new Set([
|
|
13345
|
+
"class_declaration",
|
|
13346
|
+
"class_definition",
|
|
13347
|
+
"class_specifier",
|
|
13348
|
+
"interface_declaration",
|
|
13349
|
+
"annotation_type_declaration",
|
|
13350
|
+
"impl_item",
|
|
13351
|
+
"trait_item",
|
|
13352
|
+
"struct_item",
|
|
13353
|
+
"namespace_definition",
|
|
13354
|
+
"module"
|
|
13355
|
+
]);
|
|
13350
13356
|
function collectDefinitions(root, defTypes, signal) {
|
|
13351
13357
|
const out = [];
|
|
13352
|
-
const visit = (node, depth) => {
|
|
13358
|
+
const visit = (node, depth, includeDefinitions) => {
|
|
13353
13359
|
if (signal?.aborted || out.length >= MAX_OUTLINE_ENTRIES) return;
|
|
13354
13360
|
for (const child of node.namedChildren) {
|
|
13355
13361
|
if (signal?.aborted || out.length >= MAX_OUTLINE_ENTRIES) return;
|
|
13356
13362
|
if (defTypes.has(child.type)) {
|
|
13357
|
-
|
|
13358
|
-
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13363
|
+
if (includeDefinitions) {
|
|
13364
|
+
const name = deriveDefinitionName(child);
|
|
13365
|
+
if (name !== null) out.push({
|
|
13366
|
+
kind: child.type,
|
|
13367
|
+
name,
|
|
13368
|
+
line: child.startPosition.row + 1,
|
|
13369
|
+
depth
|
|
13370
|
+
});
|
|
13371
|
+
}
|
|
13372
|
+
if (includeDefinitions && OUTLINE_MEMBER_CONTAINERS.has(child.type)) visit(child, depth + 1, true);
|
|
13365
13373
|
continue;
|
|
13366
13374
|
}
|
|
13367
|
-
visit(child, depth);
|
|
13375
|
+
visit(child, depth, includeDefinitions);
|
|
13368
13376
|
}
|
|
13369
13377
|
};
|
|
13370
|
-
visit(root, 0);
|
|
13378
|
+
visit(root, 0, true);
|
|
13371
13379
|
return out;
|
|
13372
13380
|
}
|
|
13373
13381
|
/**
|
|
@@ -13511,10 +13519,10 @@ function confirmDefinitionSites(tree, source, language, hits, signal) {
|
|
|
13511
13519
|
return confirmed;
|
|
13512
13520
|
}
|
|
13513
13521
|
/**
|
|
13514
|
-
*
|
|
13515
|
-
*
|
|
13516
|
-
*
|
|
13517
|
-
*
|
|
13522
|
+
* Navigational structural outline of a single file: top-level declarations
|
|
13523
|
+
* plus class-like members (methods, fields, interface members), excluding
|
|
13524
|
+
* function-local variables and nested implementation helpers. Each entry
|
|
13525
|
+
* carries a `depth` (0 = top-level). Reuses the
|
|
13518
13526
|
* shared grammar bundle and the same `Parser` the structural pass uses
|
|
13519
13527
|
* — no second `Parser.init()`.
|
|
13520
13528
|
*
|
|
@@ -13679,6 +13687,14 @@ var TreeSitterPool = class {
|
|
|
13679
13687
|
spawned: this.workersSpawned
|
|
13680
13688
|
};
|
|
13681
13689
|
}
|
|
13690
|
+
/**
|
|
13691
|
+
* Launch-time latency optimization: ready ONE worker and its grammar bundle.
|
|
13692
|
+
* A single worker keeps startup CPU/memory bounded while removing the first
|
|
13693
|
+
* query's initialization wait; normal demand still grows the pool lazily.
|
|
13694
|
+
*/
|
|
13695
|
+
async warm() {
|
|
13696
|
+
return await this.ensureWorkers(1) > 0;
|
|
13697
|
+
}
|
|
13682
13698
|
queue = [];
|
|
13683
13699
|
inflight = /* @__PURE__ */ new Map();
|
|
13684
13700
|
constructor() {
|
|
@@ -13694,18 +13710,19 @@ var TreeSitterPool = class {
|
|
|
13694
13710
|
* callers onto one in-flight ensure so 8 simultaneous searches don't each
|
|
13695
13711
|
* spawn a fresh batch. Returns the live worker count (0 → caller must fall
|
|
13696
13712
|
* back to the in-process path). */
|
|
13697
|
-
ensureWorkers() {
|
|
13713
|
+
ensureWorkers(target = this.size) {
|
|
13698
13714
|
if (this.unavailable || this.shuttingDown) return Promise.resolve(0);
|
|
13715
|
+
const desired = Math.max(1, Math.min(target, this.size));
|
|
13699
13716
|
const liveNow = this.workers.filter((w) => w.ready && w.loaded.size > 0).length;
|
|
13700
|
-
if (liveNow >=
|
|
13701
|
-
if (this.ensuring) return this.ensuring;
|
|
13702
|
-
this.ensuring = this.doEnsureWorkers().finally(() => {
|
|
13717
|
+
if (liveNow >= desired) return Promise.resolve(liveNow);
|
|
13718
|
+
if (this.ensuring) return liveNow > 0 ? Promise.resolve(liveNow) : this.ensuring;
|
|
13719
|
+
this.ensuring = this.doEnsureWorkers(desired).finally(() => {
|
|
13703
13720
|
this.ensuring = null;
|
|
13704
13721
|
});
|
|
13705
|
-
return this.ensuring;
|
|
13722
|
+
return liveNow > 0 ? Promise.resolve(liveNow) : this.ensuring;
|
|
13706
13723
|
}
|
|
13707
|
-
async doEnsureWorkers() {
|
|
13708
|
-
const need =
|
|
13724
|
+
async doEnsureWorkers(target) {
|
|
13725
|
+
const need = target - this.workers.length;
|
|
13709
13726
|
const spawns = [];
|
|
13710
13727
|
for (let i = 0; i < need; i++) spawns.push(this.spawnWorker());
|
|
13711
13728
|
const spawned = await Promise.all(spawns);
|
|
@@ -13926,13 +13943,23 @@ var TreeSitterPool = class {
|
|
|
13926
13943
|
budgetHit: false
|
|
13927
13944
|
};
|
|
13928
13945
|
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
13929
|
-
|
|
13930
|
-
budgetHit = true;
|
|
13931
|
-
stop();
|
|
13932
|
-
}, opts.budgetMs);
|
|
13933
|
-
budgetTimer.unref?.();
|
|
13946
|
+
let budgetTimer;
|
|
13934
13947
|
try {
|
|
13935
13948
|
if (await this.ensureWorkers() === 0) return null;
|
|
13949
|
+
if (opts.signal.aborted) return {
|
|
13950
|
+
byFile,
|
|
13951
|
+
budgetHit: false
|
|
13952
|
+
};
|
|
13953
|
+
if (opts.budgetMs <= 0) {
|
|
13954
|
+
budgetHit = true;
|
|
13955
|
+
stop();
|
|
13956
|
+
} else {
|
|
13957
|
+
budgetTimer = setTimeout(() => {
|
|
13958
|
+
budgetHit = true;
|
|
13959
|
+
stop();
|
|
13960
|
+
}, opts.budgetMs);
|
|
13961
|
+
budgetTimer.unref?.();
|
|
13962
|
+
}
|
|
13936
13963
|
const dispatchFile = async (job, retried) => {
|
|
13937
13964
|
if (stopped) return;
|
|
13938
13965
|
const id = this.nextJobId++;
|
|
@@ -13963,7 +13990,7 @@ var TreeSitterPool = class {
|
|
|
13963
13990
|
};
|
|
13964
13991
|
await Promise.all(jobs.map((job) => dispatchFile(job, false)));
|
|
13965
13992
|
} finally {
|
|
13966
|
-
clearTimeout(budgetTimer);
|
|
13993
|
+
if (budgetTimer) clearTimeout(budgetTimer);
|
|
13967
13994
|
opts.signal.removeEventListener("abort", onAbort);
|
|
13968
13995
|
}
|
|
13969
13996
|
const stillLive = this.workers.some((w) => w.ready && w.loaded.size > 0);
|
|
@@ -14009,6 +14036,7 @@ function resolveWorkerPath() {
|
|
|
14009
14036
|
let _pool = null;
|
|
14010
14037
|
let _shutdownRegistered = false;
|
|
14011
14038
|
let _testCrashOnceArmed = false;
|
|
14039
|
+
let _warmOverride;
|
|
14012
14040
|
/**
|
|
14013
14041
|
* The pool is ON by default for real (non-CI) runs and OFF under CI.
|
|
14014
14042
|
*
|
|
@@ -14054,6 +14082,24 @@ function getTreeSitterPool() {
|
|
|
14054
14082
|
}
|
|
14055
14083
|
return _pool;
|
|
14056
14084
|
}
|
|
14085
|
+
/**
|
|
14086
|
+
* Best-effort launch warm-up. Returns immediately when disabled/unavailable and
|
|
14087
|
+
* never rejects; lazy initialization remains the fallback after any failure.
|
|
14088
|
+
*/
|
|
14089
|
+
async function warmTreeSitterPool() {
|
|
14090
|
+
if (process.env.GH_ROUTER_DISABLE_TS_POOL_WARMUP === "1") return;
|
|
14091
|
+
const pool = getTreeSitterPool();
|
|
14092
|
+
if (!pool) return;
|
|
14093
|
+
try {
|
|
14094
|
+
if (await (_warmOverride?.(pool) ?? pool.warm())) return;
|
|
14095
|
+
} catch (err) {
|
|
14096
|
+
consola.debug(`[code_search] tree-sitter pool warm-up failed; using lazy initialization: ${err.message}`);
|
|
14097
|
+
}
|
|
14098
|
+
if (_pool === pool) {
|
|
14099
|
+
pool.shutdown();
|
|
14100
|
+
_pool = null;
|
|
14101
|
+
}
|
|
14102
|
+
}
|
|
14057
14103
|
|
|
14058
14104
|
//#endregion
|
|
14059
14105
|
//#region src/lib/worker-agent/paths.ts
|
|
@@ -14256,9 +14302,9 @@ const WALL_TIME_MS = 3e4;
|
|
|
14256
14302
|
* Structural-pass settings. The wall-clock budget is checked between
|
|
14257
14303
|
* files (NOT mid-parse — tree-sitter doesn't surface a usable cancel
|
|
14258
14304
|
* hook in the web-tree-sitter binding we're on), so a single
|
|
14259
|
-
* pathological file can overrun by one file's parse-time.
|
|
14260
|
-
*
|
|
14261
|
-
*
|
|
14305
|
+
* pathological file can overrun by one file's parse-time. Worker/grammar
|
|
14306
|
+
* initialization is completed before this timer starts; the 200ms budget
|
|
14307
|
+
* therefore measures parsing only, on cold and warm queries alike.
|
|
14262
14308
|
*/
|
|
14263
14309
|
const STRUCTURAL_BUDGET_MS = 200;
|
|
14264
14310
|
let _structuralBudgetTestOverride = null;
|
|
@@ -14853,7 +14899,7 @@ async function runStructuralPassPooled(opts) {
|
|
|
14853
14899
|
}
|
|
14854
14900
|
return {
|
|
14855
14901
|
confirmedHitIndexes,
|
|
14856
|
-
fallback: run.budgetHit ? `structural budget exceeded after parsing ${run.byFile.size}/${
|
|
14902
|
+
fallback: run.budgetHit ? `structural budget exceeded after parsing ${run.byFile.size}/${jobs.length} files; retry with structural: "topN" or narrow your query` : null,
|
|
14857
14903
|
outlinesByFile
|
|
14858
14904
|
};
|
|
14859
14905
|
}
|
|
@@ -15183,26 +15229,23 @@ function dropAstGrepSecrets(env) {
|
|
|
15183
15229
|
return env;
|
|
15184
15230
|
}
|
|
15185
15231
|
/**
|
|
15186
|
-
* Resolve the ast-grep binary. Checks the
|
|
15187
|
-
*
|
|
15188
|
-
*
|
|
15189
|
-
* is found. `resolveExecutable` honors PATHEXT on Windows and excludes
|
|
15190
|
-
* the cwd (no planted-`sg.exe` vector). The toolbelt dir is searched by
|
|
15191
|
-
* prepending it to a PATH copy so the same resolver handles both sources.
|
|
15232
|
+
* Resolve the ast-grep binary. Checks the known router-owned toolbelt paths
|
|
15233
|
+
* directly before consulting PATH for the unambiguous `ast-grep` name.
|
|
15234
|
+
* Returns an ABSOLUTE path or `null` when neither source has it.
|
|
15192
15235
|
*/
|
|
15193
15236
|
function resolveAstGrep() {
|
|
15194
15237
|
const toolbeltDir = PATHS.TOOLBELT_BIN_DIR;
|
|
15195
|
-
const
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15200
|
-
|
|
15201
|
-
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
return
|
|
15238
|
+
const toolbeltNames = process.platform === "win32" ? [
|
|
15239
|
+
"sg.exe",
|
|
15240
|
+
"ast-grep.exe",
|
|
15241
|
+
"sg",
|
|
15242
|
+
"ast-grep"
|
|
15243
|
+
] : ["sg", "ast-grep"];
|
|
15244
|
+
for (const name of toolbeltNames) {
|
|
15245
|
+
const candidate = path.resolve(toolbeltDir, name);
|
|
15246
|
+
if (existsSync(candidate)) return candidate;
|
|
15247
|
+
}
|
|
15248
|
+
return resolveExecutable("ast-grep", { env: process.env });
|
|
15206
15249
|
}
|
|
15207
15250
|
/**
|
|
15208
15251
|
* Test-only override for the ast-grep resolver. `undefined` = use the real
|
|
@@ -15216,11 +15259,6 @@ let _astGrepResolverOverride;
|
|
|
15216
15259
|
function resolveAstGrepForRun() {
|
|
15217
15260
|
return (_astGrepResolverOverride ?? resolveAstGrep)();
|
|
15218
15261
|
}
|
|
15219
|
-
/** Read PATH case-insensitively from the live env. */
|
|
15220
|
-
function pathEnvValue() {
|
|
15221
|
-
for (const key of Object.keys(process.env)) if (key.toLowerCase() === "path") return process.env[key] ?? "";
|
|
15222
|
-
return "";
|
|
15223
|
-
}
|
|
15224
15262
|
/**
|
|
15225
15263
|
* Run ast-grep with `pattern` over `workspaceCanonical` and return its
|
|
15226
15264
|
* matches in `RawHit` shape (relativized, 1-indexed). Read-only,
|
|
@@ -15242,7 +15280,7 @@ async function runAstGrep(opts) {
|
|
|
15242
15280
|
const binary = resolveAstGrepForRun();
|
|
15243
15281
|
if (!binary) return {
|
|
15244
15282
|
hits: [],
|
|
15245
|
-
notice:
|
|
15283
|
+
notice: `ast_pattern requires ast-grep, but no executable was found at ${PATHS.TOOLBELT_BIN_DIR} (sg/ast-grep) or on PATH (ast-grep); install ast-grep or omit ast_pattern`
|
|
15246
15284
|
};
|
|
15247
15285
|
if (!opts.lang || !/^[A-Za-z0-9_+-]{1,20}$/.test(opts.lang)) return {
|
|
15248
15286
|
hits: [],
|
|
@@ -15823,318 +15861,6 @@ function modelDirName() {
|
|
|
15823
15861
|
/** Short model id used in the sidecar metadata `model` field. */
|
|
15824
15862
|
const MODEL_ID = "LateOn-Code-edge";
|
|
15825
15863
|
|
|
15826
|
-
//#endregion
|
|
15827
|
-
//#region src/lib/colbert/index-store.ts
|
|
15828
|
-
const GIT_TIMEOUT_MS = 4e3;
|
|
15829
|
-
/** Grace window after a `building` write before a workspace with no live
|
|
15830
|
-
* build PID is declared `crashed` — covers the cross-process window where
|
|
15831
|
-
* one proxy wrote `building` but hasn't yet recorded the colgrep child PID. */
|
|
15832
|
-
const BUILD_SPAWN_GRACE_MS = 3e4;
|
|
15833
|
-
/**
|
|
15834
|
-
* Hash a workspace path the same way the metadata sidecar is keyed.
|
|
15835
|
-
* NOTE: this is the ROUTER-OWNED meta key, independent of colgrep's
|
|
15836
|
-
* internal xxh3 physical-dir key (we never need to predict colgrep's
|
|
15837
|
-
* key because we pass the workspace as colgrep's PATH arg and let it
|
|
15838
|
-
* route). A stable sha256-prefix of the canonical path is sufficient.
|
|
15839
|
-
*/
|
|
15840
|
-
function metaHashForWorkspace(workspace) {
|
|
15841
|
-
const canonical = process$1.platform === "win32" ? nodePath.resolve(workspace).toLowerCase().replace(/\\/g, "/") : nodePath.resolve(workspace);
|
|
15842
|
-
let h = 2166136261;
|
|
15843
|
-
for (let i = 0; i < canonical.length; i++) {
|
|
15844
|
-
h ^= canonical.charCodeAt(i);
|
|
15845
|
-
h = Math.imul(h, 16777619);
|
|
15846
|
-
}
|
|
15847
|
-
return (h >>> 0).toString(16).padStart(8, "0");
|
|
15848
|
-
}
|
|
15849
|
-
function metaPath(workspace) {
|
|
15850
|
-
return nodePath.join(PATHS.COLBERT_META_DIR, `${metaHashForWorkspace(workspace)}.json`);
|
|
15851
|
-
}
|
|
15852
|
-
/** Read the sidecar metadata for a workspace (null if none yet). */
|
|
15853
|
-
async function readColbertMeta(workspace) {
|
|
15854
|
-
try {
|
|
15855
|
-
const raw = await fs.readFile(metaPath(workspace), "utf8");
|
|
15856
|
-
const parsed = JSON.parse(raw);
|
|
15857
|
-
if (parsed && typeof parsed === "object" && typeof parsed.status === "string") return parsed;
|
|
15858
|
-
return null;
|
|
15859
|
-
} catch {
|
|
15860
|
-
return null;
|
|
15861
|
-
}
|
|
15862
|
-
}
|
|
15863
|
-
/**
|
|
15864
|
-
* Per-workspace write serializer. `runInit` issues a pre-spawn
|
|
15865
|
-
* `building` write, an `onSpawn` write that patches in the colgrep child
|
|
15866
|
-
* PID, and a final `ready`/`failed` write. Chaining them per workspace
|
|
15867
|
-
* guarantees the final write lands AFTER the (fire-and-forget) onSpawn
|
|
15868
|
-
* write, so a `ready` result is never clobbered back to `building` by a
|
|
15869
|
-
* late atomic-rename.
|
|
15870
|
-
*/
|
|
15871
|
-
const _metaWriteChains = /* @__PURE__ */ new Map();
|
|
15872
|
-
/** Atomically write the sidecar metadata for a workspace (serialized). */
|
|
15873
|
-
async function writeColbertMeta(meta) {
|
|
15874
|
-
const key = metaHashForWorkspace(meta.workspace);
|
|
15875
|
-
const next = (_metaWriteChains.get(key) ?? Promise.resolve()).then(() => writeColbertMetaUnchained(meta));
|
|
15876
|
-
_metaWriteChains.set(key, next.then(() => void 0, () => void 0));
|
|
15877
|
-
return next;
|
|
15878
|
-
}
|
|
15879
|
-
async function writeColbertMetaUnchained(meta) {
|
|
15880
|
-
await fs.mkdir(PATHS.COLBERT_META_DIR, { recursive: true });
|
|
15881
|
-
const dest = metaPath(meta.workspace);
|
|
15882
|
-
const tmp = `${dest}.${process$1.pid}.${Math.random().toString(16).slice(2, 10)}.tmp`;
|
|
15883
|
-
try {
|
|
15884
|
-
await fs.writeFile(tmp, JSON.stringify(meta, null, 2));
|
|
15885
|
-
await fs.rename(tmp, dest);
|
|
15886
|
-
} catch (err) {
|
|
15887
|
-
await fs.rm(tmp, { force: true }).catch(() => {});
|
|
15888
|
-
throw err;
|
|
15889
|
-
}
|
|
15890
|
-
}
|
|
15891
|
-
/**
|
|
15892
|
-
* Whether a COMPLETED colgrep index exists on disk for this workspace.
|
|
15893
|
-
* The preflight uses this to distinguish `building`/`absent` (no
|
|
15894
|
-
* completed index → don't spawn a foreground colgrep) from a real
|
|
15895
|
-
* index. We scan COLGREP_DATA_DIR for any per-project dir containing a
|
|
15896
|
-
* `project.json` whose canonical path matches this workspace AND an
|
|
15897
|
-
* `index/metadata.json` marker.
|
|
15898
|
-
*/
|
|
15899
|
-
async function completedIndexOnDisk(workspace) {
|
|
15900
|
-
const indicesDir = PATHS.COLBERT_INDICES_DIR;
|
|
15901
|
-
let names;
|
|
15902
|
-
try {
|
|
15903
|
-
names = await fs.readdir(indicesDir);
|
|
15904
|
-
} catch {
|
|
15905
|
-
return false;
|
|
15906
|
-
}
|
|
15907
|
-
const wantCanonical = await realpathForCompare(workspace);
|
|
15908
|
-
for (const name of names) {
|
|
15909
|
-
if (name === ".gh-router-meta") continue;
|
|
15910
|
-
const projJson = nodePath.join(indicesDir, name, "project.json");
|
|
15911
|
-
let proj;
|
|
15912
|
-
try {
|
|
15913
|
-
proj = JSON.parse(await fs.readFile(projJson, "utf8"));
|
|
15914
|
-
} catch {
|
|
15915
|
-
continue;
|
|
15916
|
-
}
|
|
15917
|
-
const projPath = proj.path ?? proj.project_path;
|
|
15918
|
-
if (!projPath) continue;
|
|
15919
|
-
if (await realpathForCompare(projPath) !== wantCanonical) continue;
|
|
15920
|
-
if (existsSync(nodePath.join(indicesDir, name, "index", "metadata.json"))) return true;
|
|
15921
|
-
if (existsSync(nodePath.join(indicesDir, name, "index"))) try {
|
|
15922
|
-
if ((await fs.readdir(nodePath.join(indicesDir, name, "index"))).length > 0) return true;
|
|
15923
|
-
} catch {}
|
|
15924
|
-
}
|
|
15925
|
-
return false;
|
|
15926
|
-
}
|
|
15927
|
-
function canonicalForCompare(p) {
|
|
15928
|
-
return process$1.platform === "win32" ? nodePath.resolve(p).toLowerCase().replace(/\\/g, "/") : nodePath.resolve(p);
|
|
15929
|
-
}
|
|
15930
|
-
/** Sync realpath-aware canonicalization (sibling of `realpathForCompare`,
|
|
15931
|
-
* for the on-a-timer inactivity probe which must be synchronous). */
|
|
15932
|
-
function canonicalRealpathSync(p) {
|
|
15933
|
-
try {
|
|
15934
|
-
return canonicalForCompare(realpathSync(p));
|
|
15935
|
-
} catch {
|
|
15936
|
-
return canonicalForCompare(p);
|
|
15937
|
-
}
|
|
15938
|
-
}
|
|
15939
|
-
/** Recursive (bytes, fileCount) of a directory; sync + best-effort. A
|
|
15940
|
-
* colgrep index is a bounded set of shards so the walk stays small. */
|
|
15941
|
-
function dirSizeSync(dir) {
|
|
15942
|
-
let bytes = 0;
|
|
15943
|
-
let count = 0;
|
|
15944
|
-
let entries;
|
|
15945
|
-
try {
|
|
15946
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
15947
|
-
} catch {
|
|
15948
|
-
return [0, 0];
|
|
15949
|
-
}
|
|
15950
|
-
for (const e of entries) {
|
|
15951
|
-
const p = nodePath.join(dir, e.name);
|
|
15952
|
-
if (e.isDirectory()) {
|
|
15953
|
-
const [b, c] = dirSizeSync(p);
|
|
15954
|
-
bytes += b;
|
|
15955
|
-
count += c;
|
|
15956
|
-
} else try {
|
|
15957
|
-
bytes += statSync(p).size;
|
|
15958
|
-
count += 1;
|
|
15959
|
-
} catch {}
|
|
15960
|
-
}
|
|
15961
|
-
return [bytes, count];
|
|
15962
|
-
}
|
|
15963
|
-
/**
|
|
15964
|
-
* (sync) Progress signature of a workspace's colgrep index dir for the init
|
|
15965
|
-
* inactivity watchdog: `${totalBytes}:${fileCount}` of the project dir, or
|
|
15966
|
-
* `null` if it isn't on disk yet. colgrep is SILENT on a non-TTY pipe
|
|
15967
|
-
* during the (potentially multi-hour) encode phase, so output is useless as
|
|
15968
|
-
* a progress signal — but it writes index shards incrementally, so a
|
|
15969
|
-
* changing signature means "still progressing" and a frozen one means
|
|
15970
|
-
* "hung". Successive signatures drive the watchdog: change ⇒ re-arm, frozen
|
|
15971
|
-
* ⇒ kill. Sync because it's called from a `setTimeout` (not awaited).
|
|
15972
|
-
*/
|
|
15973
|
-
function indexDirSignature(workspace) {
|
|
15974
|
-
const indicesDir = PATHS.COLBERT_INDICES_DIR;
|
|
15975
|
-
let names;
|
|
15976
|
-
try {
|
|
15977
|
-
names = readdirSync(indicesDir);
|
|
15978
|
-
} catch {
|
|
15979
|
-
return null;
|
|
15980
|
-
}
|
|
15981
|
-
const want = canonicalRealpathSync(workspace);
|
|
15982
|
-
for (const name of names) {
|
|
15983
|
-
if (name === ".gh-router-meta") continue;
|
|
15984
|
-
const dir = nodePath.join(indicesDir, name);
|
|
15985
|
-
let proj;
|
|
15986
|
-
try {
|
|
15987
|
-
proj = JSON.parse(readFileSync(nodePath.join(dir, "project.json"), "utf8"));
|
|
15988
|
-
} catch {
|
|
15989
|
-
continue;
|
|
15990
|
-
}
|
|
15991
|
-
const projPath = proj.path ?? proj.project_path;
|
|
15992
|
-
if (!projPath || canonicalRealpathSync(projPath) !== want) continue;
|
|
15993
|
-
const [bytes, count] = dirSizeSync(dir);
|
|
15994
|
-
return `${bytes}:${count}`;
|
|
15995
|
-
}
|
|
15996
|
-
return null;
|
|
15997
|
-
}
|
|
15998
|
-
/**
|
|
15999
|
-
* Realpath-aware canonicalization for matching a workspace against
|
|
16000
|
-
* colgrep's stored `project_path`. colgrep stores the OS realpath (e.g.
|
|
16001
|
-
* macOS `/tmp` → `/private/tmp`, Windows 8.3 short names), so a plain
|
|
16002
|
-
* `path.resolve` comparison misses. Falls back to `canonicalForCompare`
|
|
16003
|
-
* when realpath fails (path doesn't exist yet).
|
|
16004
|
-
*/
|
|
16005
|
-
async function realpathForCompare(p) {
|
|
16006
|
-
try {
|
|
16007
|
-
return canonicalForCompare(await fs.realpath(p));
|
|
16008
|
-
} catch {
|
|
16009
|
-
return canonicalForCompare(p);
|
|
16010
|
-
}
|
|
16011
|
-
}
|
|
16012
|
-
/**
|
|
16013
|
-
* Compute the freshness verdict for a query against a workspace.
|
|
16014
|
-
*
|
|
16015
|
-
* Routing (per the dropped-fallback contract):
|
|
16016
|
-
* - `failed` — sidecar says failed → caller returns isError.
|
|
16017
|
-
* - `building` — a tracked init is live OR no completed index on disk
|
|
16018
|
-
* → caller returns building notice (NO results).
|
|
16019
|
-
* - `absent` — never indexed → caller kicks a debounced background
|
|
16020
|
-
* init, returns absent (isError).
|
|
16021
|
-
* - `stale` — ready but HEAD moved / tree dirty since index → caller
|
|
16022
|
-
* returns stale notice (NO results, NO re-search).
|
|
16023
|
-
* - `fresh` — ready + completed index + HEAD matches + not newly
|
|
16024
|
-
* dirty → caller spawns colgrep search.
|
|
16025
|
-
*/
|
|
16026
|
-
async function freshnessVerdict(workspace) {
|
|
16027
|
-
const meta = await readColbertMeta(workspace);
|
|
16028
|
-
if (!meta || meta.status === "absent") return {
|
|
16029
|
-
verdict: "absent",
|
|
16030
|
-
meta
|
|
16031
|
-
};
|
|
16032
|
-
if (meta.status === "failed") return {
|
|
16033
|
-
verdict: "failed",
|
|
16034
|
-
meta
|
|
16035
|
-
};
|
|
16036
|
-
if (meta.status === "building") {
|
|
16037
|
-
const pid = typeof meta.buildPid === "number" ? meta.buildPid : 0;
|
|
16038
|
-
if (isInitInFlight(workspace) || pid > 0 && isPidAlive(pid)) return {
|
|
16039
|
-
verdict: "building",
|
|
16040
|
-
meta
|
|
16041
|
-
};
|
|
16042
|
-
const startedMs = meta.lastIndexedAt ? Date.parse(meta.lastIndexedAt) : NaN;
|
|
16043
|
-
if (Number.isFinite(startedMs) && Date.now() - startedMs < BUILD_SPAWN_GRACE_MS) return {
|
|
16044
|
-
verdict: "building",
|
|
16045
|
-
meta
|
|
16046
|
-
};
|
|
16047
|
-
if (!await completedIndexOnDisk(workspace)) return {
|
|
16048
|
-
verdict: "crashed",
|
|
16049
|
-
meta
|
|
16050
|
-
};
|
|
16051
|
-
}
|
|
16052
|
-
if (!await completedIndexOnDisk(workspace)) return {
|
|
16053
|
-
verdict: "building",
|
|
16054
|
-
meta
|
|
16055
|
-
};
|
|
16056
|
-
const git = await gitState(workspace);
|
|
16057
|
-
if (!git.isRepo) return {
|
|
16058
|
-
verdict: "fresh",
|
|
16059
|
-
meta
|
|
16060
|
-
};
|
|
16061
|
-
const headMoved = meta.lastIndexedHead !== void 0 && git.head !== meta.lastIndexedHead;
|
|
16062
|
-
const newlyDirty = git.dirty && meta.lastIndexedDirty !== true;
|
|
16063
|
-
if (headMoved || newlyDirty) return {
|
|
16064
|
-
verdict: "stale",
|
|
16065
|
-
meta,
|
|
16066
|
-
head: git.head,
|
|
16067
|
-
dirty: git.dirty
|
|
16068
|
-
};
|
|
16069
|
-
return {
|
|
16070
|
-
verdict: "fresh",
|
|
16071
|
-
meta,
|
|
16072
|
-
head: git.head,
|
|
16073
|
-
dirty: git.dirty
|
|
16074
|
-
};
|
|
16075
|
-
}
|
|
16076
|
-
/** Cheap, bounded git probe via the native-exe runner. */
|
|
16077
|
-
async function gitState(workspace) {
|
|
16078
|
-
const git = resolveExecutable("git");
|
|
16079
|
-
if (!git) return { isRepo: false };
|
|
16080
|
-
try {
|
|
16081
|
-
const inside = await runManagedExeCapture(git, [
|
|
16082
|
-
"-C",
|
|
16083
|
-
workspace,
|
|
16084
|
-
"rev-parse",
|
|
16085
|
-
"--is-inside-work-tree"
|
|
16086
|
-
], {
|
|
16087
|
-
timeoutMs: GIT_TIMEOUT_MS,
|
|
16088
|
-
maxStdoutBytes: 64 * 1024
|
|
16089
|
-
});
|
|
16090
|
-
if (inside.code !== 0 || inside.stdout.trim() !== "true") return { isRepo: false };
|
|
16091
|
-
const head = await runManagedExeCapture(git, [
|
|
16092
|
-
"-C",
|
|
16093
|
-
workspace,
|
|
16094
|
-
"rev-parse",
|
|
16095
|
-
"HEAD"
|
|
16096
|
-
], {
|
|
16097
|
-
timeoutMs: GIT_TIMEOUT_MS,
|
|
16098
|
-
maxStdoutBytes: 64 * 1024
|
|
16099
|
-
});
|
|
16100
|
-
const status = await runManagedExeCapture(git, [
|
|
16101
|
-
"-C",
|
|
16102
|
-
workspace,
|
|
16103
|
-
"status",
|
|
16104
|
-
"--porcelain"
|
|
16105
|
-
], {
|
|
16106
|
-
timeoutMs: GIT_TIMEOUT_MS,
|
|
16107
|
-
maxStdoutBytes: 1024 * 1024
|
|
16108
|
-
});
|
|
16109
|
-
return {
|
|
16110
|
-
isRepo: true,
|
|
16111
|
-
head: head.code === 0 ? head.stdout.trim() || void 0 : void 0,
|
|
16112
|
-
dirty: status.code === 0 ? status.stdout.trim().length > 0 : void 0
|
|
16113
|
-
};
|
|
16114
|
-
} catch {
|
|
16115
|
-
return { isRepo: false };
|
|
16116
|
-
}
|
|
16117
|
-
}
|
|
16118
|
-
const _initInFlight = /* @__PURE__ */ new Set();
|
|
16119
|
-
/** True iff a background init for this workspace is already in flight. */
|
|
16120
|
-
function isInitInFlight(workspace) {
|
|
16121
|
-
return _initInFlight.has(initKey(workspace));
|
|
16122
|
-
}
|
|
16123
|
-
/** Mark a background init started (debounce). Returns false if already running. */
|
|
16124
|
-
function tryClaimInit(workspace) {
|
|
16125
|
-
const k = initKey(workspace);
|
|
16126
|
-
if (_initInFlight.has(k)) return false;
|
|
16127
|
-
_initInFlight.add(k);
|
|
16128
|
-
return true;
|
|
16129
|
-
}
|
|
16130
|
-
/** Release the debounce claim (call in the init's finally). */
|
|
16131
|
-
function releaseInit(workspace) {
|
|
16132
|
-
_initInFlight.delete(initKey(workspace));
|
|
16133
|
-
}
|
|
16134
|
-
function initKey(workspace) {
|
|
16135
|
-
return `${MODEL_ID}::${canonicalForCompare(workspace)}`;
|
|
16136
|
-
}
|
|
16137
|
-
|
|
16138
15864
|
//#endregion
|
|
16139
15865
|
//#region src/lib/toolbelt/extract.ts
|
|
16140
15866
|
function baseName(p) {
|
|
@@ -16167,13 +15893,13 @@ function baseName(p) {
|
|
|
16167
15893
|
*/
|
|
16168
15894
|
async function extractTarXzMember(buf, wantBasename, tmpDir) {
|
|
16169
15895
|
const { spawn: spawn$1 } = await import("node:child_process");
|
|
16170
|
-
const fs$
|
|
15896
|
+
const fs$2 = await import("node:fs/promises");
|
|
16171
15897
|
const path$1 = await import("node:path");
|
|
16172
15898
|
const archivePath = path$1.join(tmpDir, "archive.tar.xz");
|
|
16173
15899
|
const extractDir = path$1.join(tmpDir, "x");
|
|
16174
15900
|
try {
|
|
16175
|
-
await fs$
|
|
16176
|
-
await fs$
|
|
15901
|
+
await fs$2.mkdir(extractDir, { recursive: true });
|
|
15902
|
+
await fs$2.writeFile(archivePath, buf);
|
|
16177
15903
|
} catch {
|
|
16178
15904
|
return null;
|
|
16179
15905
|
}
|
|
@@ -16210,25 +15936,25 @@ async function extractTarXzMember(buf, wantBasename, tmpDir) {
|
|
|
16210
15936
|
resolve(code === 0);
|
|
16211
15937
|
});
|
|
16212
15938
|
})) return null;
|
|
16213
|
-
const found = await findRegularFile(fs$
|
|
15939
|
+
const found = await findRegularFile(fs$2, path$1, extractDir, new Set([wantBasename, `${wantBasename}.exe`]), 6);
|
|
16214
15940
|
if (!found) return null;
|
|
16215
15941
|
try {
|
|
16216
|
-
return await fs$
|
|
15942
|
+
return await fs$2.readFile(found);
|
|
16217
15943
|
} catch {
|
|
16218
15944
|
return null;
|
|
16219
15945
|
}
|
|
16220
15946
|
}
|
|
16221
|
-
async function findRegularFile(fs$
|
|
15947
|
+
async function findRegularFile(fs$2, path$1, dir, wants, depthBudget) {
|
|
16222
15948
|
if (depthBudget < 0) return null;
|
|
16223
15949
|
let entries;
|
|
16224
15950
|
try {
|
|
16225
|
-
entries = await fs$
|
|
15951
|
+
entries = await fs$2.readdir(dir, { withFileTypes: true });
|
|
16226
15952
|
} catch {
|
|
16227
15953
|
return null;
|
|
16228
15954
|
}
|
|
16229
15955
|
for (const e of entries) if (e.isFile() && wants.has(e.name)) return path$1.join(dir, e.name);
|
|
16230
15956
|
for (const e of entries) if (e.isDirectory()) {
|
|
16231
|
-
const hit = await findRegularFile(fs$
|
|
15957
|
+
const hit = await findRegularFile(fs$2, path$1, path$1.join(dir, e.name), wants, depthBudget - 1);
|
|
16232
15958
|
if (hit) return hit;
|
|
16233
15959
|
}
|
|
16234
15960
|
return null;
|
|
@@ -16339,6 +16065,14 @@ function colgrepBinaryPath() {
|
|
|
16339
16065
|
function colbertModelDir() {
|
|
16340
16066
|
return nodePath.join(PATHS.COLBERT_MODELS_DIR, "LateOn-Code-edge", modelDirName());
|
|
16341
16067
|
}
|
|
16068
|
+
/**
|
|
16069
|
+
* Canonical model argument for every colgrep invocation and project lookup.
|
|
16070
|
+
* colgrep hashes the raw model string, so slash variants fork one physical
|
|
16071
|
+
* index into distinct project keys on Windows.
|
|
16072
|
+
*/
|
|
16073
|
+
function canonicalColbertModelDir() {
|
|
16074
|
+
return nodePath.resolve(colbertModelDir());
|
|
16075
|
+
}
|
|
16342
16076
|
/** Absolute path the provisioned ORT dylib lives at. */
|
|
16343
16077
|
function colbertOrtDylibPath() {
|
|
16344
16078
|
const lib = ortLibAsset()?.member ?? "libonnxruntime.so";
|
|
@@ -16461,7 +16195,7 @@ async function provisionColbert() {
|
|
|
16461
16195
|
result.status = "incomplete";
|
|
16462
16196
|
});
|
|
16463
16197
|
if (result.binaryPath && result.ortDylibPath && result.modelDir) {
|
|
16464
|
-
const smoke = await runSmokeTest(result.binaryPath, result.ortDylibPath
|
|
16198
|
+
const smoke = await runSmokeTest(result.binaryPath, result.ortDylibPath);
|
|
16465
16199
|
if (smoke.ok) {
|
|
16466
16200
|
await writeFile(smokeMarkerPath(), expectedSmokeMarker()).catch(() => {});
|
|
16467
16201
|
result.status = "ready";
|
|
@@ -16590,7 +16324,7 @@ async function sidecarMatches(sidecar, sha256) {
|
|
|
16590
16324
|
* scanning stderr for colgrep's exact "ignoring" warning — if present,
|
|
16591
16325
|
* the dylib didn't load and we fail the smoke test even on exit 0.
|
|
16592
16326
|
*/
|
|
16593
|
-
async function runSmokeTest(binaryPath, ortDylibPath
|
|
16327
|
+
async function runSmokeTest(binaryPath, ortDylibPath) {
|
|
16594
16328
|
const tmp = nodePath.join(PATHS.COLBERT_DIR, `smoke-${process$1.pid}-${randomBytes(4).toString("hex")}`);
|
|
16595
16329
|
const fixtureDir = nodePath.join(tmp, "fixture");
|
|
16596
16330
|
const dataDir = nodePath.join(tmp, "data");
|
|
@@ -16619,7 +16353,7 @@ async function runSmokeTest(binaryPath, ortDylibPath, modelDir) {
|
|
|
16619
16353
|
"never",
|
|
16620
16354
|
"--force-cpu",
|
|
16621
16355
|
"--model",
|
|
16622
|
-
|
|
16356
|
+
canonicalColbertModelDir(),
|
|
16623
16357
|
"-y",
|
|
16624
16358
|
"-k",
|
|
16625
16359
|
"1",
|
|
@@ -16657,6 +16391,395 @@ async function runSmokeTest(binaryPath, ortDylibPath, modelDir) {
|
|
|
16657
16391
|
}
|
|
16658
16392
|
}
|
|
16659
16393
|
|
|
16394
|
+
//#endregion
|
|
16395
|
+
//#region src/lib/colbert/index-store.ts
|
|
16396
|
+
const GIT_TIMEOUT_MS = 4e3;
|
|
16397
|
+
/** Grace window after a `building` write before a workspace with no live
|
|
16398
|
+
* build PID is declared `crashed` — covers the cross-process window where
|
|
16399
|
+
* one proxy wrote `building` but hasn't yet recorded the colgrep child PID. */
|
|
16400
|
+
const BUILD_SPAWN_GRACE_MS = 3e4;
|
|
16401
|
+
/**
|
|
16402
|
+
* Hash a workspace path the same way the metadata sidecar is keyed.
|
|
16403
|
+
* NOTE: this is the ROUTER-OWNED meta key, independent of colgrep's
|
|
16404
|
+
* internal xxh3 physical-dir key (we never need to predict colgrep's
|
|
16405
|
+
* key because we pass the workspace as colgrep's PATH arg and let it
|
|
16406
|
+
* route). A stable sha256-prefix of the canonical path is sufficient.
|
|
16407
|
+
*/
|
|
16408
|
+
function metaHashForWorkspace(workspace) {
|
|
16409
|
+
const canonical = process$1.platform === "win32" ? nodePath.resolve(workspace).toLowerCase().replace(/\\/g, "/") : nodePath.resolve(workspace);
|
|
16410
|
+
let h = 2166136261;
|
|
16411
|
+
for (let i = 0; i < canonical.length; i++) {
|
|
16412
|
+
h ^= canonical.charCodeAt(i);
|
|
16413
|
+
h = Math.imul(h, 16777619);
|
|
16414
|
+
}
|
|
16415
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
16416
|
+
}
|
|
16417
|
+
function metaPath(workspace) {
|
|
16418
|
+
return nodePath.join(PATHS.COLBERT_META_DIR, `${metaHashForWorkspace(workspace)}.json`);
|
|
16419
|
+
}
|
|
16420
|
+
/** Read the sidecar metadata for a workspace (null if none yet). */
|
|
16421
|
+
async function readColbertMeta(workspace) {
|
|
16422
|
+
try {
|
|
16423
|
+
const raw = await fs.readFile(metaPath(workspace), "utf8");
|
|
16424
|
+
const parsed = JSON.parse(raw);
|
|
16425
|
+
if (parsed && typeof parsed === "object" && typeof parsed.status === "string") return parsed;
|
|
16426
|
+
return null;
|
|
16427
|
+
} catch {
|
|
16428
|
+
return null;
|
|
16429
|
+
}
|
|
16430
|
+
}
|
|
16431
|
+
/**
|
|
16432
|
+
* Per-workspace write serializer. `runInit` issues a pre-spawn
|
|
16433
|
+
* `building` write, an `onSpawn` write that patches in the colgrep child
|
|
16434
|
+
* PID, and a final `ready`/`failed` write. Chaining them per workspace
|
|
16435
|
+
* guarantees the final write lands AFTER the (fire-and-forget) onSpawn
|
|
16436
|
+
* write, so a `ready` result is never clobbered back to `building` by a
|
|
16437
|
+
* late atomic-rename.
|
|
16438
|
+
*/
|
|
16439
|
+
const _metaWriteChains = /* @__PURE__ */ new Map();
|
|
16440
|
+
/** Atomically write the sidecar metadata for a workspace (serialized). */
|
|
16441
|
+
async function writeColbertMeta(meta) {
|
|
16442
|
+
const key = metaHashForWorkspace(meta.workspace);
|
|
16443
|
+
const next = (_metaWriteChains.get(key) ?? Promise.resolve()).then(() => writeColbertMetaUnchained(meta));
|
|
16444
|
+
_metaWriteChains.set(key, next.then(() => void 0, () => void 0));
|
|
16445
|
+
return next;
|
|
16446
|
+
}
|
|
16447
|
+
async function writeColbertMetaUnchained(meta) {
|
|
16448
|
+
await fs.mkdir(PATHS.COLBERT_META_DIR, { recursive: true });
|
|
16449
|
+
const dest = metaPath(meta.workspace);
|
|
16450
|
+
const tmp = `${dest}.${process$1.pid}.${Math.random().toString(16).slice(2, 10)}.tmp`;
|
|
16451
|
+
try {
|
|
16452
|
+
await fs.writeFile(tmp, JSON.stringify(meta, null, 2));
|
|
16453
|
+
await fs.rename(tmp, dest);
|
|
16454
|
+
} catch (err) {
|
|
16455
|
+
await fs.rm(tmp, { force: true }).catch(() => {});
|
|
16456
|
+
throw err;
|
|
16457
|
+
}
|
|
16458
|
+
}
|
|
16459
|
+
/**
|
|
16460
|
+
* Whether a COMPLETED colgrep index exists on disk for this workspace.
|
|
16461
|
+
* The preflight uses this to distinguish `building`/`absent` (no
|
|
16462
|
+
* completed index → don't spawn a foreground colgrep) from a real
|
|
16463
|
+
* index. We scan COLGREP_DATA_DIR for any per-project dir containing a
|
|
16464
|
+
* `project.json` whose canonical path matches this workspace AND an
|
|
16465
|
+
* `index/metadata.json` marker.
|
|
16466
|
+
*/
|
|
16467
|
+
async function colbertProjectDir(workspace) {
|
|
16468
|
+
const indicesDir = PATHS.COLBERT_INDICES_DIR;
|
|
16469
|
+
let names;
|
|
16470
|
+
try {
|
|
16471
|
+
names = await fs.readdir(indicesDir);
|
|
16472
|
+
} catch {
|
|
16473
|
+
return null;
|
|
16474
|
+
}
|
|
16475
|
+
const wantCanonical = await realpathForCompare(workspace);
|
|
16476
|
+
for (const name of names) {
|
|
16477
|
+
if (name === ".gh-router-meta" || name.includes(".corrupt-")) continue;
|
|
16478
|
+
const dir = nodePath.join(indicesDir, name);
|
|
16479
|
+
let proj;
|
|
16480
|
+
try {
|
|
16481
|
+
proj = JSON.parse(await fs.readFile(nodePath.join(dir, "project.json"), "utf8"));
|
|
16482
|
+
} catch {
|
|
16483
|
+
continue;
|
|
16484
|
+
}
|
|
16485
|
+
const projPath = proj.path ?? proj.project_path;
|
|
16486
|
+
if (!projPath || await realpathForCompare(projPath) !== wantCanonical) continue;
|
|
16487
|
+
if (proj.model !== canonicalColbertModelDir()) continue;
|
|
16488
|
+
return dir;
|
|
16489
|
+
}
|
|
16490
|
+
return null;
|
|
16491
|
+
}
|
|
16492
|
+
/** Validate the contiguous embedding interval encoded by PLAID shard metadata. */
|
|
16493
|
+
function validateIndexIntegrity(projectIndexDir) {
|
|
16494
|
+
let names;
|
|
16495
|
+
try {
|
|
16496
|
+
names = readdirSync(projectIndexDir).filter((name) => /^\d+\.metadata\.json$/.test(name));
|
|
16497
|
+
} catch (err) {
|
|
16498
|
+
return err.code === "ENOENT" ? { verdict: "not-built" } : {
|
|
16499
|
+
verdict: "corrupt",
|
|
16500
|
+
reason: "index directory unreadable"
|
|
16501
|
+
};
|
|
16502
|
+
}
|
|
16503
|
+
if (names.length === 0) return { verdict: "not-built" };
|
|
16504
|
+
const intervals = [];
|
|
16505
|
+
for (const name of names) try {
|
|
16506
|
+
const parsed = JSON.parse(readFileSync(nodePath.join(projectIndexDir, name), "utf8"));
|
|
16507
|
+
const start = parsed.embedding_offset;
|
|
16508
|
+
const count = parsed.num_embeddings;
|
|
16509
|
+
if (typeof start !== "number" || typeof count !== "number" || !Number.isSafeInteger(start) || !Number.isSafeInteger(count) || start < 0 || count < 0) return {
|
|
16510
|
+
verdict: "corrupt",
|
|
16511
|
+
reason: `invalid shard metadata: ${name}`
|
|
16512
|
+
};
|
|
16513
|
+
intervals.push({
|
|
16514
|
+
start,
|
|
16515
|
+
count
|
|
16516
|
+
});
|
|
16517
|
+
} catch {
|
|
16518
|
+
return {
|
|
16519
|
+
verdict: "corrupt",
|
|
16520
|
+
reason: `unreadable shard metadata: ${name}`
|
|
16521
|
+
};
|
|
16522
|
+
}
|
|
16523
|
+
intervals.sort((a, b) => a.start - b.start || a.count - b.count);
|
|
16524
|
+
let cursorEnd = 0;
|
|
16525
|
+
let sum = 0;
|
|
16526
|
+
let gapped = false;
|
|
16527
|
+
for (const interval of intervals) {
|
|
16528
|
+
if (interval.start < cursorEnd) return {
|
|
16529
|
+
verdict: "corrupt",
|
|
16530
|
+
reason: "overlapping shard intervals"
|
|
16531
|
+
};
|
|
16532
|
+
if (interval.start > cursorEnd) gapped = true;
|
|
16533
|
+
cursorEnd = interval.start + interval.count;
|
|
16534
|
+
sum += interval.count;
|
|
16535
|
+
}
|
|
16536
|
+
if (gapped) return {
|
|
16537
|
+
verdict: "suspect",
|
|
16538
|
+
reason: "gap between shard intervals"
|
|
16539
|
+
};
|
|
16540
|
+
return {
|
|
16541
|
+
verdict: "coherent",
|
|
16542
|
+
shardCount: intervals.length,
|
|
16543
|
+
embeddingCount: sum
|
|
16544
|
+
};
|
|
16545
|
+
}
|
|
16546
|
+
async function completedIndexOnDisk(workspace) {
|
|
16547
|
+
const projectDir = await colbertProjectDir(workspace);
|
|
16548
|
+
if (!projectDir) return false;
|
|
16549
|
+
return validateIndexIntegrity(nodePath.join(projectDir, "index")).verdict !== "not-built";
|
|
16550
|
+
}
|
|
16551
|
+
function canonicalForCompare(p) {
|
|
16552
|
+
return process$1.platform === "win32" ? nodePath.resolve(p).toLowerCase().replace(/\\/g, "/") : nodePath.resolve(p);
|
|
16553
|
+
}
|
|
16554
|
+
/** Sync realpath-aware canonicalization (sibling of `realpathForCompare`,
|
|
16555
|
+
* for the on-a-timer inactivity probe which must be synchronous). */
|
|
16556
|
+
function canonicalRealpathSync(p) {
|
|
16557
|
+
try {
|
|
16558
|
+
return canonicalForCompare(realpathSync(p));
|
|
16559
|
+
} catch {
|
|
16560
|
+
return canonicalForCompare(p);
|
|
16561
|
+
}
|
|
16562
|
+
}
|
|
16563
|
+
/** Recursive (bytes, fileCount) of a directory; sync + best-effort. A
|
|
16564
|
+
* colgrep index is a bounded set of shards so the walk stays small. */
|
|
16565
|
+
function dirSizeSync(dir) {
|
|
16566
|
+
let bytes = 0;
|
|
16567
|
+
let count = 0;
|
|
16568
|
+
let entries;
|
|
16569
|
+
try {
|
|
16570
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
16571
|
+
} catch {
|
|
16572
|
+
return [0, 0];
|
|
16573
|
+
}
|
|
16574
|
+
for (const e of entries) {
|
|
16575
|
+
const p = nodePath.join(dir, e.name);
|
|
16576
|
+
if (e.isDirectory()) {
|
|
16577
|
+
const [b, c] = dirSizeSync(p);
|
|
16578
|
+
bytes += b;
|
|
16579
|
+
count += c;
|
|
16580
|
+
} else try {
|
|
16581
|
+
bytes += statSync(p).size;
|
|
16582
|
+
count += 1;
|
|
16583
|
+
} catch {}
|
|
16584
|
+
}
|
|
16585
|
+
return [bytes, count];
|
|
16586
|
+
}
|
|
16587
|
+
/**
|
|
16588
|
+
* (sync) Progress signature of a workspace's colgrep index dir for the init
|
|
16589
|
+
* inactivity watchdog: `${totalBytes}:${fileCount}` of the project dir, or
|
|
16590
|
+
* `null` if it isn't on disk yet. colgrep is SILENT on a non-TTY pipe
|
|
16591
|
+
* during the (potentially multi-hour) encode phase, so output is useless as
|
|
16592
|
+
* a progress signal — but it writes index shards incrementally, so a
|
|
16593
|
+
* changing signature means "still progressing" and a frozen one means
|
|
16594
|
+
* "hung". Successive signatures drive the watchdog: change ⇒ re-arm, frozen
|
|
16595
|
+
* ⇒ kill. Sync because it's called from a `setTimeout` (not awaited).
|
|
16596
|
+
*/
|
|
16597
|
+
function indexDirSignature(workspace) {
|
|
16598
|
+
const indicesDir = PATHS.COLBERT_INDICES_DIR;
|
|
16599
|
+
let names;
|
|
16600
|
+
try {
|
|
16601
|
+
names = readdirSync(indicesDir);
|
|
16602
|
+
} catch {
|
|
16603
|
+
return null;
|
|
16604
|
+
}
|
|
16605
|
+
const want = canonicalRealpathSync(workspace);
|
|
16606
|
+
for (const name of names) {
|
|
16607
|
+
if (name === ".gh-router-meta") continue;
|
|
16608
|
+
if (name.includes(".corrupt-")) continue;
|
|
16609
|
+
const dir = nodePath.join(indicesDir, name);
|
|
16610
|
+
let proj;
|
|
16611
|
+
try {
|
|
16612
|
+
proj = JSON.parse(readFileSync(nodePath.join(dir, "project.json"), "utf8"));
|
|
16613
|
+
} catch {
|
|
16614
|
+
continue;
|
|
16615
|
+
}
|
|
16616
|
+
const projPath = proj.path ?? proj.project_path;
|
|
16617
|
+
if (!projPath || canonicalRealpathSync(projPath) !== want) continue;
|
|
16618
|
+
const [bytes, count] = dirSizeSync(dir);
|
|
16619
|
+
return `${bytes}:${count}`;
|
|
16620
|
+
}
|
|
16621
|
+
return null;
|
|
16622
|
+
}
|
|
16623
|
+
/**
|
|
16624
|
+
* Realpath-aware canonicalization for matching a workspace against
|
|
16625
|
+
* colgrep's stored `project_path`. colgrep stores the OS realpath (e.g.
|
|
16626
|
+
* macOS `/tmp` → `/private/tmp`, Windows 8.3 short names), so a plain
|
|
16627
|
+
* `path.resolve` comparison misses. Falls back to `canonicalForCompare`
|
|
16628
|
+
* when realpath fails (path doesn't exist yet).
|
|
16629
|
+
*/
|
|
16630
|
+
async function realpathForCompare(p) {
|
|
16631
|
+
try {
|
|
16632
|
+
return canonicalForCompare(await fs.realpath(p));
|
|
16633
|
+
} catch {
|
|
16634
|
+
return canonicalForCompare(p);
|
|
16635
|
+
}
|
|
16636
|
+
}
|
|
16637
|
+
/**
|
|
16638
|
+
* Compute the freshness verdict for a query against a workspace.
|
|
16639
|
+
*
|
|
16640
|
+
* Routing (per the dropped-fallback contract):
|
|
16641
|
+
* - `failed` — sidecar says failed → caller returns isError.
|
|
16642
|
+
* - `building` — a tracked init is live OR no completed index on disk
|
|
16643
|
+
* → caller returns building notice (NO results).
|
|
16644
|
+
* - `absent` — never indexed → caller kicks a debounced background
|
|
16645
|
+
* init, returns absent (isError).
|
|
16646
|
+
* - `stale` — ready but HEAD moved / tree dirty since index → caller
|
|
16647
|
+
* returns stale notice (NO results, NO re-search).
|
|
16648
|
+
* - `fresh` — ready + completed index + HEAD matches + not newly
|
|
16649
|
+
* dirty → caller spawns colgrep search.
|
|
16650
|
+
*/
|
|
16651
|
+
async function freshnessVerdict(workspace) {
|
|
16652
|
+
const meta = await readColbertMeta(workspace);
|
|
16653
|
+
if (!meta || meta.status === "absent") return {
|
|
16654
|
+
verdict: "absent",
|
|
16655
|
+
meta
|
|
16656
|
+
};
|
|
16657
|
+
if (meta.status === "failed") return {
|
|
16658
|
+
verdict: "failed",
|
|
16659
|
+
meta
|
|
16660
|
+
};
|
|
16661
|
+
if (meta.status === "building") {
|
|
16662
|
+
const pid = typeof meta.buildPid === "number" ? meta.buildPid : 0;
|
|
16663
|
+
if (isInitInFlight(workspace) || pid > 0 && isPidAlive(pid)) return {
|
|
16664
|
+
verdict: "building",
|
|
16665
|
+
meta
|
|
16666
|
+
};
|
|
16667
|
+
const startedMs = meta.lastIndexedAt ? Date.parse(meta.lastIndexedAt) : NaN;
|
|
16668
|
+
if (Number.isFinite(startedMs) && Date.now() - startedMs < BUILD_SPAWN_GRACE_MS) return {
|
|
16669
|
+
verdict: "building",
|
|
16670
|
+
meta
|
|
16671
|
+
};
|
|
16672
|
+
if (!await completedIndexOnDisk(workspace)) return {
|
|
16673
|
+
verdict: "crashed",
|
|
16674
|
+
meta
|
|
16675
|
+
};
|
|
16676
|
+
}
|
|
16677
|
+
const projectDir = await colbertProjectDir(workspace);
|
|
16678
|
+
if (!projectDir) return {
|
|
16679
|
+
verdict: "building",
|
|
16680
|
+
meta
|
|
16681
|
+
};
|
|
16682
|
+
const integrity = validateIndexIntegrity(nodePath.join(projectDir, "index"));
|
|
16683
|
+
if (integrity.verdict === "not-built") return {
|
|
16684
|
+
verdict: "building",
|
|
16685
|
+
meta
|
|
16686
|
+
};
|
|
16687
|
+
if (integrity.verdict === "corrupt") return {
|
|
16688
|
+
verdict: "corrupt",
|
|
16689
|
+
meta
|
|
16690
|
+
};
|
|
16691
|
+
if (integrity.verdict === "suspect") return {
|
|
16692
|
+
verdict: "stale",
|
|
16693
|
+
meta
|
|
16694
|
+
};
|
|
16695
|
+
const binarySha = colgrepBinAsset()?.sha256;
|
|
16696
|
+
const ortSha = ortLibAsset()?.sha256;
|
|
16697
|
+
if (binarySha && meta.binarySha !== binarySha || ortSha && meta.ortSha !== ortSha) return {
|
|
16698
|
+
verdict: "stale",
|
|
16699
|
+
meta
|
|
16700
|
+
};
|
|
16701
|
+
const git = await gitState(workspace);
|
|
16702
|
+
if (!git.isRepo) return {
|
|
16703
|
+
verdict: "fresh",
|
|
16704
|
+
meta
|
|
16705
|
+
};
|
|
16706
|
+
const headMoved = meta.lastIndexedHead !== void 0 && git.head !== meta.lastIndexedHead;
|
|
16707
|
+
const newlyDirty = git.dirty && meta.lastIndexedDirty !== true;
|
|
16708
|
+
if (headMoved || newlyDirty) return {
|
|
16709
|
+
verdict: "stale",
|
|
16710
|
+
meta,
|
|
16711
|
+
head: git.head,
|
|
16712
|
+
dirty: git.dirty
|
|
16713
|
+
};
|
|
16714
|
+
return {
|
|
16715
|
+
verdict: "fresh",
|
|
16716
|
+
meta,
|
|
16717
|
+
head: git.head,
|
|
16718
|
+
dirty: git.dirty
|
|
16719
|
+
};
|
|
16720
|
+
}
|
|
16721
|
+
/** Cheap, bounded git probe via the native-exe runner. */
|
|
16722
|
+
async function gitState(workspace) {
|
|
16723
|
+
const git = resolveExecutable("git");
|
|
16724
|
+
if (!git) return { isRepo: false };
|
|
16725
|
+
try {
|
|
16726
|
+
const inside = await runManagedExeCapture(git, [
|
|
16727
|
+
"-C",
|
|
16728
|
+
workspace,
|
|
16729
|
+
"rev-parse",
|
|
16730
|
+
"--is-inside-work-tree"
|
|
16731
|
+
], {
|
|
16732
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
16733
|
+
maxStdoutBytes: 64 * 1024
|
|
16734
|
+
});
|
|
16735
|
+
if (inside.code !== 0 || inside.stdout.trim() !== "true") return { isRepo: false };
|
|
16736
|
+
const head = await runManagedExeCapture(git, [
|
|
16737
|
+
"-C",
|
|
16738
|
+
workspace,
|
|
16739
|
+
"rev-parse",
|
|
16740
|
+
"HEAD"
|
|
16741
|
+
], {
|
|
16742
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
16743
|
+
maxStdoutBytes: 64 * 1024
|
|
16744
|
+
});
|
|
16745
|
+
const status = await runManagedExeCapture(git, [
|
|
16746
|
+
"-C",
|
|
16747
|
+
workspace,
|
|
16748
|
+
"status",
|
|
16749
|
+
"--porcelain"
|
|
16750
|
+
], {
|
|
16751
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
16752
|
+
maxStdoutBytes: 1024 * 1024
|
|
16753
|
+
});
|
|
16754
|
+
return {
|
|
16755
|
+
isRepo: true,
|
|
16756
|
+
head: head.code === 0 ? head.stdout.trim() || void 0 : void 0,
|
|
16757
|
+
dirty: status.code === 0 ? status.stdout.trim().length > 0 : void 0
|
|
16758
|
+
};
|
|
16759
|
+
} catch {
|
|
16760
|
+
return { isRepo: false };
|
|
16761
|
+
}
|
|
16762
|
+
}
|
|
16763
|
+
const _initInFlight = /* @__PURE__ */ new Set();
|
|
16764
|
+
/** True iff a background init for this workspace is already in flight. */
|
|
16765
|
+
function isInitInFlight(workspace) {
|
|
16766
|
+
return _initInFlight.has(initKey(workspace));
|
|
16767
|
+
}
|
|
16768
|
+
/** Mark a background init started (debounce). Returns false if already running. */
|
|
16769
|
+
function tryClaimInit(workspace) {
|
|
16770
|
+
const k = initKey(workspace);
|
|
16771
|
+
if (_initInFlight.has(k)) return false;
|
|
16772
|
+
_initInFlight.add(k);
|
|
16773
|
+
return true;
|
|
16774
|
+
}
|
|
16775
|
+
/** Release the debounce claim (call in the init's finally). */
|
|
16776
|
+
function releaseInit(workspace) {
|
|
16777
|
+
_initInFlight.delete(initKey(workspace));
|
|
16778
|
+
}
|
|
16779
|
+
function initKey(workspace) {
|
|
16780
|
+
return `${MODEL_ID}::${canonicalForCompare(workspace)}`;
|
|
16781
|
+
}
|
|
16782
|
+
|
|
16660
16783
|
//#endregion
|
|
16661
16784
|
//#region src/lib/colbert/runner.ts
|
|
16662
16785
|
/** Caller responsiveness budget for a search. A warm search is sub-second;
|
|
@@ -16726,6 +16849,10 @@ function makeIndexProgressProbe(workspace) {
|
|
|
16726
16849
|
* colgrep writer per workspace" goal as the init debounce. Cleared when the
|
|
16727
16850
|
* detached search completes. */
|
|
16728
16851
|
const _searchIndexInFlight = /* @__PURE__ */ new Set();
|
|
16852
|
+
const _initPromises = /* @__PURE__ */ new Map();
|
|
16853
|
+
/** In-flight quarantine deletions, drained by teardown (Windows EBUSY). */
|
|
16854
|
+
const _quarantineRemovals = /* @__PURE__ */ new Set();
|
|
16855
|
+
let _runManagedExeCapture = runManagedExeCapture;
|
|
16729
16856
|
/** Build the isolating env for any colgrep child (search or init). */
|
|
16730
16857
|
function colgrepEnv() {
|
|
16731
16858
|
const ortDir = nodePath.dirname(colbertOrtDylibPath());
|
|
@@ -16763,6 +16890,7 @@ async function runSemanticSearch(opts) {
|
|
|
16763
16890
|
};
|
|
16764
16891
|
case "failed": return handleFailure(workspace, fresh.meta, false);
|
|
16765
16892
|
case "crashed": return handleFailure(workspace, fresh.meta, true);
|
|
16893
|
+
case "corrupt": return repairCorruptIndex(workspace, fresh.meta);
|
|
16766
16894
|
case "building": return {
|
|
16767
16895
|
status: "building",
|
|
16768
16896
|
notice: "semantic index is being built for this workspace; retry shortly (or use code_search now)"
|
|
@@ -16795,6 +16923,64 @@ async function runSemanticSearch(opts) {
|
|
|
16795
16923
|
* by the inactivity watchdog) retries at most once — re-running a hung build
|
|
16796
16924
|
* usually hangs again; transient classes retry up to `MAX_FAILED_ATTEMPTS`.
|
|
16797
16925
|
*/
|
|
16926
|
+
async function quarantineProjectDir(projectDir) {
|
|
16927
|
+
const quarantine = `${projectDir}.corrupt-${process$1.pid}-${randomBytes(4).toString("hex")}`;
|
|
16928
|
+
try {
|
|
16929
|
+
await fs.rename(projectDir, quarantine);
|
|
16930
|
+
} catch (err) {
|
|
16931
|
+
consola.debug("colbert: corrupt index quarantine rename failed:", err);
|
|
16932
|
+
return false;
|
|
16933
|
+
}
|
|
16934
|
+
const removal = fs.rm(quarantine, {
|
|
16935
|
+
recursive: true,
|
|
16936
|
+
force: true
|
|
16937
|
+
}).catch((err) => {
|
|
16938
|
+
consola.debug("colbert: corrupt index quarantine cleanup failed:", err);
|
|
16939
|
+
}).finally(() => {
|
|
16940
|
+
_quarantineRemovals.delete(removal);
|
|
16941
|
+
});
|
|
16942
|
+
_quarantineRemovals.add(removal);
|
|
16943
|
+
return true;
|
|
16944
|
+
}
|
|
16945
|
+
async function repairCorruptIndex(workspace, meta) {
|
|
16946
|
+
const wsKey = nodePath.resolve(workspace);
|
|
16947
|
+
const attempts = (meta?.failureClass === "corrupt" ? meta.failedAttempts ?? 0 : 0) + 1;
|
|
16948
|
+
const failedMeta = {
|
|
16949
|
+
workspace,
|
|
16950
|
+
model: meta?.model ?? MODEL_ID,
|
|
16951
|
+
modelRev: meta?.modelRev ?? MODEL_REVISION,
|
|
16952
|
+
binarySha: colgrepBinAsset()?.sha256,
|
|
16953
|
+
ortSha: ortLibAsset()?.sha256,
|
|
16954
|
+
status: "failed",
|
|
16955
|
+
failureClass: "corrupt",
|
|
16956
|
+
failedAttempts: attempts,
|
|
16957
|
+
lastIndexedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16958
|
+
lastIndexedHead: meta?.lastIndexedHead,
|
|
16959
|
+
lastIndexedDirty: meta?.lastIndexedDirty,
|
|
16960
|
+
ownerInstanceId: getColbertInstanceUuid()
|
|
16961
|
+
};
|
|
16962
|
+
if (_searchIndexInFlight.has(wsKey) || isInitInFlight(workspace)) return {
|
|
16963
|
+
status: "building",
|
|
16964
|
+
notice: "semantic index was found corrupt but a writer is still active; returned results are disabled until it exits"
|
|
16965
|
+
};
|
|
16966
|
+
const projectDir = await colbertProjectDir(workspace);
|
|
16967
|
+
if (!projectDir) return handleFailure(workspace, failedMeta, false);
|
|
16968
|
+
if (!await quarantineProjectDir(projectDir)) {
|
|
16969
|
+
await writeColbertMeta(failedMeta).catch(() => {});
|
|
16970
|
+
return {
|
|
16971
|
+
status: "failed",
|
|
16972
|
+
isError: true,
|
|
16973
|
+
notice: "semantic index is corrupt but could not be quarantined; returned lexical results — close active colgrep processes and retry"
|
|
16974
|
+
};
|
|
16975
|
+
}
|
|
16976
|
+
await writeColbertMeta(failedMeta).catch(() => {});
|
|
16977
|
+
if (attempts < 2) kickBackgroundInit(workspace);
|
|
16978
|
+
return {
|
|
16979
|
+
status: "failed",
|
|
16980
|
+
isError: true,
|
|
16981
|
+
notice: attempts < 2 ? "semantic index was found corrupt and quarantined; a clean rebuild was started — retry mode:\"semantic\" shortly" : "semantic index repeatedly failed integrity checks; automatic rebuild is capped — do NOT retry mode:\"semantic\", use lexical search with specific symbol/keyword terms and see proxy logs"
|
|
16982
|
+
};
|
|
16983
|
+
}
|
|
16798
16984
|
async function handleFailure(workspace, meta, crashedVerdict) {
|
|
16799
16985
|
const cls = crashedVerdict ? "crashed" : meta?.failureClass ?? "error";
|
|
16800
16986
|
const attempts = crashedVerdict ? (meta?.failedAttempts ?? 0) + 1 : meta?.failedAttempts ?? 1;
|
|
@@ -16811,7 +16997,7 @@ async function handleFailure(workspace, meta, crashedVerdict) {
|
|
|
16811
16997
|
lastIndexedDirty: meta?.lastIndexedDirty,
|
|
16812
16998
|
ownerInstanceId: getColbertInstanceUuid()
|
|
16813
16999
|
}).catch(() => {});
|
|
16814
|
-
const cap = cls === "stuck" ? 2 : MAX_FAILED_ATTEMPTS;
|
|
17000
|
+
const cap = cls === "stuck" || cls === "corrupt" ? 2 : MAX_FAILED_ATTEMPTS;
|
|
16815
17001
|
const lastMs = lastAt ? Date.parse(lastAt) : NaN;
|
|
16816
17002
|
const backoffElapsed = !Number.isFinite(lastMs) || Date.now() - lastMs >= FAILED_RETRY_BACKOFF_MS;
|
|
16817
17003
|
if (attempts < cap && backoffElapsed) {
|
|
@@ -16854,7 +17040,7 @@ async function spawnSearch(opts) {
|
|
|
16854
17040
|
"never",
|
|
16855
17041
|
"--force-cpu",
|
|
16856
17042
|
"--model",
|
|
16857
|
-
|
|
17043
|
+
canonicalColbertModelDir(),
|
|
16858
17044
|
"-y",
|
|
16859
17045
|
"-k",
|
|
16860
17046
|
String(opts.limit)
|
|
@@ -16869,7 +17055,7 @@ async function spawnSearch(opts) {
|
|
|
16869
17055
|
_searchIndexInFlight.add(wsKey);
|
|
16870
17056
|
let searchPromise;
|
|
16871
17057
|
try {
|
|
16872
|
-
searchPromise =
|
|
17058
|
+
searchPromise = _runManagedExeCapture(binary, args, {
|
|
16873
17059
|
env: colgrepEnv(),
|
|
16874
17060
|
inactivityTimeoutMs: INIT_STALL_MS,
|
|
16875
17061
|
onInactivityCheck: makeIndexProgressProbe(opts.workspace),
|
|
@@ -16985,14 +17171,24 @@ function buildSnippet(unit) {
|
|
|
16985
17171
|
const sig = typeof unit.signature === "string" ? unit.signature.trim() : "";
|
|
16986
17172
|
const code = typeof unit.code === "string" ? unit.code : "";
|
|
16987
17173
|
if (!code) return sig;
|
|
16988
|
-
const
|
|
16989
|
-
const
|
|
17174
|
+
const lines = code.split("\n");
|
|
17175
|
+
const body = lines.slice(0, 6).join("\n");
|
|
17176
|
+
const firstBodyLine = lines[0]?.trim() ?? "";
|
|
17177
|
+
const snippet = sig && firstBodyLine !== sig ? `${sig}\n${body}` : body;
|
|
16990
17178
|
return snippet.length > 600 ? snippet.slice(0, 600) + "…" : snippet;
|
|
16991
17179
|
}
|
|
17180
|
+
function stripExtendedPathPrefix(value) {
|
|
17181
|
+
if (/^\\\\\?\\UNC\\/i.test(value)) return `\\\\${value.slice(8)}`;
|
|
17182
|
+
if (/^\\\\\?\\[a-z]:\\/i.test(value)) return value.slice(4);
|
|
17183
|
+
return value;
|
|
17184
|
+
}
|
|
16992
17185
|
function relativize(file, workspace, workspaceReal) {
|
|
16993
|
-
|
|
16994
|
-
|
|
16995
|
-
|
|
17186
|
+
const normalizedFile = stripExtendedPathPrefix(file);
|
|
17187
|
+
for (const rawBase of [workspace, workspaceReal]) try {
|
|
17188
|
+
const base = stripExtendedPathPrefix(rawBase);
|
|
17189
|
+
const flavor = /^[a-z]:[\\/]/i.test(base) || base.startsWith("\\\\") ? nodePath.win32 : nodePath;
|
|
17190
|
+
const rel = flavor.relative(base, normalizedFile);
|
|
17191
|
+
if (rel && !rel.startsWith("..") && !flavor.isAbsolute(rel)) return rel;
|
|
16996
17192
|
} catch {}
|
|
16997
17193
|
return file;
|
|
16998
17194
|
}
|
|
@@ -17019,9 +17215,31 @@ function clampLimit(limit) {
|
|
|
17019
17215
|
function kickBackgroundInit(workspace) {
|
|
17020
17216
|
if (isInitInFlight(workspace)) return;
|
|
17021
17217
|
if (!tryClaimInit(workspace)) return;
|
|
17022
|
-
|
|
17023
|
-
|
|
17218
|
+
const key = nodePath.resolve(workspace);
|
|
17219
|
+
const promise = runInit(workspace).catch(async (err) => {
|
|
17220
|
+
releaseInit(workspace);
|
|
17221
|
+
consola.error("colbert: background init failed:", err);
|
|
17222
|
+
const prior = await readColbertMeta(workspace);
|
|
17223
|
+
await writeColbertMeta({
|
|
17224
|
+
workspace,
|
|
17225
|
+
model: prior?.model ?? MODEL_ID,
|
|
17226
|
+
modelRev: prior?.modelRev ?? MODEL_REVISION,
|
|
17227
|
+
binarySha: colgrepBinAsset()?.sha256,
|
|
17228
|
+
ortSha: ortLibAsset()?.sha256,
|
|
17229
|
+
status: "failed",
|
|
17230
|
+
failureClass: "launch",
|
|
17231
|
+
failedAttempts: (prior?.failedAttempts ?? 0) + 1,
|
|
17232
|
+
lastIndexedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17233
|
+
lastIndexedHead: prior?.lastIndexedHead,
|
|
17234
|
+
lastIndexedDirty: prior?.lastIndexedDirty,
|
|
17235
|
+
ownerInstanceId: getColbertInstanceUuid()
|
|
17236
|
+
}).catch((writeErr) => {
|
|
17237
|
+
consola.error("colbert: failed to record background init failure:", writeErr);
|
|
17238
|
+
});
|
|
17239
|
+
}).finally(() => {
|
|
17240
|
+
_initPromises.delete(key);
|
|
17024
17241
|
});
|
|
17242
|
+
_initPromises.set(key, promise);
|
|
17025
17243
|
}
|
|
17026
17244
|
/**
|
|
17027
17245
|
* Whether the STARTUP auto-kick should fire for a workspace. Skips a build
|
|
@@ -17034,25 +17252,72 @@ function kickBackgroundInit(workspace) {
|
|
|
17034
17252
|
async function startupKickAllowed(workspace) {
|
|
17035
17253
|
const meta = await readColbertMeta(workspace);
|
|
17036
17254
|
if (!meta || meta.status !== "failed") return true;
|
|
17037
|
-
|
|
17255
|
+
const cap = meta.failureClass === "stuck" || meta.failureClass === "corrupt" ? 2 : MAX_FAILED_ATTEMPTS;
|
|
17256
|
+
if ((meta.failedAttempts ?? 0) >= cap) return false;
|
|
17038
17257
|
if (meta.failureClass === "stuck") return false;
|
|
17039
17258
|
return true;
|
|
17040
17259
|
}
|
|
17260
|
+
/**
|
|
17261
|
+
* Encoding sessions colgrep may run in parallel: 25% of the machine's
|
|
17262
|
+
* threads, never fewer than 2.
|
|
17263
|
+
*
|
|
17264
|
+
* colgrep defaults this to the FULL CPU count, so a background index build
|
|
17265
|
+
* saturates the machine — the exact opposite of what a background build
|
|
17266
|
+
* should do during an interactive agent session (the proxy even holds a
|
|
17267
|
+
* keep-awake assertion so those sessions run long and unattended). The floor
|
|
17268
|
+
* of 2 keeps a 1- to 7-thread box from dropping to a single session and
|
|
17269
|
+
* taking proportionally forever.
|
|
17270
|
+
*
|
|
17271
|
+
* Override with `GH_ROUTER_COLBERT_PARALLEL` (a positive integer).
|
|
17272
|
+
*/
|
|
17273
|
+
function colbertParallelSessions() {
|
|
17274
|
+
const raw = Number(process$1.env.GH_ROUTER_COLBERT_PARALLEL);
|
|
17275
|
+
if (Number.isSafeInteger(raw) && raw > 0) return raw;
|
|
17276
|
+
const threads = os.availableParallelism?.() ?? os.cpus?.().length ?? 4;
|
|
17277
|
+
return Math.max(2, Math.floor(threads * .25));
|
|
17278
|
+
}
|
|
17279
|
+
/**
|
|
17280
|
+
* Persist the parallelism cap into the router-owned colgrep config.
|
|
17281
|
+
*
|
|
17282
|
+
* `--parallel` exists only on the `settings` subcommand — there is no
|
|
17283
|
+
* per-run flag and no env var. It writes `parallel_sessions` to
|
|
17284
|
+
* `<COLGREP_DATA_DIR>/../config.json`, which for us is
|
|
17285
|
+
* `<APP_DIR>/colbert/config.json`: router-owned, and NOT the user's own
|
|
17286
|
+
* colgrep config (verified — after we write ours, a plain `colgrep
|
|
17287
|
+
* settings` with no COLGREP_DATA_DIR still reports `auto`).
|
|
17288
|
+
*
|
|
17289
|
+
* Best-effort: failing here means colgrep encodes at its default (all
|
|
17290
|
+
* threads), which is greedy but not incorrect, so it must never block a
|
|
17291
|
+
* build.
|
|
17292
|
+
*/
|
|
17293
|
+
async function applyParallelismCap(binary) {
|
|
17294
|
+
const sessions$1 = colbertParallelSessions();
|
|
17295
|
+
try {
|
|
17296
|
+
await _runManagedExeCapture(binary, [
|
|
17297
|
+
"settings",
|
|
17298
|
+
"--parallel",
|
|
17299
|
+
String(sessions$1)
|
|
17300
|
+
], {
|
|
17301
|
+
env: colgrepEnv(),
|
|
17302
|
+
timeoutMs: 3e4,
|
|
17303
|
+
maxStdoutBytes: MAX_STDOUT_BYTES
|
|
17304
|
+
});
|
|
17305
|
+
} catch (err) {
|
|
17306
|
+
consola.debug("colbert: could not cap colgrep parallelism:", err);
|
|
17307
|
+
}
|
|
17308
|
+
}
|
|
17041
17309
|
async function runInit(workspace) {
|
|
17042
17310
|
const binary = colgrepBinaryPath();
|
|
17043
|
-
if (!existsSync(binary))
|
|
17044
|
-
|
|
17045
|
-
|
|
17046
|
-
}
|
|
17047
|
-
if (!existsSync(colbertOrtDylibPath())) {
|
|
17048
|
-
releaseInit(workspace);
|
|
17049
|
-
return;
|
|
17050
|
-
}
|
|
17311
|
+
if (!existsSync(binary)) throw new Error("colgrep binary is missing");
|
|
17312
|
+
if (!existsSync(colbertOrtDylibPath())) throw new Error("ColBERT ONNX runtime is missing");
|
|
17313
|
+
await applyParallelismCap(binary);
|
|
17051
17314
|
const prior = await readColbertMeta(workspace);
|
|
17052
17315
|
const baseMeta = {
|
|
17053
17316
|
workspace,
|
|
17054
17317
|
model: MODEL_ID,
|
|
17055
17318
|
modelRev: MODEL_REVISION,
|
|
17319
|
+
binarySha: colgrepBinAsset()?.sha256,
|
|
17320
|
+
ortSha: ortLibAsset()?.sha256,
|
|
17056
17321
|
status: "building",
|
|
17057
17322
|
buildPid: void 0,
|
|
17058
17323
|
ownerInstanceId: getColbertInstanceUuid(),
|
|
@@ -17074,7 +17339,7 @@ async function runInit(workspace) {
|
|
|
17074
17339
|
"never",
|
|
17075
17340
|
"--force-cpu",
|
|
17076
17341
|
"--model",
|
|
17077
|
-
|
|
17342
|
+
canonicalColbertModelDir(),
|
|
17078
17343
|
workspace
|
|
17079
17344
|
];
|
|
17080
17345
|
const onInactivityCheck = makeIndexProgressProbe(workspace);
|
|
@@ -17082,7 +17347,7 @@ async function runInit(workspace) {
|
|
|
17082
17347
|
let ok$3 = false;
|
|
17083
17348
|
let failureClass;
|
|
17084
17349
|
try {
|
|
17085
|
-
const res = await
|
|
17350
|
+
const res = await _runManagedExeCapture(binary, args, {
|
|
17086
17351
|
env: colgrepEnv(),
|
|
17087
17352
|
timeoutMs: INIT_TIMEOUT_MS,
|
|
17088
17353
|
inactivityTimeoutMs: INIT_STALL_MS,
|
|
@@ -17097,10 +17362,18 @@ async function runInit(workspace) {
|
|
|
17097
17362
|
}
|
|
17098
17363
|
});
|
|
17099
17364
|
ok$3 = !res.stalled && !res.timedOut && res.code === 0;
|
|
17100
|
-
if (
|
|
17101
|
-
|
|
17365
|
+
if (ok$3) {
|
|
17366
|
+
const projectDir = await colbertProjectDir(workspace);
|
|
17367
|
+
ok$3 = projectDir !== null && validateIndexIntegrity(nodePath.join(projectDir, "index")).verdict === "coherent";
|
|
17368
|
+
if (!ok$3) {
|
|
17369
|
+
failureClass = "corrupt";
|
|
17370
|
+
if (projectDir) await quarantineProjectDir(projectDir);
|
|
17371
|
+
}
|
|
17372
|
+
} else failureClass = res.stalled || res.timedOut ? "stuck" : "error";
|
|
17373
|
+
} catch (err) {
|
|
17102
17374
|
ok$3 = false;
|
|
17103
17375
|
failureClass = "launch";
|
|
17376
|
+
consola.error("colbert: init failed to launch:", err);
|
|
17104
17377
|
} finally {
|
|
17105
17378
|
releaseInit(workspace);
|
|
17106
17379
|
}
|
|
@@ -17203,8 +17476,10 @@ function lexicalSearchCodeMode(mode) {
|
|
|
17203
17476
|
* it both levers: retry `mode:"semantic"` shortly (the index is self-healing
|
|
17204
17477
|
* in the background) OR re-query now with specific symbol/keyword terms.
|
|
17205
17478
|
*/
|
|
17479
|
+
const FALLBACK_GUIDANCE_MARKER = "retry mode:\"semantic\"";
|
|
17480
|
+
const FALLBACK_GUIDANCE = `${FALLBACK_GUIDANCE_MARKER} shortly, or re-query now with specific symbol/keyword terms`;
|
|
17206
17481
|
function fallbackNoticeFor(status) {
|
|
17207
|
-
const tail =
|
|
17482
|
+
const tail = FALLBACK_GUIDANCE;
|
|
17208
17483
|
switch (status) {
|
|
17209
17484
|
case "building": return `semantic index is building; returned lexical keyword matches — ${tail}`;
|
|
17210
17485
|
case "stale": return `semantic index predates the current HEAD/tree (a background re-index was started); returned lexical keyword matches — ${tail}`;
|
|
@@ -17223,6 +17498,38 @@ function joinNotice(primary, secondary) {
|
|
|
17223
17498
|
if (primary && secondary) return `${primary} (${secondary})`;
|
|
17224
17499
|
return primary || secondary || void 0;
|
|
17225
17500
|
}
|
|
17501
|
+
/** Preserve runner-specific context while guaranteeing actionable guidance once. */
|
|
17502
|
+
function semanticFallbackNotice(sem) {
|
|
17503
|
+
if (!sem.notice) return fallbackNoticeFor(sem.status);
|
|
17504
|
+
if (sem.notice.includes(FALLBACK_GUIDANCE_MARKER)) return sem.notice;
|
|
17505
|
+
return `${sem.notice} — ${FALLBACK_GUIDANCE}`;
|
|
17506
|
+
}
|
|
17507
|
+
async function outlinesForSemanticResults(input, results, signal) {
|
|
17508
|
+
if (input.summary === false) return void 0;
|
|
17509
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17510
|
+
const files = [];
|
|
17511
|
+
for (const result of results) {
|
|
17512
|
+
if (seen.has(result.file)) continue;
|
|
17513
|
+
seen.add(result.file);
|
|
17514
|
+
files.push(result.file);
|
|
17515
|
+
if (files.length >= 10) break;
|
|
17516
|
+
}
|
|
17517
|
+
const outlines = [];
|
|
17518
|
+
const deadline = Date.now() + 2e3;
|
|
17519
|
+
const workspace = nodePath.resolve(input.workspace);
|
|
17520
|
+
for (const file of files) {
|
|
17521
|
+
if (signal?.aborted || Date.now() > deadline) break;
|
|
17522
|
+
const abs = nodePath.resolve(workspace, file);
|
|
17523
|
+
const rel = nodePath.relative(workspace, abs);
|
|
17524
|
+
if (!rel || rel.startsWith("..") || nodePath.isAbsolute(rel)) continue;
|
|
17525
|
+
const outlined = await outlineFile(abs, signal);
|
|
17526
|
+
outlines.push({
|
|
17527
|
+
file,
|
|
17528
|
+
outline: outlined.outline
|
|
17529
|
+
});
|
|
17530
|
+
}
|
|
17531
|
+
return outlines;
|
|
17532
|
+
}
|
|
17226
17533
|
async function runLexical(input, mode, source, signal) {
|
|
17227
17534
|
const isAst = mode === "ast";
|
|
17228
17535
|
const resp = await searchCode({
|
|
@@ -17284,22 +17591,26 @@ async function runUnifiedCodeSearch(input, signal) {
|
|
|
17284
17591
|
notice: joinNotice(r$1.notice, "semantic search errored; returned lexical results")
|
|
17285
17592
|
};
|
|
17286
17593
|
}
|
|
17287
|
-
if (sem.status === "ready")
|
|
17288
|
-
|
|
17289
|
-
results: (sem.results ?? []).map((r$1) => ({
|
|
17594
|
+
if (sem.status === "ready") {
|
|
17595
|
+
const results = (sem.results ?? []).map((r$1) => ({
|
|
17290
17596
|
file: r$1.file,
|
|
17291
17597
|
line: r$1.line,
|
|
17292
17598
|
snippet: r$1.snippet,
|
|
17293
17599
|
...r$1.endLine !== void 0 ? { endLine: r$1.endLine } : {},
|
|
17294
17600
|
...r$1.name !== void 0 ? { name: r$1.name } : {},
|
|
17295
17601
|
...r$1.score !== void 0 ? { score: r$1.score } : {}
|
|
17296
|
-
}))
|
|
17297
|
-
|
|
17298
|
-
|
|
17602
|
+
}));
|
|
17603
|
+
return {
|
|
17604
|
+
source: "semantic",
|
|
17605
|
+
results,
|
|
17606
|
+
outlines: await outlinesForSemanticResults(input, results, signal),
|
|
17607
|
+
...sem.notice ? { notice: sem.notice } : {}
|
|
17608
|
+
};
|
|
17609
|
+
}
|
|
17299
17610
|
const r = await runLexical(input, "lexical", "lexical-fallback", signal);
|
|
17300
17611
|
return {
|
|
17301
17612
|
...r,
|
|
17302
|
-
notice: joinNotice(r.notice,
|
|
17613
|
+
notice: joinNotice(r.notice, semanticFallbackNotice(sem))
|
|
17303
17614
|
};
|
|
17304
17615
|
}
|
|
17305
17616
|
|
|
@@ -18495,16 +18806,16 @@ function logAudit$1(record) {
|
|
|
18495
18806
|
if (process.env.GH_ROUTER_LOG_BROWSER_MCP !== "1") return;
|
|
18496
18807
|
(async () => {
|
|
18497
18808
|
try {
|
|
18498
|
-
const fs$
|
|
18809
|
+
const fs$2 = await import("node:fs/promises");
|
|
18499
18810
|
const path$1 = await import("node:path");
|
|
18500
18811
|
const { PATHS: PATHS$1 } = await import("./paths-B5k78n0d.js");
|
|
18501
18812
|
const dir = path$1.join(PATHS$1.APP_DIR, "browser-mcp");
|
|
18502
|
-
await fs$
|
|
18813
|
+
await fs$2.mkdir(dir, { recursive: true });
|
|
18503
18814
|
const line = JSON.stringify({
|
|
18504
18815
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18505
18816
|
...record
|
|
18506
18817
|
}) + "\n";
|
|
18507
|
-
await fs$
|
|
18818
|
+
await fs$2.appendFile(path$1.join(dir, "audit.log"), line, "utf8");
|
|
18508
18819
|
} catch {}
|
|
18509
18820
|
})();
|
|
18510
18821
|
}
|
|
@@ -21340,58 +21651,6 @@ function registerExitHandlers$1() {
|
|
|
21340
21651
|
}
|
|
21341
21652
|
registerExitHandlers$1();
|
|
21342
21653
|
|
|
21343
|
-
//#endregion
|
|
21344
|
-
//#region src/vendor/pi/ai/api-registry.ts
|
|
21345
|
-
const apiProviderRegistry = /* @__PURE__ */ new Map();
|
|
21346
|
-
function getApiProvider(api) {
|
|
21347
|
-
return apiProviderRegistry.get(api)?.provider;
|
|
21348
|
-
}
|
|
21349
|
-
|
|
21350
|
-
//#endregion
|
|
21351
|
-
//#region src/vendor/pi/ai/env-api-keys.ts
|
|
21352
|
-
let _existsSync = null;
|
|
21353
|
-
let _homedir = null;
|
|
21354
|
-
let _join = null;
|
|
21355
|
-
const dynamicImport = (specifier) => import(specifier);
|
|
21356
|
-
const NODE_FS_SPECIFIER = "node:fs";
|
|
21357
|
-
const NODE_OS_SPECIFIER = "node:os";
|
|
21358
|
-
const NODE_PATH_SPECIFIER = "node:path";
|
|
21359
|
-
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
|
|
21360
|
-
dynamicImport(NODE_FS_SPECIFIER).then((m) => {
|
|
21361
|
-
_existsSync = m.existsSync;
|
|
21362
|
-
});
|
|
21363
|
-
dynamicImport(NODE_OS_SPECIFIER).then((m) => {
|
|
21364
|
-
_homedir = m.homedir;
|
|
21365
|
-
});
|
|
21366
|
-
dynamicImport(NODE_PATH_SPECIFIER).then((m) => {
|
|
21367
|
-
_join = m.join;
|
|
21368
|
-
});
|
|
21369
|
-
}
|
|
21370
|
-
|
|
21371
|
-
//#endregion
|
|
21372
|
-
//#region src/vendor/pi/ai/models.generated.ts
|
|
21373
|
-
const MODELS = {};
|
|
21374
|
-
|
|
21375
|
-
//#endregion
|
|
21376
|
-
//#region src/vendor/pi/ai/models.ts
|
|
21377
|
-
const modelRegistry = /* @__PURE__ */ new Map();
|
|
21378
|
-
for (const [provider, models] of Object.entries(MODELS)) {
|
|
21379
|
-
const providerModels = /* @__PURE__ */ new Map();
|
|
21380
|
-
for (const [id, model] of Object.entries(models)) providerModels.set(id, model);
|
|
21381
|
-
modelRegistry.set(provider, providerModels);
|
|
21382
|
-
}
|
|
21383
|
-
|
|
21384
|
-
//#endregion
|
|
21385
|
-
//#region src/vendor/pi/ai/stream.ts
|
|
21386
|
-
function resolveApiProvider(api) {
|
|
21387
|
-
const provider = getApiProvider(api);
|
|
21388
|
-
if (!provider) throw new Error(`No API provider registered for api: ${api}`);
|
|
21389
|
-
return provider;
|
|
21390
|
-
}
|
|
21391
|
-
function streamSimple(model, context, options) {
|
|
21392
|
-
return resolveApiProvider(model.api).streamSimple(model, context, options);
|
|
21393
|
-
}
|
|
21394
|
-
|
|
21395
21654
|
//#endregion
|
|
21396
21655
|
//#region src/vendor/pi/ai/utils/event-stream.ts
|
|
21397
21656
|
var EventStream = class {
|
|
@@ -21453,19 +21712,89 @@ var AssistantMessageEventStream = class extends EventStream {
|
|
|
21453
21712
|
}
|
|
21454
21713
|
};
|
|
21455
21714
|
|
|
21715
|
+
//#endregion
|
|
21716
|
+
//#region src/vendor/pi/ai/env-api-keys.ts
|
|
21717
|
+
let _existsSync = null;
|
|
21718
|
+
let _homedir = null;
|
|
21719
|
+
let _join = null;
|
|
21720
|
+
const dynamicImport = (specifier) => import(specifier);
|
|
21721
|
+
const NODE_FS_SPECIFIER = "node:fs";
|
|
21722
|
+
const NODE_OS_SPECIFIER = "node:os";
|
|
21723
|
+
const NODE_PATH_SPECIFIER = "node:path";
|
|
21724
|
+
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
|
|
21725
|
+
dynamicImport(NODE_FS_SPECIFIER).then((m) => {
|
|
21726
|
+
_existsSync = m.existsSync;
|
|
21727
|
+
});
|
|
21728
|
+
dynamicImport(NODE_OS_SPECIFIER).then((m) => {
|
|
21729
|
+
_homedir = m.homedir;
|
|
21730
|
+
});
|
|
21731
|
+
dynamicImport(NODE_PATH_SPECIFIER).then((m) => {
|
|
21732
|
+
_join = m.join;
|
|
21733
|
+
});
|
|
21734
|
+
}
|
|
21735
|
+
|
|
21736
|
+
//#endregion
|
|
21737
|
+
//#region src/vendor/pi/ai/utils/retry.ts
|
|
21738
|
+
function buildProviderErrorPattern(patterns) {
|
|
21739
|
+
return new RegExp(patterns.join("|"), "i");
|
|
21740
|
+
}
|
|
21741
|
+
const NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN = buildProviderErrorPattern([
|
|
21742
|
+
"GoUsageLimitError",
|
|
21743
|
+
"FreeUsageLimitError",
|
|
21744
|
+
"Monthly usage limit reached",
|
|
21745
|
+
"available balance",
|
|
21746
|
+
"insufficient_quota",
|
|
21747
|
+
"out of budget",
|
|
21748
|
+
"quota exceeded",
|
|
21749
|
+
"billing"
|
|
21750
|
+
]);
|
|
21751
|
+
const RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([
|
|
21752
|
+
"overloaded",
|
|
21753
|
+
"rate.?limit",
|
|
21754
|
+
"too many requests",
|
|
21755
|
+
"429",
|
|
21756
|
+
"500",
|
|
21757
|
+
"502",
|
|
21758
|
+
"503",
|
|
21759
|
+
"504",
|
|
21760
|
+
"524",
|
|
21761
|
+
"service.?unavailable",
|
|
21762
|
+
"server.?error",
|
|
21763
|
+
"internal.?error",
|
|
21764
|
+
"provider.?returned.?error",
|
|
21765
|
+
"network.?error",
|
|
21766
|
+
"connection.?error",
|
|
21767
|
+
"connection.?refused",
|
|
21768
|
+
"connection.?lost",
|
|
21769
|
+
"other side closed",
|
|
21770
|
+
"fetch failed",
|
|
21771
|
+
"getaddrinfo",
|
|
21772
|
+
"ENOTFOUND",
|
|
21773
|
+
"EAI_AGAIN",
|
|
21774
|
+
"upstream.?connect",
|
|
21775
|
+
"reset before headers",
|
|
21776
|
+
"socket hang up",
|
|
21777
|
+
"socket connection was closed",
|
|
21778
|
+
"timed? out",
|
|
21779
|
+
"timeout",
|
|
21780
|
+
"terminated",
|
|
21781
|
+
"websocket.?closed",
|
|
21782
|
+
"websocket.?error",
|
|
21783
|
+
"ended without",
|
|
21784
|
+
"stream ended before message_stop",
|
|
21785
|
+
"stream ended before a terminal response event",
|
|
21786
|
+
"http2 request did not get a response",
|
|
21787
|
+
"retry delay",
|
|
21788
|
+
"you can retry your request",
|
|
21789
|
+
"try your request again",
|
|
21790
|
+
"please retry your request",
|
|
21791
|
+
"ResourceExhausted"
|
|
21792
|
+
]);
|
|
21793
|
+
|
|
21456
21794
|
//#endregion
|
|
21457
21795
|
//#region src/vendor/pi/ai/utils/validation.ts
|
|
21458
21796
|
const validatorCache = /* @__PURE__ */ new WeakMap();
|
|
21459
21797
|
const TYPEBOX_KIND = Symbol.for("TypeBox.Kind");
|
|
21460
|
-
function isRecord(value) {
|
|
21461
|
-
return typeof value === "object" && value !== null;
|
|
21462
|
-
}
|
|
21463
|
-
function isJsonSchemaObject(value) {
|
|
21464
|
-
return isRecord(value);
|
|
21465
|
-
}
|
|
21466
|
-
function hasTypeBoxMetadata(schema) {
|
|
21467
|
-
return isRecord(schema) && Object.getOwnPropertySymbols(schema).includes(TYPEBOX_KIND);
|
|
21468
|
-
}
|
|
21469
21798
|
function getSchemaTypes(schema) {
|
|
21470
21799
|
if (typeof schema.type === "string") return [schema.type];
|
|
21471
21800
|
if (Array.isArray(schema.type)) return schema.type.filter((type) => typeof type === "string");
|
|
@@ -21479,15 +21808,11 @@ function matchesJsonType(value, type) {
|
|
|
21479
21808
|
case "string": return typeof value === "string";
|
|
21480
21809
|
case "null": return value === null;
|
|
21481
21810
|
case "array": return Array.isArray(value);
|
|
21482
|
-
case "object": return
|
|
21811
|
+
case "object": return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21483
21812
|
default: return false;
|
|
21484
21813
|
}
|
|
21485
21814
|
}
|
|
21486
|
-
function isValidatorSchema(value) {
|
|
21487
|
-
return isRecord(value);
|
|
21488
|
-
}
|
|
21489
21815
|
function getSubSchemaValidator(schema) {
|
|
21490
|
-
if (!isValidatorSchema(schema)) return;
|
|
21491
21816
|
try {
|
|
21492
21817
|
return getValidator(schema);
|
|
21493
21818
|
} catch {
|
|
@@ -21540,7 +21865,7 @@ function applySchemaObjectCoercion(value, schema) {
|
|
|
21540
21865
|
if (!(key in value)) continue;
|
|
21541
21866
|
value[key] = coerceWithJsonSchema(value[key], propertySchema);
|
|
21542
21867
|
}
|
|
21543
|
-
if (schema.additionalProperties &&
|
|
21868
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === "object") for (const [key, propertyValue] of Object.entries(value)) {
|
|
21544
21869
|
if (definedKeys.has(key)) continue;
|
|
21545
21870
|
value[key] = coerceWithJsonSchema(propertyValue, schema.additionalProperties);
|
|
21546
21871
|
}
|
|
@@ -21554,7 +21879,7 @@ function applySchemaArrayCoercion(value, schema) {
|
|
|
21554
21879
|
}
|
|
21555
21880
|
return;
|
|
21556
21881
|
}
|
|
21557
|
-
if (
|
|
21882
|
+
if (schema.items && typeof schema.items === "object") for (let index = 0; index < value.length; index++) value[index] = coerceWithJsonSchema(value[index], schema.items);
|
|
21558
21883
|
}
|
|
21559
21884
|
function coerceWithUnionSchema(value, schemas) {
|
|
21560
21885
|
for (const schema of schemas) {
|
|
@@ -21577,7 +21902,7 @@ function coerceWithJsonSchema(value, schema) {
|
|
|
21577
21902
|
break;
|
|
21578
21903
|
}
|
|
21579
21904
|
}
|
|
21580
|
-
if (schemaTypes.includes("object") &&
|
|
21905
|
+
if (schemaTypes.includes("object") && typeof nextValue === "object" && nextValue !== null && !Array.isArray(nextValue)) applySchemaObjectCoercion(nextValue, schema);
|
|
21581
21906
|
if (schemaTypes.includes("array") && Array.isArray(nextValue)) applySchemaArrayCoercion(nextValue, schema);
|
|
21582
21907
|
return nextValue;
|
|
21583
21908
|
}
|
|
@@ -21610,9 +21935,9 @@ function validateToolArguments(tool$1, toolCall) {
|
|
|
21610
21935
|
const args = structuredClone(toolCall.arguments);
|
|
21611
21936
|
Value.Convert(tool$1.parameters, args);
|
|
21612
21937
|
const validator = getValidator(tool$1.parameters);
|
|
21613
|
-
if (!
|
|
21938
|
+
if (!Object.getOwnPropertySymbols(tool$1.parameters).includes(TYPEBOX_KIND)) {
|
|
21614
21939
|
const coerced = coerceWithJsonSchema(args, tool$1.parameters);
|
|
21615
|
-
if (coerced !== args) if (
|
|
21940
|
+
if (coerced !== args) if (typeof args === "object" && args !== null && typeof coerced === "object" && coerced !== null) {
|
|
21616
21941
|
for (const key of Object.keys(args)) delete args[key];
|
|
21617
21942
|
Object.assign(args, coerced);
|
|
21618
21943
|
} else return validator.Check(coerced) ? coerced : args;
|
|
@@ -21623,6 +21948,14 @@ function validateToolArguments(tool$1, toolCall) {
|
|
|
21623
21948
|
throw new Error(errorMessage);
|
|
21624
21949
|
}
|
|
21625
21950
|
|
|
21951
|
+
//#endregion
|
|
21952
|
+
//#region src/vendor/pi/agent/stream-fn.ts
|
|
21953
|
+
let defaultStreamFn;
|
|
21954
|
+
function getDefaultStreamFn() {
|
|
21955
|
+
if (!defaultStreamFn) throw new Error("No default stream function configured. Pass streamFn explicitly or call setDefaultStreamFn().");
|
|
21956
|
+
return defaultStreamFn;
|
|
21957
|
+
}
|
|
21958
|
+
|
|
21626
21959
|
//#endregion
|
|
21627
21960
|
//#region src/vendor/pi/agent/agent-loop.ts
|
|
21628
21961
|
async function runAgentLoop(prompts, context, config, emit, signal, streamFn) {
|
|
@@ -21643,7 +21976,7 @@ async function runAgentLoop(prompts, context, config, emit, signal, streamFn) {
|
|
|
21643
21976
|
message: prompt
|
|
21644
21977
|
});
|
|
21645
21978
|
}
|
|
21646
|
-
await runLoop(currentContext, newMessages, config, signal, emit, streamFn);
|
|
21979
|
+
await runLoop(currentContext, newMessages, config, signal, emit, streamFn ?? getDefaultStreamFn());
|
|
21647
21980
|
return newMessages;
|
|
21648
21981
|
}
|
|
21649
21982
|
async function runAgentLoopContinue(context, config, emit, signal, streamFn) {
|
|
@@ -21653,13 +21986,13 @@ async function runAgentLoopContinue(context, config, emit, signal, streamFn) {
|
|
|
21653
21986
|
const currentContext = { ...context };
|
|
21654
21987
|
await emit({ type: "agent_start" });
|
|
21655
21988
|
await emit({ type: "turn_start" });
|
|
21656
|
-
await runLoop(currentContext, newMessages, config, signal, emit, streamFn);
|
|
21989
|
+
await runLoop(currentContext, newMessages, config, signal, emit, streamFn ?? getDefaultStreamFn());
|
|
21657
21990
|
return newMessages;
|
|
21658
21991
|
}
|
|
21659
21992
|
/**
|
|
21660
21993
|
* Main loop logic shared by agentLoop and agentLoopContinue.
|
|
21661
21994
|
*/
|
|
21662
|
-
async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
21995
|
+
async function runLoop(initialContext, newMessages, initialConfig, signal, emit, streamFunction) {
|
|
21663
21996
|
let currentContext = initialContext;
|
|
21664
21997
|
let config = initialConfig;
|
|
21665
21998
|
let firstTurn = true;
|
|
@@ -21684,7 +22017,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
21684
22017
|
}
|
|
21685
22018
|
pendingMessages = [];
|
|
21686
22019
|
}
|
|
21687
|
-
const message = await streamAssistantResponse(currentContext, config, signal, emit,
|
|
22020
|
+
const message = await streamAssistantResponse(currentContext, config, signal, emit, streamFunction);
|
|
21688
22021
|
newMessages.push(message);
|
|
21689
22022
|
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
21690
22023
|
await emit({
|
|
@@ -21702,7 +22035,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
21702
22035
|
const toolResults = [];
|
|
21703
22036
|
hasMoreToolCalls = false;
|
|
21704
22037
|
if (toolCalls.length > 0) {
|
|
21705
|
-
const executedToolBatch = await executeToolCalls(currentContext, message, config, signal, emit);
|
|
22038
|
+
const executedToolBatch = message.stopReason === "length" ? await failToolCallsFromTruncatedMessage(toolCalls, emit) : await executeToolCalls(currentContext, message, config, signal, emit);
|
|
21706
22039
|
toolResults.push(...executedToolBatch.messages);
|
|
21707
22040
|
hasMoreToolCalls = !executedToolBatch.terminate;
|
|
21708
22041
|
for (const result of toolResults) {
|
|
@@ -21760,7 +22093,7 @@ async function runLoop(initialContext, newMessages, initialConfig, signal, emit,
|
|
|
21760
22093
|
* Stream an assistant response from the LLM.
|
|
21761
22094
|
* This is where AgentMessage[] gets transformed to Message[] for the LLM.
|
|
21762
22095
|
*/
|
|
21763
|
-
async function streamAssistantResponse(context, config, signal, emit,
|
|
22096
|
+
async function streamAssistantResponse(context, config, signal, emit, streamFunction) {
|
|
21764
22097
|
let messages = context.messages;
|
|
21765
22098
|
if (config.transformContext) messages = await config.transformContext(messages, signal);
|
|
21766
22099
|
const llmMessages = await config.convertToLlm(messages);
|
|
@@ -21769,7 +22102,6 @@ async function streamAssistantResponse(context, config, signal, emit, streamFn)
|
|
|
21769
22102
|
messages: llmMessages,
|
|
21770
22103
|
tools: context.tools
|
|
21771
22104
|
};
|
|
21772
|
-
const streamFunction = streamFn || streamSimple;
|
|
21773
22105
|
const resolvedApiKey = (config.getApiKey ? await config.getApiKey(config.model.provider) : void 0) || config.apiKey;
|
|
21774
22106
|
const response = await streamFunction(config.model, llmContext, {
|
|
21775
22107
|
...config,
|
|
@@ -21839,6 +22171,37 @@ async function streamAssistantResponse(context, config, signal, emit, streamFn)
|
|
|
21839
22171
|
return finalMessage;
|
|
21840
22172
|
}
|
|
21841
22173
|
/**
|
|
22174
|
+
* Fail all tool calls from an assistant message that was truncated by the
|
|
22175
|
+
* output token limit. Streamed tool-call arguments are finalized with a
|
|
22176
|
+
* best-effort JSON salvage parser, so a truncated message can yield tool calls
|
|
22177
|
+
* whose arguments parse and validate but are silently incomplete. None of them
|
|
22178
|
+
* are safe to execute; report each as an error so the model can re-issue them.
|
|
22179
|
+
*/
|
|
22180
|
+
async function failToolCallsFromTruncatedMessage(toolCalls, emit) {
|
|
22181
|
+
const messages = [];
|
|
22182
|
+
for (const toolCall of toolCalls) {
|
|
22183
|
+
await emit({
|
|
22184
|
+
type: "tool_execution_start",
|
|
22185
|
+
toolCallId: toolCall.id,
|
|
22186
|
+
toolName: toolCall.name,
|
|
22187
|
+
args: toolCall.arguments
|
|
22188
|
+
});
|
|
22189
|
+
const finalized = {
|
|
22190
|
+
toolCall,
|
|
22191
|
+
result: createErrorToolResult(`Tool call "${toolCall.name}" was not executed: the response hit the output token limit, so its arguments may be truncated. Re-issue the tool call with complete arguments.`),
|
|
22192
|
+
isError: true
|
|
22193
|
+
};
|
|
22194
|
+
await emitToolExecutionEnd(finalized, emit);
|
|
22195
|
+
const toolResultMessage = createToolResultMessage(finalized);
|
|
22196
|
+
await emitToolResultMessage(toolResultMessage, emit);
|
|
22197
|
+
messages.push(toolResultMessage);
|
|
22198
|
+
}
|
|
22199
|
+
return {
|
|
22200
|
+
messages,
|
|
22201
|
+
terminate: false
|
|
22202
|
+
};
|
|
22203
|
+
}
|
|
22204
|
+
/**
|
|
21842
22205
|
* Execute tool calls from an assistant message.
|
|
21843
22206
|
*/
|
|
21844
22207
|
async function executeToolCalls(currentContext, assistantMessage, config, signal, emit) {
|
|
@@ -21977,8 +22340,10 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
|
|
|
21977
22340
|
}
|
|
21978
22341
|
async function executePreparedToolCall(prepared, signal, emit) {
|
|
21979
22342
|
const updateEvents = [];
|
|
22343
|
+
let acceptingUpdates = true;
|
|
21980
22344
|
try {
|
|
21981
22345
|
const result = await prepared.tool.execute(prepared.toolCall.id, prepared.args, signal, (partialResult) => {
|
|
22346
|
+
if (!acceptingUpdates) return;
|
|
21982
22347
|
updateEvents.push(Promise.resolve(emit({
|
|
21983
22348
|
type: "tool_execution_update",
|
|
21984
22349
|
toolCallId: prepared.toolCall.id,
|
|
@@ -21987,17 +22352,21 @@ async function executePreparedToolCall(prepared, signal, emit) {
|
|
|
21987
22352
|
partialResult
|
|
21988
22353
|
})));
|
|
21989
22354
|
});
|
|
22355
|
+
acceptingUpdates = false;
|
|
21990
22356
|
await Promise.all(updateEvents);
|
|
21991
22357
|
return {
|
|
21992
22358
|
result,
|
|
21993
22359
|
isError: false
|
|
21994
22360
|
};
|
|
21995
22361
|
} catch (error) {
|
|
22362
|
+
acceptingUpdates = false;
|
|
21996
22363
|
await Promise.all(updateEvents);
|
|
21997
22364
|
return {
|
|
21998
22365
|
result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
|
|
21999
22366
|
isError: true
|
|
22000
22367
|
};
|
|
22368
|
+
} finally {
|
|
22369
|
+
acceptingUpdates = false;
|
|
22001
22370
|
}
|
|
22002
22371
|
}
|
|
22003
22372
|
async function finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, signal) {
|
|
@@ -22014,8 +22383,10 @@ async function finalizeExecutedToolCall(currentContext, assistantMessage, prepar
|
|
|
22014
22383
|
}, signal);
|
|
22015
22384
|
if (afterResult) {
|
|
22016
22385
|
result = {
|
|
22386
|
+
...result,
|
|
22017
22387
|
content: afterResult.content ?? result.content,
|
|
22018
22388
|
details: afterResult.details ?? result.details,
|
|
22389
|
+
usage: afterResult.usage ?? result.usage,
|
|
22019
22390
|
terminate: afterResult.terminate ?? result.terminate
|
|
22020
22391
|
};
|
|
22021
22392
|
isError = afterResult.isError ?? isError;
|
|
@@ -22053,8 +22424,10 @@ function createToolResultMessage(finalized) {
|
|
|
22053
22424
|
role: "toolResult",
|
|
22054
22425
|
toolCallId: finalized.toolCall.id,
|
|
22055
22426
|
toolName: finalized.toolCall.name,
|
|
22056
|
-
content: finalized.result.content,
|
|
22427
|
+
content: finalized.result.content ?? [],
|
|
22057
22428
|
details: finalized.result.details,
|
|
22429
|
+
usage: finalized.result.usage,
|
|
22430
|
+
...finalized.result.addedToolNames?.length ? { addedToolNames: finalized.result.addedToolNames } : {},
|
|
22058
22431
|
isError: finalized.isError,
|
|
22059
22432
|
timestamp: Date.now()
|
|
22060
22433
|
};
|
|
@@ -22171,13 +22544,15 @@ var Agent$1 = class {
|
|
|
22171
22544
|
followUpQueue;
|
|
22172
22545
|
convertToLlm;
|
|
22173
22546
|
transformContext;
|
|
22174
|
-
|
|
22547
|
+
streamFunction;
|
|
22175
22548
|
getApiKey;
|
|
22176
22549
|
onPayload;
|
|
22177
22550
|
onResponse;
|
|
22178
22551
|
beforeToolCall;
|
|
22179
22552
|
afterToolCall;
|
|
22553
|
+
shouldStopAfterTurn;
|
|
22180
22554
|
prepareNextTurn;
|
|
22555
|
+
prepareNextTurnWithContext;
|
|
22181
22556
|
activeRun;
|
|
22182
22557
|
/** Session identifier forwarded to providers for cache-aware backends. */
|
|
22183
22558
|
sessionId;
|
|
@@ -22189,24 +22564,27 @@ var Agent$1 = class {
|
|
|
22189
22564
|
maxRetryDelayMs;
|
|
22190
22565
|
/** Tool execution strategy for assistant messages that contain multiple tool calls. */
|
|
22191
22566
|
toolExecution;
|
|
22192
|
-
constructor(options
|
|
22193
|
-
|
|
22194
|
-
this.
|
|
22195
|
-
this.
|
|
22196
|
-
this.
|
|
22197
|
-
this.
|
|
22198
|
-
this.
|
|
22199
|
-
this.
|
|
22200
|
-
this.
|
|
22201
|
-
this.
|
|
22202
|
-
this.
|
|
22203
|
-
this.
|
|
22204
|
-
this.
|
|
22205
|
-
this.
|
|
22206
|
-
this.
|
|
22207
|
-
this.
|
|
22208
|
-
this.
|
|
22209
|
-
this.
|
|
22567
|
+
constructor(options) {
|
|
22568
|
+
const runtimeOptions = options ?? {};
|
|
22569
|
+
this._state = createMutableAgentState(runtimeOptions.initialState);
|
|
22570
|
+
this.convertToLlm = runtimeOptions.convertToLlm ?? defaultConvertToLlm;
|
|
22571
|
+
this.transformContext = runtimeOptions.transformContext;
|
|
22572
|
+
this.streamFunction = runtimeOptions.streamFn ?? getDefaultStreamFn();
|
|
22573
|
+
this.getApiKey = runtimeOptions.getApiKey;
|
|
22574
|
+
this.onPayload = runtimeOptions.onPayload;
|
|
22575
|
+
this.onResponse = runtimeOptions.onResponse;
|
|
22576
|
+
this.beforeToolCall = runtimeOptions.beforeToolCall;
|
|
22577
|
+
this.afterToolCall = runtimeOptions.afterToolCall;
|
|
22578
|
+
this.shouldStopAfterTurn = runtimeOptions.shouldStopAfterTurn;
|
|
22579
|
+
this.prepareNextTurn = runtimeOptions.prepareNextTurn;
|
|
22580
|
+
this.prepareNextTurnWithContext = runtimeOptions.prepareNextTurnWithContext;
|
|
22581
|
+
this.steeringQueue = new PendingMessageQueue(runtimeOptions.steeringMode ?? "one-at-a-time");
|
|
22582
|
+
this.followUpQueue = new PendingMessageQueue(runtimeOptions.followUpMode ?? "one-at-a-time");
|
|
22583
|
+
this.sessionId = runtimeOptions.sessionId;
|
|
22584
|
+
this.thinkingBudgets = runtimeOptions.thinkingBudgets;
|
|
22585
|
+
this.transport = runtimeOptions.transport ?? "auto";
|
|
22586
|
+
this.maxRetryDelayMs = runtimeOptions.maxRetryDelayMs;
|
|
22587
|
+
this.toolExecution = runtimeOptions.toolExecution ?? "parallel";
|
|
22210
22588
|
}
|
|
22211
22589
|
/**
|
|
22212
22590
|
* Subscribe to agent lifecycle events.
|
|
@@ -22336,12 +22714,12 @@ var Agent$1 = class {
|
|
|
22336
22714
|
}
|
|
22337
22715
|
async runPromptMessages(messages, options = {}) {
|
|
22338
22716
|
await this.runWithLifecycle(async (signal) => {
|
|
22339
|
-
await runAgentLoop(messages, this.createContextSnapshot(), this.createLoopConfig(options), (event) => this.processEvents(event), signal, this.
|
|
22717
|
+
await runAgentLoop(messages, this.createContextSnapshot(), this.createLoopConfig(options), (event) => this.processEvents(event), signal, this.streamFunction);
|
|
22340
22718
|
});
|
|
22341
22719
|
}
|
|
22342
22720
|
async runContinuation() {
|
|
22343
22721
|
await this.runWithLifecycle(async (signal) => {
|
|
22344
|
-
await runAgentLoopContinue(this.createContextSnapshot(), this.createLoopConfig(), (event) => this.processEvents(event), signal, this.
|
|
22722
|
+
await runAgentLoopContinue(this.createContextSnapshot(), this.createLoopConfig(), (event) => this.processEvents(event), signal, this.streamFunction);
|
|
22345
22723
|
});
|
|
22346
22724
|
}
|
|
22347
22725
|
createContextSnapshot() {
|
|
@@ -22365,7 +22743,11 @@ var Agent$1 = class {
|
|
|
22365
22743
|
toolExecution: this.toolExecution,
|
|
22366
22744
|
beforeToolCall: this.beforeToolCall,
|
|
22367
22745
|
afterToolCall: this.afterToolCall,
|
|
22368
|
-
|
|
22746
|
+
shouldStopAfterTurn: this.shouldStopAfterTurn,
|
|
22747
|
+
prepareNextTurn: this.prepareNextTurnWithContext || this.prepareNextTurn ? async (context) => {
|
|
22748
|
+
if (this.prepareNextTurnWithContext) return await this.prepareNextTurnWithContext(context, this.signal);
|
|
22749
|
+
return await this.prepareNextTurn?.(this.signal);
|
|
22750
|
+
} : void 0,
|
|
22369
22751
|
convertToLlm: this.convertToLlm,
|
|
22370
22752
|
transformContext: this.transformContext,
|
|
22371
22753
|
getApiKey: this.getApiKey,
|
|
@@ -22490,28 +22872,49 @@ var Agent$1 = class {
|
|
|
22490
22872
|
const DEFAULT_MAX_BYTES = 50 * 1024;
|
|
22491
22873
|
const runtimeBuffer = globalThis.Buffer;
|
|
22492
22874
|
|
|
22875
|
+
//#endregion
|
|
22876
|
+
//#region src/vendor/pi/agent/harness/tools/bash.ts
|
|
22877
|
+
const MAX_TIMEOUT_SECONDS = 2147483647 / 1e3;
|
|
22878
|
+
const bashSchema = Type.Object({
|
|
22879
|
+
command: Type.String({ description: "Bash command to execute" }),
|
|
22880
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" }))
|
|
22881
|
+
});
|
|
22882
|
+
|
|
22883
|
+
//#endregion
|
|
22884
|
+
//#region src/vendor/pi/agent/harness/tools/edit.ts
|
|
22885
|
+
const replaceEditSchema = Type.Object({
|
|
22886
|
+
oldText: Type.String({ description: "Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call." }),
|
|
22887
|
+
newText: Type.String({ description: "Replacement text for this targeted edit." })
|
|
22888
|
+
}, {});
|
|
22889
|
+
const editSchema = Type.Object({
|
|
22890
|
+
path: Type.String({ description: "Path to the file to edit (relative or absolute)" }),
|
|
22891
|
+
edits: Type.Array(replaceEditSchema, { description: "One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead." })
|
|
22892
|
+
}, {});
|
|
22893
|
+
|
|
22894
|
+
//#endregion
|
|
22895
|
+
//#region src/vendor/pi/agent/harness/tools/read.ts
|
|
22896
|
+
const readSchema = Type.Object({
|
|
22897
|
+
path: Type.String({ description: "Path to the file to read (relative or absolute)" }),
|
|
22898
|
+
offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
|
|
22899
|
+
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" }))
|
|
22900
|
+
});
|
|
22901
|
+
|
|
22902
|
+
//#endregion
|
|
22903
|
+
//#region src/vendor/pi/agent/harness/tools/write.ts
|
|
22904
|
+
const writeSchema = Type.Object({
|
|
22905
|
+
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
|
22906
|
+
content: Type.String({ description: "Content to write to the file" })
|
|
22907
|
+
});
|
|
22908
|
+
|
|
22493
22909
|
//#endregion
|
|
22494
22910
|
//#region src/lib/worker-agent/budget.ts
|
|
22495
22911
|
const DEFAULT_MAX_TURNS = 500;
|
|
22496
22912
|
const DEFAULT_MAX_WALLCLOCK_MS = 360 * 6e4;
|
|
22913
|
+
const DEFAULT_MODEL_CALL_TIMEOUT_MS = 15 * 6e4;
|
|
22497
22914
|
const DEFAULT_MAX_TOOL_BYTES = 16 * 1024 * 1024;
|
|
22498
22915
|
const DEFAULT_MAX_TOOL_CALLS = 250;
|
|
22499
22916
|
const DEFAULT_MAX_REPEATED_CALLS = 3;
|
|
22500
22917
|
/**
|
|
22501
|
-
* Thrown when the wall-clock budget is exceeded. Engine catches this
|
|
22502
|
-
* around `agent.prompt()` / `agent.continue()` and converts it to a
|
|
22503
|
-
* terse `[halted: wallclock]` reply. Carries no extra metadata — by
|
|
22504
|
-
* design (no advice).
|
|
22505
|
-
*/
|
|
22506
|
-
var WorkerAbort = class extends Error {
|
|
22507
|
-
reason;
|
|
22508
|
-
constructor(reason) {
|
|
22509
|
-
super(`[halted: ${reason}]`);
|
|
22510
|
-
this.reason = reason;
|
|
22511
|
-
this.name = "WorkerAbort";
|
|
22512
|
-
}
|
|
22513
|
-
};
|
|
22514
|
-
/**
|
|
22515
22918
|
* Read a positive-integer env override. Returns `undefined` if the
|
|
22516
22919
|
* env var is unset, empty, or doesn't parse to a positive integer —
|
|
22517
22920
|
* keeping the constructor defaults intact. We don't throw on bad input
|
|
@@ -22540,6 +22943,8 @@ const DEFAULT_MCP_TOOL_TIMEOUT_MS = 225e5;
|
|
|
22540
22943
|
* hard-killed by the harness.
|
|
22541
22944
|
*/
|
|
22542
22945
|
const MCP_TIMEOUT_HEADROOM_MS = 15 * 6e4;
|
|
22946
|
+
/** Smallest useful worker deadline when the MCP timeout is misconfigured below headroom. */
|
|
22947
|
+
const MIN_WORKER_WALLCLOCK_MS = 1e3;
|
|
22543
22948
|
/**
|
|
22544
22949
|
* The MCP per-tool-call timeout the proxy injects into the spawned CLI, in ms.
|
|
22545
22950
|
* Positive-integer override via `GH_ROUTER_MCP_TOOL_TIMEOUT_MS`; falls back to
|
|
@@ -22551,6 +22956,10 @@ const MCP_TIMEOUT_HEADROOM_MS = 15 * 6e4;
|
|
|
22551
22956
|
function resolveMcpToolTimeoutMs() {
|
|
22552
22957
|
return envInt("GH_ROUTER_MCP_TOOL_TIMEOUT_MS") ?? DEFAULT_MCP_TOOL_TIMEOUT_MS;
|
|
22553
22958
|
}
|
|
22959
|
+
/** Whole-call deadline for one worker model turn, including SSE consumption. */
|
|
22960
|
+
function resolveWorkerModelCallTimeoutMs() {
|
|
22961
|
+
return envInt("GH_ROUTER_WORKER_MODEL_CALL_TIMEOUT_MS") ?? DEFAULT_MODEL_CALL_TIMEOUT_MS;
|
|
22962
|
+
}
|
|
22554
22963
|
/**
|
|
22555
22964
|
* The maximum wall-clock a single worker call may be granted: the MCP
|
|
22556
22965
|
* tool-call timeout minus the teardown headroom. A per-call `maxWallClockMs`
|
|
@@ -22560,7 +22969,7 @@ function resolveMcpToolTimeoutMs() {
|
|
|
22560
22969
|
* the default MCP timeout (22_500_000 − 900_000 === 21_600_000).
|
|
22561
22970
|
*/
|
|
22562
22971
|
function workerWallClockCeilingMs() {
|
|
22563
|
-
return resolveMcpToolTimeoutMs() - MCP_TIMEOUT_HEADROOM_MS;
|
|
22972
|
+
return Math.max(MIN_WORKER_WALLCLOCK_MS, resolveMcpToolTimeoutMs() - MCP_TIMEOUT_HEADROOM_MS);
|
|
22564
22973
|
}
|
|
22565
22974
|
/**
|
|
22566
22975
|
* Resolve a `BudgetConfig` from defaults + env overrides + caller-
|
|
@@ -22570,13 +22979,14 @@ function workerWallClockCeilingMs() {
|
|
|
22570
22979
|
* can introspect the merged config without spinning up the `Budget`
|
|
22571
22980
|
* class.
|
|
22572
22981
|
*/
|
|
22573
|
-
function resolveBudgetConfig(overrides) {
|
|
22982
|
+
function resolveBudgetConfig(overrides$1) {
|
|
22983
|
+
const requestedWallClockMs = overrides$1?.maxWallClockMs ?? envInt("GH_ROUTER_WORKER_MAX_WALLCLOCK_MS") ?? DEFAULT_MAX_WALLCLOCK_MS;
|
|
22574
22984
|
return {
|
|
22575
|
-
maxTurns: overrides?.maxTurns ?? envInt("GH_ROUTER_WORKER_MAX_TURNS") ?? DEFAULT_MAX_TURNS,
|
|
22576
|
-
maxWallClockMs:
|
|
22577
|
-
maxToolBytes: overrides?.maxToolBytes ?? envInt("GH_ROUTER_WORKER_MAX_TOOL_BYTES") ?? DEFAULT_MAX_TOOL_BYTES,
|
|
22578
|
-
maxToolCalls: overrides?.maxToolCalls ?? envInt("GH_ROUTER_WORKER_MAX_TOOL_CALLS") ?? DEFAULT_MAX_TOOL_CALLS,
|
|
22579
|
-
maxRepeatedCalls: overrides?.maxRepeatedCalls ?? envInt("GH_ROUTER_WORKER_MAX_REPEATED_CALLS") ?? DEFAULT_MAX_REPEATED_CALLS
|
|
22985
|
+
maxTurns: overrides$1?.maxTurns ?? envInt("GH_ROUTER_WORKER_MAX_TURNS") ?? DEFAULT_MAX_TURNS,
|
|
22986
|
+
maxWallClockMs: Math.min(requestedWallClockMs, workerWallClockCeilingMs()),
|
|
22987
|
+
maxToolBytes: overrides$1?.maxToolBytes ?? envInt("GH_ROUTER_WORKER_MAX_TOOL_BYTES") ?? DEFAULT_MAX_TOOL_BYTES,
|
|
22988
|
+
maxToolCalls: overrides$1?.maxToolCalls ?? envInt("GH_ROUTER_WORKER_MAX_TOOL_CALLS") ?? DEFAULT_MAX_TOOL_CALLS,
|
|
22989
|
+
maxRepeatedCalls: overrides$1?.maxRepeatedCalls ?? envInt("GH_ROUTER_WORKER_MAX_REPEATED_CALLS") ?? DEFAULT_MAX_REPEATED_CALLS
|
|
22580
22990
|
};
|
|
22581
22991
|
}
|
|
22582
22992
|
/**
|
|
@@ -22588,11 +22998,10 @@ function resolveBudgetConfig(overrides) {
|
|
|
22588
22998
|
* - `checkBeforeCall(name, args)` is called from Pi's
|
|
22589
22999
|
* `beforeToolCall` hook. Returns `{block: true, reason: "[halted:
|
|
22590
23000
|
* turns]"}` etc. when a cap fires.
|
|
22591
|
-
* - `recordToolBytes(result)` is called from Pi's `afterToolCall`
|
|
22592
|
-
*
|
|
22593
|
-
*
|
|
22594
|
-
*
|
|
22595
|
-
* `Date.now() - startMs > maxWallClockMs`.
|
|
23001
|
+
* - `recordToolBytes(result)` is called from Pi's `afterToolCall` hook.
|
|
23002
|
+
* - A hard cap latches `hardStopReason`; the engine's
|
|
23003
|
+
* `shouldStopAfterTurn` hook reads it and ends the loop before another
|
|
23004
|
+
* provider request. The repeated-call guard remains a non-terminal block.
|
|
22596
23005
|
*/
|
|
22597
23006
|
var Budget = class {
|
|
22598
23007
|
config;
|
|
@@ -22600,10 +23009,11 @@ var Budget = class {
|
|
|
22600
23009
|
turnCount = 0;
|
|
22601
23010
|
toolBytes = 0;
|
|
22602
23011
|
toolCallCount = 0;
|
|
23012
|
+
hardStopReasonValue = null;
|
|
22603
23013
|
lastCallKey = null;
|
|
22604
23014
|
consecutiveRepeats = 0;
|
|
22605
|
-
constructor(overrides) {
|
|
22606
|
-
this.config = resolveBudgetConfig(overrides);
|
|
23015
|
+
constructor(overrides$1) {
|
|
23016
|
+
this.config = resolveBudgetConfig(overrides$1);
|
|
22607
23017
|
this.startMs = Date.now();
|
|
22608
23018
|
}
|
|
22609
23019
|
/** Record a turn. Does NOT throw — `checkBeforeCall` surfaces the cap. */
|
|
@@ -22622,16 +23032,16 @@ var Budget = class {
|
|
|
22622
23032
|
get elapsedMs() {
|
|
22623
23033
|
return Date.now() - this.startMs;
|
|
22624
23034
|
}
|
|
22625
|
-
/**
|
|
22626
|
-
|
|
22627
|
-
|
|
22628
|
-
|
|
22629
|
-
|
|
22630
|
-
|
|
22631
|
-
|
|
22632
|
-
|
|
22633
|
-
|
|
22634
|
-
|
|
23035
|
+
/** First hard cap reached during this run, or null while work may continue. */
|
|
23036
|
+
get hardStopReason() {
|
|
23037
|
+
return this.hardStopReasonValue;
|
|
23038
|
+
}
|
|
23039
|
+
halt(reason) {
|
|
23040
|
+
this.hardStopReasonValue ??= reason;
|
|
23041
|
+
return {
|
|
23042
|
+
block: true,
|
|
23043
|
+
reason: `[halted: ${reason}]`
|
|
23044
|
+
};
|
|
22635
23045
|
}
|
|
22636
23046
|
/**
|
|
22637
23047
|
* Pi `beforeToolCall` integration. Returns `{block: true, reason}`
|
|
@@ -22650,23 +23060,12 @@ var Budget = class {
|
|
|
22650
23060
|
* signature in Pi without forcing the engine into a wrapper.
|
|
22651
23061
|
*/
|
|
22652
23062
|
checkBeforeCall(toolName, args) {
|
|
22653
|
-
if (this.
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
if (this.elapsedMs > this.config.maxWallClockMs) return {
|
|
22658
|
-
block: true,
|
|
22659
|
-
reason: "[halted: wallclock]"
|
|
22660
|
-
};
|
|
22661
|
-
if (this.toolBytes > this.config.maxToolBytes) return {
|
|
22662
|
-
block: true,
|
|
22663
|
-
reason: "[halted: tool-bytes]"
|
|
22664
|
-
};
|
|
23063
|
+
if (this.hardStopReasonValue) return this.halt(this.hardStopReasonValue);
|
|
23064
|
+
if (this.turnCount > this.config.maxTurns) return this.halt("turns");
|
|
23065
|
+
if (this.elapsedMs > this.config.maxWallClockMs) return this.halt("wallclock");
|
|
23066
|
+
if (this.toolBytes > this.config.maxToolBytes) return this.halt("tool-bytes");
|
|
22665
23067
|
this.toolCallCount += 1;
|
|
22666
|
-
if (this.toolCallCount > this.config.maxToolCalls) return
|
|
22667
|
-
block: true,
|
|
22668
|
-
reason: "[halted: tool-calls]"
|
|
22669
|
-
};
|
|
23068
|
+
if (this.toolCallCount > this.config.maxToolCalls) return this.halt("tool-calls");
|
|
22670
23069
|
const key = `${toolName}:${stableArgs(args)}`;
|
|
22671
23070
|
if (key === this.lastCallKey) this.consecutiveRepeats += 1;
|
|
22672
23071
|
else {
|
|
@@ -22735,14 +23134,15 @@ function extractTextByteLength(result) {
|
|
|
22735
23134
|
* the clamp logic. Lower index = less thinking. `"off"` is below
|
|
22736
23135
|
* everything; `"xhigh"` is the cap.
|
|
22737
23136
|
*/
|
|
22738
|
-
const
|
|
23137
|
+
const WORKER_THINKING_LEVELS = Object.freeze([
|
|
22739
23138
|
"off",
|
|
22740
23139
|
"minimal",
|
|
22741
23140
|
"low",
|
|
22742
23141
|
"medium",
|
|
22743
23142
|
"high",
|
|
22744
23143
|
"xhigh"
|
|
22745
|
-
];
|
|
23144
|
+
]);
|
|
23145
|
+
const THINKING_ORDER = WORKER_THINKING_LEVELS;
|
|
22746
23146
|
function tier(level) {
|
|
22747
23147
|
const i = THINKING_ORDER.indexOf(level);
|
|
22748
23148
|
return i < 0 ? THINKING_ORDER.indexOf("high") : i;
|
|
@@ -22784,13 +23184,7 @@ function resolveModelAndThinking(opts) {
|
|
|
22784
23184
|
});
|
|
22785
23185
|
const allowedRaw = found.capabilities?.supports?.reasoning_effort;
|
|
22786
23186
|
if (!allowedRaw || allowedRaw.length === 0) return mkOk("off");
|
|
22787
|
-
const allowed = allowedRaw.filter((l) =>
|
|
22788
|
-
"minimal",
|
|
22789
|
-
"low",
|
|
22790
|
-
"medium",
|
|
22791
|
-
"high",
|
|
22792
|
-
"xhigh"
|
|
22793
|
-
].includes(l)).sort((a, b) => tier(a) - tier(b));
|
|
23187
|
+
const allowed = allowedRaw.filter((l) => WORKER_THINKING_LEVELS.includes(l) && l !== "off").sort((a, b) => tier(a) - tier(b));
|
|
22794
23188
|
if (allowed.length === 0) return mkOk("off");
|
|
22795
23189
|
if (opts.thinking === "off") return mkOk("off");
|
|
22796
23190
|
if (allowed.includes(opts.thinking)) return mkOk(opts.thinking);
|
|
@@ -22804,6 +23198,40 @@ function resolveModelAndThinking(opts) {
|
|
|
22804
23198
|
return mkOk(clamp$1);
|
|
22805
23199
|
}
|
|
22806
23200
|
|
|
23201
|
+
//#endregion
|
|
23202
|
+
//#region src/lib/worker-agent/session-defaults.ts
|
|
23203
|
+
const MODES = Object.freeze([
|
|
23204
|
+
"explore",
|
|
23205
|
+
"review",
|
|
23206
|
+
"plan",
|
|
23207
|
+
"implement",
|
|
23208
|
+
"test",
|
|
23209
|
+
"browse"
|
|
23210
|
+
]);
|
|
23211
|
+
/**
|
|
23212
|
+
* Process-global, in-memory overrides. They are intentionally not persisted.
|
|
23213
|
+
* In `serve` mode one process may serve multiple client sessions, so these
|
|
23214
|
+
* values are process-wide rather than isolated to an individual client.
|
|
23215
|
+
*/
|
|
23216
|
+
const overrides = {};
|
|
23217
|
+
function getWorkerSessionDefault(mode) {
|
|
23218
|
+
return { ...overrides[mode] };
|
|
23219
|
+
}
|
|
23220
|
+
function setWorkerSessionDefault(mode, value) {
|
|
23221
|
+
const next = { ...overrides[mode] };
|
|
23222
|
+
if (value.model !== void 0) next.model = value.model;
|
|
23223
|
+
if (value.thinking !== void 0) next.thinking = value.thinking;
|
|
23224
|
+
overrides[mode] = next;
|
|
23225
|
+
return { ...next };
|
|
23226
|
+
}
|
|
23227
|
+
function resetWorkerSessionDefault(mode) {
|
|
23228
|
+
delete overrides[mode];
|
|
23229
|
+
}
|
|
23230
|
+
function resetAllWorkerSessionDefaults() {
|
|
23231
|
+
for (const mode of MODES) delete overrides[mode];
|
|
23232
|
+
}
|
|
23233
|
+
const WORKER_MODES = MODES;
|
|
23234
|
+
|
|
22807
23235
|
//#endregion
|
|
22808
23236
|
//#region src/lib/worker-agent/prompts.ts
|
|
22809
23237
|
/**
|
|
@@ -23220,6 +23648,13 @@ function assembleResponsesPayload(opts) {
|
|
|
23220
23648
|
*/
|
|
23221
23649
|
/** Conservative bytes/token for dense DOM-JSON; over-counts tokens by design. */
|
|
23222
23650
|
const BYTES_PER_TOKEN = 3;
|
|
23651
|
+
/**
|
|
23652
|
+
* Conservative context floor when the live catalog omits or reports an invalid window.
|
|
23653
|
+
* This is deliberately not a hardcoded per-model window table: duplicating the
|
|
23654
|
+
* live catalog would go stale on every model launch and violate the rule to
|
|
23655
|
+
* gate on catalog capabilities rather than model slugs.
|
|
23656
|
+
*/
|
|
23657
|
+
const FALLBACK_WINDOW_TOKENS = 128e3;
|
|
23223
23658
|
const OUTPUT_RESERVE_TOKENS = 12e3;
|
|
23224
23659
|
const TOOL_SCHEMA_RESERVE_TOKENS = 6e3;
|
|
23225
23660
|
const SYSTEM_RESERVE_TOKENS = 2e3;
|
|
@@ -23253,17 +23688,19 @@ function tokensFromBytes(bytes) {
|
|
|
23253
23688
|
/**
|
|
23254
23689
|
* Build a per-run budget from the model's catalog context window (tokens).
|
|
23255
23690
|
*
|
|
23256
|
-
*
|
|
23257
|
-
*
|
|
23258
|
-
*
|
|
23259
|
-
*
|
|
23691
|
+
* Unknown, non-finite, and non-positive windows use the conservative fallback
|
|
23692
|
+
* floor so compaction and the dynamic result cap remain engaged. The returned
|
|
23693
|
+
* `windowKnown` flag keeps the request backstop advisory in that case: upstream
|
|
23694
|
+
* remains authoritative when the model's real window is unknown.
|
|
23260
23695
|
*/
|
|
23261
23696
|
function makeContextBudget(windowTokens) {
|
|
23262
|
-
|
|
23263
|
-
const
|
|
23697
|
+
const windowKnown = windowTokens !== void 0 && Number.isFinite(windowTokens) && windowTokens > 0;
|
|
23698
|
+
const effectiveWindowTokens = windowKnown ? windowTokens : FALLBACK_WINDOW_TOKENS;
|
|
23699
|
+
const inputHardLimitTokens = Math.max(0, Math.floor(effectiveWindowTokens * (1 - ASSEMBLY_MARGIN_FRACTION)) - OUTPUT_RESERVE_TOKENS);
|
|
23264
23700
|
const promptBudgetTokens = Math.max(0, inputHardLimitTokens - TOOL_SCHEMA_RESERVE_TOKENS - SYSTEM_RESERVE_TOKENS);
|
|
23265
23701
|
return {
|
|
23266
|
-
|
|
23702
|
+
windowKnown,
|
|
23703
|
+
windowTokens: effectiveWindowTokens,
|
|
23267
23704
|
inputHardLimitTokens,
|
|
23268
23705
|
promptBudgetTokens,
|
|
23269
23706
|
compactTriggerTokens: Math.floor(promptBudgetTokens * COMPACT_TRIGGER_FRACTION),
|
|
@@ -23271,7 +23708,7 @@ function makeContextBudget(windowTokens) {
|
|
|
23271
23708
|
hardLimitTokens: Math.floor(promptBudgetTokens * HARD_LIMIT_FRACTION),
|
|
23272
23709
|
keepRecentTokens: Math.max(KEEP_RECENT_FLOOR_TOKENS, Math.floor(promptBudgetTokens * KEEP_RECENT_FRACTION)),
|
|
23273
23710
|
maxProtectedTokens: Math.max(Math.max(KEEP_RECENT_FLOOR_TOKENS, Math.floor(promptBudgetTokens * KEEP_RECENT_FRACTION)), Math.floor(promptBudgetTokens * MAX_PROTECTED_FRACTION)),
|
|
23274
|
-
perResultCapBytes: clamp(Math.round(
|
|
23711
|
+
perResultCapBytes: clamp(Math.round(effectiveWindowTokens * PER_RESULT_CAP_FRACTION * BYTES_PER_TOKEN), PER_RESULT_CAP_MIN_BYTES, PER_RESULT_CAP_MAX_BYTES)
|
|
23275
23712
|
};
|
|
23276
23713
|
}
|
|
23277
23714
|
|
|
@@ -23294,11 +23731,52 @@ function createCopilotStreamFn(opts) {
|
|
|
23294
23731
|
return stream;
|
|
23295
23732
|
};
|
|
23296
23733
|
}
|
|
23734
|
+
async function runModelCallAttempts(stream, resolved, options, timeoutMs, runAttempt) {
|
|
23735
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
23736
|
+
const deadline = new AbortController();
|
|
23737
|
+
let localDeadlineFired = false;
|
|
23738
|
+
let rejectDeadline = () => {};
|
|
23739
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
23740
|
+
rejectDeadline = reject;
|
|
23741
|
+
});
|
|
23742
|
+
timeoutPromise.catch(() => {});
|
|
23743
|
+
const timer = setTimeout(() => {
|
|
23744
|
+
localDeadlineFired = true;
|
|
23745
|
+
const error = new DOMException("Worker model call timed out", "TimeoutError");
|
|
23746
|
+
deadline.abort(error);
|
|
23747
|
+
rejectDeadline(error);
|
|
23748
|
+
}, timeoutMs);
|
|
23749
|
+
const signal = options?.signal ? AbortSignal.any([options.signal, deadline.signal]) : deadline.signal;
|
|
23750
|
+
const state$1 = {
|
|
23751
|
+
emitted: false,
|
|
23752
|
+
active: true
|
|
23753
|
+
};
|
|
23754
|
+
const attemptPromise = runAttempt(signal, state$1);
|
|
23755
|
+
attemptPromise.catch(() => {});
|
|
23756
|
+
try {
|
|
23757
|
+
await Promise.race([attemptPromise, timeoutPromise]);
|
|
23758
|
+
return;
|
|
23759
|
+
} catch (err) {
|
|
23760
|
+
if (options?.signal?.aborted) {
|
|
23761
|
+
pushTerminalError(stream, resolved, options.signal.reason ?? err);
|
|
23762
|
+
return;
|
|
23763
|
+
}
|
|
23764
|
+
if (!state$1.emitted && attempt === 0) continue;
|
|
23765
|
+
if (localDeadlineFired) pushModelCallTimeoutDiagnostic(stream, resolved, timeoutMs, state$1.emitted);
|
|
23766
|
+
else pushTerminalError(stream, resolved, err);
|
|
23767
|
+
return;
|
|
23768
|
+
} finally {
|
|
23769
|
+
state$1.active = false;
|
|
23770
|
+
clearTimeout(timer);
|
|
23771
|
+
}
|
|
23772
|
+
}
|
|
23773
|
+
}
|
|
23297
23774
|
async function runStreamLoop(stream, context, opts, options) {
|
|
23298
23775
|
const { resolved } = opts;
|
|
23299
23776
|
if (opts.contextBudget) {
|
|
23300
23777
|
const assembledTokens = tokensFromBytes(estimateContextBytes(context));
|
|
23301
|
-
if (assembledTokens > opts.contextBudget.inputHardLimitTokens) {
|
|
23778
|
+
if (assembledTokens > opts.contextBudget.inputHardLimitTokens) if (!opts.contextBudget.windowKnown) consola.warn(`[worker] assembled request estimate ${assembledTokens} tokens exceeds fallback input limit ${opts.contextBudget.inputHardLimitTokens}; proceeding because the catalog window is unknown`);
|
|
23779
|
+
else {
|
|
23302
23780
|
pushBackstopDiagnostic(stream, resolved, assembledTokens, opts.contextBudget.inputHardLimitTokens);
|
|
23303
23781
|
return;
|
|
23304
23782
|
}
|
|
@@ -23314,15 +23792,13 @@ async function runStreamLoop(stream, context, opts, options) {
|
|
|
23314
23792
|
pushTerminalError(stream, resolved, err);
|
|
23315
23793
|
return;
|
|
23316
23794
|
}
|
|
23317
|
-
|
|
23318
|
-
|
|
23319
|
-
|
|
23320
|
-
|
|
23321
|
-
|
|
23322
|
-
|
|
23323
|
-
|
|
23324
|
-
return;
|
|
23325
|
-
}
|
|
23795
|
+
await runModelCallAttempts(stream, resolved, options, opts.modelCallTimeoutMs ?? 15 * 6e4, (signal, state$1) => runChatAttempt(stream, payload, opts, signal, state$1));
|
|
23796
|
+
}
|
|
23797
|
+
async function runChatAttempt(stream, payload, opts, signal, state$1) {
|
|
23798
|
+
const { resolved } = opts;
|
|
23799
|
+
const result = await createChatCompletions(payload, void 0, signal, false);
|
|
23800
|
+
if (result == null || typeof result[Symbol.asyncIterator] !== "function") throw new Error("Upstream did not return an SSE stream (stream: true expected)");
|
|
23801
|
+
const sseStream = result;
|
|
23326
23802
|
const accum = {
|
|
23327
23803
|
blocks: [],
|
|
23328
23804
|
textChunksByIndex: /* @__PURE__ */ new Map(),
|
|
@@ -23331,100 +23807,102 @@ async function runStreamLoop(stream, context, opts, options) {
|
|
|
23331
23807
|
let nextContentIndex = 0;
|
|
23332
23808
|
let activeTextIndex = null;
|
|
23333
23809
|
const toolPiIndexByOAI = /* @__PURE__ */ new Map();
|
|
23334
|
-
|
|
23335
|
-
|
|
23336
|
-
|
|
23337
|
-
|
|
23338
|
-
|
|
23339
|
-
|
|
23340
|
-
|
|
23341
|
-
|
|
23342
|
-
|
|
23343
|
-
|
|
23810
|
+
for await (const evt of sseStream) {
|
|
23811
|
+
const data = evt?.data;
|
|
23812
|
+
if (data == null) continue;
|
|
23813
|
+
if (data === "[DONE]") break;
|
|
23814
|
+
let chunk;
|
|
23815
|
+
try {
|
|
23816
|
+
chunk = JSON.parse(data);
|
|
23817
|
+
} catch {
|
|
23818
|
+
continue;
|
|
23819
|
+
}
|
|
23820
|
+
try {
|
|
23821
|
+
opts.onChunk?.(chunk);
|
|
23822
|
+
} catch {}
|
|
23823
|
+
if (chunk.usage) accum.usage = chunk.usage;
|
|
23824
|
+
const choice = chunk.choices?.[0];
|
|
23825
|
+
if (!choice) continue;
|
|
23826
|
+
const delta = choice.delta ?? {};
|
|
23827
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
23828
|
+
if (activeTextIndex == null) {
|
|
23829
|
+
state$1.emitted = true;
|
|
23830
|
+
activeTextIndex = nextContentIndex++;
|
|
23831
|
+
accum.blocks.push({
|
|
23832
|
+
kind: "text",
|
|
23833
|
+
contentIndex: activeTextIndex
|
|
23834
|
+
});
|
|
23835
|
+
accum.textChunksByIndex.set(activeTextIndex, []);
|
|
23836
|
+
if (!state$1.active) return;
|
|
23837
|
+
stream.push({
|
|
23838
|
+
type: "text_start",
|
|
23839
|
+
contentIndex: activeTextIndex,
|
|
23840
|
+
partial: buildPartial(resolved, accum)
|
|
23841
|
+
});
|
|
23344
23842
|
}
|
|
23345
|
-
|
|
23346
|
-
|
|
23347
|
-
|
|
23348
|
-
|
|
23349
|
-
|
|
23350
|
-
|
|
23351
|
-
|
|
23352
|
-
|
|
23353
|
-
|
|
23354
|
-
|
|
23355
|
-
|
|
23356
|
-
kind: "text",
|
|
23357
|
-
contentIndex: activeTextIndex
|
|
23358
|
-
});
|
|
23359
|
-
accum.textChunksByIndex.set(activeTextIndex, []);
|
|
23360
|
-
stream.push({
|
|
23361
|
-
type: "text_start",
|
|
23362
|
-
contentIndex: activeTextIndex,
|
|
23363
|
-
partial: buildPartial(resolved, accum)
|
|
23364
|
-
});
|
|
23365
|
-
}
|
|
23366
|
-
accum.textChunksByIndex.get(activeTextIndex).push(delta.content);
|
|
23843
|
+
accum.textChunksByIndex.get(activeTextIndex).push(delta.content);
|
|
23844
|
+
stream.push({
|
|
23845
|
+
type: "text_delta",
|
|
23846
|
+
contentIndex: activeTextIndex,
|
|
23847
|
+
delta: delta.content,
|
|
23848
|
+
partial: buildPartial(resolved, accum)
|
|
23849
|
+
});
|
|
23850
|
+
}
|
|
23851
|
+
if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) {
|
|
23852
|
+
if (activeTextIndex != null) {
|
|
23853
|
+
if (!state$1.active) return;
|
|
23367
23854
|
stream.push({
|
|
23368
|
-
type: "
|
|
23855
|
+
type: "text_end",
|
|
23369
23856
|
contentIndex: activeTextIndex,
|
|
23370
|
-
|
|
23857
|
+
content: joinTextChunks(accum, activeTextIndex),
|
|
23371
23858
|
partial: buildPartial(resolved, accum)
|
|
23372
23859
|
});
|
|
23860
|
+
activeTextIndex = null;
|
|
23373
23861
|
}
|
|
23374
|
-
|
|
23375
|
-
if (
|
|
23862
|
+
for (const tcd of delta.tool_calls) {
|
|
23863
|
+
if (tcd == null || tcd.index == null) continue;
|
|
23864
|
+
let piIdx = toolPiIndexByOAI.get(tcd.index);
|
|
23865
|
+
if (piIdx == null) {
|
|
23866
|
+
state$1.emitted = true;
|
|
23867
|
+
piIdx = nextContentIndex++;
|
|
23868
|
+
toolPiIndexByOAI.set(tcd.index, piIdx);
|
|
23869
|
+
accum.blocks.push({
|
|
23870
|
+
kind: "tool",
|
|
23871
|
+
contentIndex: piIdx,
|
|
23872
|
+
openaiIndex: tcd.index
|
|
23873
|
+
});
|
|
23874
|
+
accum.toolByIndex.set(piIdx, {
|
|
23875
|
+
id: "",
|
|
23876
|
+
name: "",
|
|
23877
|
+
argumentChunks: []
|
|
23878
|
+
});
|
|
23879
|
+
if (!state$1.active) return;
|
|
23376
23880
|
stream.push({
|
|
23377
|
-
type: "
|
|
23378
|
-
contentIndex:
|
|
23379
|
-
content: joinTextChunks(accum, activeTextIndex),
|
|
23881
|
+
type: "toolcall_start",
|
|
23882
|
+
contentIndex: piIdx,
|
|
23380
23883
|
partial: buildPartial(resolved, accum)
|
|
23381
23884
|
});
|
|
23382
|
-
activeTextIndex = null;
|
|
23383
23885
|
}
|
|
23384
|
-
|
|
23385
|
-
|
|
23386
|
-
|
|
23387
|
-
|
|
23388
|
-
|
|
23389
|
-
|
|
23390
|
-
|
|
23391
|
-
|
|
23392
|
-
|
|
23393
|
-
|
|
23394
|
-
|
|
23395
|
-
|
|
23396
|
-
|
|
23397
|
-
|
|
23398
|
-
argumentChunks: []
|
|
23399
|
-
});
|
|
23400
|
-
stream.push({
|
|
23401
|
-
type: "toolcall_start",
|
|
23402
|
-
contentIndex: piIdx,
|
|
23403
|
-
partial: buildPartial(resolved, accum)
|
|
23404
|
-
});
|
|
23405
|
-
}
|
|
23406
|
-
const entry = accum.toolByIndex.get(piIdx);
|
|
23407
|
-
if (!entry) continue;
|
|
23408
|
-
if (tcd.id) entry.id = tcd.id;
|
|
23409
|
-
if (tcd.function?.name) entry.name = tcd.function.name;
|
|
23410
|
-
const argDelta = tcd.function?.arguments;
|
|
23411
|
-
if (typeof argDelta === "string" && argDelta.length > 0) {
|
|
23412
|
-
entry.argumentChunks.push(argDelta);
|
|
23413
|
-
stream.push({
|
|
23414
|
-
type: "toolcall_delta",
|
|
23415
|
-
contentIndex: piIdx,
|
|
23416
|
-
delta: argDelta,
|
|
23417
|
-
partial: buildPartial(resolved, accum)
|
|
23418
|
-
});
|
|
23419
|
-
}
|
|
23886
|
+
const entry = accum.toolByIndex.get(piIdx);
|
|
23887
|
+
if (!entry) continue;
|
|
23888
|
+
if (tcd.id) entry.id = tcd.id;
|
|
23889
|
+
if (tcd.function?.name) entry.name = tcd.function.name;
|
|
23890
|
+
const argDelta = tcd.function?.arguments;
|
|
23891
|
+
if (typeof argDelta === "string" && argDelta.length > 0) {
|
|
23892
|
+
entry.argumentChunks.push(argDelta);
|
|
23893
|
+
if (!state$1.active) return;
|
|
23894
|
+
stream.push({
|
|
23895
|
+
type: "toolcall_delta",
|
|
23896
|
+
contentIndex: piIdx,
|
|
23897
|
+
delta: argDelta,
|
|
23898
|
+
partial: buildPartial(resolved, accum)
|
|
23899
|
+
});
|
|
23420
23900
|
}
|
|
23421
23901
|
}
|
|
23422
|
-
if (choice.finish_reason) accum.finishReason = choice.finish_reason;
|
|
23423
23902
|
}
|
|
23424
|
-
|
|
23425
|
-
pushTerminalError(stream, resolved, err);
|
|
23426
|
-
return;
|
|
23903
|
+
if (choice.finish_reason) accum.finishReason = choice.finish_reason;
|
|
23427
23904
|
}
|
|
23905
|
+
if (!state$1.active) return;
|
|
23428
23906
|
if (activeTextIndex != null) {
|
|
23429
23907
|
stream.push({
|
|
23430
23908
|
type: "text_end",
|
|
@@ -23587,15 +24065,13 @@ async function runResponsesStreamLoop(stream, context, opts, options) {
|
|
|
23587
24065
|
pushTerminalError(stream, resolved, err);
|
|
23588
24066
|
return;
|
|
23589
24067
|
}
|
|
23590
|
-
|
|
23591
|
-
|
|
23592
|
-
|
|
23593
|
-
|
|
23594
|
-
|
|
23595
|
-
|
|
23596
|
-
|
|
23597
|
-
return;
|
|
23598
|
-
}
|
|
24068
|
+
await runModelCallAttempts(stream, resolved, options, opts.modelCallTimeoutMs ?? 15 * 6e4, (signal, state$1) => runResponsesAttempt(stream, payload, opts, signal, state$1));
|
|
24069
|
+
}
|
|
24070
|
+
async function runResponsesAttempt(stream, payload, opts, signal, state$1) {
|
|
24071
|
+
const { resolved } = opts;
|
|
24072
|
+
const result = await createResponses(payload, void 0, signal, false);
|
|
24073
|
+
if (result == null || typeof result[Symbol.asyncIterator] !== "function") throw new Error("Upstream did not return an SSE stream (stream: true expected)");
|
|
24074
|
+
const sseStream = result;
|
|
23599
24075
|
const accum = {
|
|
23600
24076
|
blocks: [],
|
|
23601
24077
|
textChunksByIndex: /* @__PURE__ */ new Map(),
|
|
@@ -23615,166 +24091,173 @@ async function runResponsesStreamLoop(stream, context, opts, options) {
|
|
|
23615
24091
|
});
|
|
23616
24092
|
activeTextIndex = null;
|
|
23617
24093
|
};
|
|
23618
|
-
|
|
23619
|
-
|
|
23620
|
-
|
|
23621
|
-
|
|
23622
|
-
|
|
23623
|
-
|
|
23624
|
-
|
|
23625
|
-
|
|
23626
|
-
|
|
23627
|
-
|
|
23628
|
-
|
|
23629
|
-
|
|
23630
|
-
|
|
23631
|
-
|
|
23632
|
-
|
|
23633
|
-
|
|
23634
|
-
|
|
23635
|
-
|
|
23636
|
-
|
|
23637
|
-
|
|
23638
|
-
|
|
23639
|
-
|
|
23640
|
-
|
|
23641
|
-
type: "text_start",
|
|
23642
|
-
contentIndex: activeTextIndex,
|
|
23643
|
-
partial: buildPartial(resolved, accum)
|
|
23644
|
-
});
|
|
23645
|
-
}
|
|
23646
|
-
accum.textChunksByIndex.get(activeTextIndex).push(delta);
|
|
24094
|
+
for await (const evt of sseStream) {
|
|
24095
|
+
const data = evt?.data;
|
|
24096
|
+
if (data == null) continue;
|
|
24097
|
+
if (data === "[DONE]") break;
|
|
24098
|
+
let ev;
|
|
24099
|
+
try {
|
|
24100
|
+
ev = JSON.parse(data);
|
|
24101
|
+
} catch {
|
|
24102
|
+
continue;
|
|
24103
|
+
}
|
|
24104
|
+
switch (ev.type) {
|
|
24105
|
+
case "response.output_text.delta": {
|
|
24106
|
+
const delta = ev.delta;
|
|
24107
|
+
if (typeof delta !== "string" || delta.length === 0) break;
|
|
24108
|
+
if (activeTextIndex == null) {
|
|
24109
|
+
state$1.emitted = true;
|
|
24110
|
+
activeTextIndex = nextContentIndex++;
|
|
24111
|
+
accum.blocks.push({
|
|
24112
|
+
kind: "text",
|
|
24113
|
+
contentIndex: activeTextIndex
|
|
24114
|
+
});
|
|
24115
|
+
accum.textChunksByIndex.set(activeTextIndex, []);
|
|
24116
|
+
if (!state$1.active) return;
|
|
23647
24117
|
stream.push({
|
|
23648
|
-
type: "
|
|
24118
|
+
type: "text_start",
|
|
23649
24119
|
contentIndex: activeTextIndex,
|
|
23650
|
-
delta,
|
|
23651
24120
|
partial: buildPartial(resolved, accum)
|
|
23652
24121
|
});
|
|
23653
|
-
break;
|
|
23654
24122
|
}
|
|
23655
|
-
|
|
23656
|
-
|
|
23657
|
-
|
|
23658
|
-
|
|
23659
|
-
|
|
23660
|
-
|
|
23661
|
-
|
|
23662
|
-
|
|
23663
|
-
|
|
23664
|
-
|
|
23665
|
-
|
|
23666
|
-
|
|
23667
|
-
|
|
23668
|
-
|
|
23669
|
-
stream.push({
|
|
23670
|
-
type: "text_delta",
|
|
23671
|
-
contentIndex: activeTextIndex,
|
|
23672
|
-
delta: ev.text,
|
|
23673
|
-
partial: buildPartial(resolved, accum)
|
|
23674
|
-
});
|
|
23675
|
-
}
|
|
23676
|
-
closeActiveText();
|
|
23677
|
-
break;
|
|
23678
|
-
case "response.output_item.added": {
|
|
23679
|
-
const item = ev.item;
|
|
23680
|
-
if (item?.type !== "function_call") break;
|
|
23681
|
-
const key = responsesToolKey(ev.output_index, item.id);
|
|
23682
|
-
if (key == null) break;
|
|
23683
|
-
if (toolPiIndexByKey.has(key)) break;
|
|
23684
|
-
closeActiveText();
|
|
23685
|
-
const piIdx = nextContentIndex++;
|
|
23686
|
-
toolPiIndexByKey.set(key, piIdx);
|
|
24123
|
+
accum.textChunksByIndex.get(activeTextIndex).push(delta);
|
|
24124
|
+
if (!state$1.active) return;
|
|
24125
|
+
stream.push({
|
|
24126
|
+
type: "text_delta",
|
|
24127
|
+
contentIndex: activeTextIndex,
|
|
24128
|
+
delta,
|
|
24129
|
+
partial: buildPartial(resolved, accum)
|
|
24130
|
+
});
|
|
24131
|
+
break;
|
|
24132
|
+
}
|
|
24133
|
+
case "response.output_text.done":
|
|
24134
|
+
if (activeTextIndex == null && typeof ev.text === "string" && ev.text.length > 0) {
|
|
24135
|
+
state$1.emitted = true;
|
|
24136
|
+
activeTextIndex = nextContentIndex++;
|
|
23687
24137
|
accum.blocks.push({
|
|
23688
|
-
kind: "
|
|
23689
|
-
contentIndex:
|
|
23690
|
-
openaiIndex: piIdx
|
|
23691
|
-
});
|
|
23692
|
-
accum.toolByIndex.set(piIdx, {
|
|
23693
|
-
id: item.call_id ?? item.id ?? key,
|
|
23694
|
-
name: item.name ?? "",
|
|
23695
|
-
argumentChunks: []
|
|
23696
|
-
});
|
|
23697
|
-
stream.push({
|
|
23698
|
-
type: "toolcall_start",
|
|
23699
|
-
contentIndex: piIdx,
|
|
23700
|
-
partial: buildPartial(resolved, accum)
|
|
24138
|
+
kind: "text",
|
|
24139
|
+
contentIndex: activeTextIndex
|
|
23701
24140
|
});
|
|
23702
|
-
|
|
23703
|
-
|
|
23704
|
-
case "response.function_call_arguments.delta": {
|
|
23705
|
-
const key = responsesToolKey(ev.output_index, ev.item_id);
|
|
23706
|
-
if (key == null) break;
|
|
23707
|
-
const piIdx = toolPiIndexByKey.get(key);
|
|
23708
|
-
if (piIdx == null) break;
|
|
23709
|
-
const entry = accum.toolByIndex.get(piIdx);
|
|
23710
|
-
if (!entry) break;
|
|
23711
|
-
const delta = ev.delta;
|
|
23712
|
-
if (typeof delta !== "string" || delta.length === 0) break;
|
|
23713
|
-
entry.argumentChunks.push(delta);
|
|
24141
|
+
accum.textChunksByIndex.set(activeTextIndex, []);
|
|
24142
|
+
if (!state$1.active) return;
|
|
23714
24143
|
stream.push({
|
|
23715
|
-
type: "
|
|
23716
|
-
contentIndex:
|
|
23717
|
-
delta,
|
|
24144
|
+
type: "text_start",
|
|
24145
|
+
contentIndex: activeTextIndex,
|
|
23718
24146
|
partial: buildPartial(resolved, accum)
|
|
23719
24147
|
});
|
|
23720
|
-
|
|
23721
|
-
|
|
23722
|
-
case "response.function_call_arguments.done": {
|
|
23723
|
-
const key = responsesToolKey(ev.output_index, ev.item_id);
|
|
23724
|
-
if (key == null) break;
|
|
23725
|
-
const piIdx = toolPiIndexByKey.get(key);
|
|
23726
|
-
if (piIdx == null) break;
|
|
23727
|
-
const entry = accum.toolByIndex.get(piIdx);
|
|
23728
|
-
if (entry && typeof ev.arguments === "string") entry.argumentChunks = [ev.arguments];
|
|
23729
|
-
break;
|
|
23730
|
-
}
|
|
23731
|
-
case "response.output_item.done": {
|
|
23732
|
-
const item = ev.item;
|
|
23733
|
-
if (item?.type !== "function_call") break;
|
|
23734
|
-
const key = responsesToolKey(ev.output_index, item.id);
|
|
23735
|
-
if (key == null) break;
|
|
23736
|
-
const piIdx = toolPiIndexByKey.get(key);
|
|
23737
|
-
if (piIdx == null) break;
|
|
23738
|
-
const entry = accum.toolByIndex.get(piIdx);
|
|
23739
|
-
if (!entry) break;
|
|
23740
|
-
if (item.call_id) entry.id = item.call_id;
|
|
23741
|
-
if (item.name) entry.name = item.name;
|
|
23742
|
-
if (typeof item.arguments === "string") entry.argumentChunks = [item.arguments];
|
|
24148
|
+
accum.textChunksByIndex.get(activeTextIndex).push(ev.text);
|
|
24149
|
+
if (!state$1.active) return;
|
|
23743
24150
|
stream.push({
|
|
23744
|
-
type: "
|
|
23745
|
-
contentIndex:
|
|
23746
|
-
|
|
24151
|
+
type: "text_delta",
|
|
24152
|
+
contentIndex: activeTextIndex,
|
|
24153
|
+
delta: ev.text,
|
|
23747
24154
|
partial: buildPartial(resolved, accum)
|
|
23748
24155
|
});
|
|
23749
|
-
closedToolItems.add(piIdx);
|
|
23750
|
-
break;
|
|
23751
24156
|
}
|
|
23752
|
-
|
|
23753
|
-
|
|
23754
|
-
|
|
23755
|
-
|
|
23756
|
-
|
|
23757
|
-
|
|
23758
|
-
|
|
23759
|
-
|
|
23760
|
-
|
|
23761
|
-
|
|
23762
|
-
|
|
23763
|
-
|
|
23764
|
-
|
|
23765
|
-
|
|
23766
|
-
|
|
23767
|
-
|
|
23768
|
-
|
|
23769
|
-
|
|
23770
|
-
|
|
23771
|
-
|
|
24157
|
+
closeActiveText();
|
|
24158
|
+
break;
|
|
24159
|
+
case "response.output_item.added": {
|
|
24160
|
+
const item = ev.item;
|
|
24161
|
+
if (item?.type !== "function_call") break;
|
|
24162
|
+
const key = responsesToolKey(ev.output_index, item.id);
|
|
24163
|
+
if (key == null) break;
|
|
24164
|
+
if (toolPiIndexByKey.has(key)) break;
|
|
24165
|
+
closeActiveText();
|
|
24166
|
+
state$1.emitted = true;
|
|
24167
|
+
const piIdx = nextContentIndex++;
|
|
24168
|
+
toolPiIndexByKey.set(key, piIdx);
|
|
24169
|
+
accum.blocks.push({
|
|
24170
|
+
kind: "tool",
|
|
24171
|
+
contentIndex: piIdx,
|
|
24172
|
+
openaiIndex: piIdx
|
|
24173
|
+
});
|
|
24174
|
+
accum.toolByIndex.set(piIdx, {
|
|
24175
|
+
id: item.call_id ?? item.id ?? key,
|
|
24176
|
+
name: item.name ?? "",
|
|
24177
|
+
argumentChunks: []
|
|
24178
|
+
});
|
|
24179
|
+
if (!state$1.active) return;
|
|
24180
|
+
stream.push({
|
|
24181
|
+
type: "toolcall_start",
|
|
24182
|
+
contentIndex: piIdx,
|
|
24183
|
+
partial: buildPartial(resolved, accum)
|
|
24184
|
+
});
|
|
24185
|
+
break;
|
|
24186
|
+
}
|
|
24187
|
+
case "response.function_call_arguments.delta": {
|
|
24188
|
+
const key = responsesToolKey(ev.output_index, ev.item_id);
|
|
24189
|
+
if (key == null) break;
|
|
24190
|
+
const piIdx = toolPiIndexByKey.get(key);
|
|
24191
|
+
if (piIdx == null) break;
|
|
24192
|
+
const entry = accum.toolByIndex.get(piIdx);
|
|
24193
|
+
if (!entry) break;
|
|
24194
|
+
const delta = ev.delta;
|
|
24195
|
+
if (typeof delta !== "string" || delta.length === 0) break;
|
|
24196
|
+
entry.argumentChunks.push(delta);
|
|
24197
|
+
if (!state$1.active) return;
|
|
24198
|
+
stream.push({
|
|
24199
|
+
type: "toolcall_delta",
|
|
24200
|
+
contentIndex: piIdx,
|
|
24201
|
+
delta,
|
|
24202
|
+
partial: buildPartial(resolved, accum)
|
|
24203
|
+
});
|
|
24204
|
+
break;
|
|
24205
|
+
}
|
|
24206
|
+
case "response.function_call_arguments.done": {
|
|
24207
|
+
const key = responsesToolKey(ev.output_index, ev.item_id);
|
|
24208
|
+
if (key == null) break;
|
|
24209
|
+
const piIdx = toolPiIndexByKey.get(key);
|
|
24210
|
+
if (piIdx == null) break;
|
|
24211
|
+
const entry = accum.toolByIndex.get(piIdx);
|
|
24212
|
+
if (entry && typeof ev.arguments === "string") entry.argumentChunks = [ev.arguments];
|
|
24213
|
+
break;
|
|
24214
|
+
}
|
|
24215
|
+
case "response.output_item.done": {
|
|
24216
|
+
const item = ev.item;
|
|
24217
|
+
if (item?.type !== "function_call") break;
|
|
24218
|
+
const key = responsesToolKey(ev.output_index, item.id);
|
|
24219
|
+
if (key == null) break;
|
|
24220
|
+
const piIdx = toolPiIndexByKey.get(key);
|
|
24221
|
+
if (piIdx == null) break;
|
|
24222
|
+
const entry = accum.toolByIndex.get(piIdx);
|
|
24223
|
+
if (!entry) break;
|
|
24224
|
+
if (item.call_id) entry.id = item.call_id;
|
|
24225
|
+
if (item.name) entry.name = item.name;
|
|
24226
|
+
if (typeof item.arguments === "string") entry.argumentChunks = [item.arguments];
|
|
24227
|
+
if (!state$1.active) return;
|
|
24228
|
+
stream.push({
|
|
24229
|
+
type: "toolcall_end",
|
|
24230
|
+
contentIndex: piIdx,
|
|
24231
|
+
toolCall: makePiToolCall(entry),
|
|
24232
|
+
partial: buildPartial(resolved, accum)
|
|
24233
|
+
});
|
|
24234
|
+
closedToolItems.add(piIdx);
|
|
24235
|
+
break;
|
|
23772
24236
|
}
|
|
24237
|
+
case "response.completed":
|
|
24238
|
+
case "response.incomplete":
|
|
24239
|
+
accum.usage = mapResponsesUsage(ev.response?.usage);
|
|
24240
|
+
if (ev.type === "response.incomplete" && ev.response?.incomplete_details?.reason === "max_output_tokens") accum.finishReason = "length";
|
|
24241
|
+
if (opts.onChunk && accum.usage) try {
|
|
24242
|
+
opts.onChunk({
|
|
24243
|
+
id: "",
|
|
24244
|
+
object: "chat.completion.chunk",
|
|
24245
|
+
created: 0,
|
|
24246
|
+
model: resolved.modelId,
|
|
24247
|
+
choices: [],
|
|
24248
|
+
usage: accum.usage
|
|
24249
|
+
});
|
|
24250
|
+
} catch {}
|
|
24251
|
+
break;
|
|
24252
|
+
case "response.failed":
|
|
24253
|
+
if (!state$1.active) return;
|
|
24254
|
+
closeActiveText();
|
|
24255
|
+
pushTerminalError(stream, resolved, new Error(ev.response?.error?.message ?? "response.failed"));
|
|
24256
|
+
return;
|
|
24257
|
+
default: break;
|
|
23773
24258
|
}
|
|
23774
|
-
} catch (err) {
|
|
23775
|
-
pushTerminalError(stream, resolved, err);
|
|
23776
|
-
return;
|
|
23777
24259
|
}
|
|
24260
|
+
if (!state$1.active) return;
|
|
23778
24261
|
closeActiveText();
|
|
23779
24262
|
for (const block of accum.blocks) {
|
|
23780
24263
|
if (block.kind !== "tool") continue;
|
|
@@ -24092,6 +24575,23 @@ function fieldBytes(v) {
|
|
|
24092
24575
|
* the engine marks the result isError). No upstream call is made — this
|
|
24093
24576
|
* replaces an opaque upstream 4xx with an actionable, sanitized message.
|
|
24094
24577
|
*/
|
|
24578
|
+
function pushModelCallTimeoutDiagnostic(stream, resolved, timeoutMs, emitted) {
|
|
24579
|
+
const text = `Worker model call timed out after ${timeoutMs}ms${emitted ? " after emitting partial output" : " after two attempts with no output"}. The upstream stream stopped making progress. Retry the worker call; if it repeats, choose a different model.`;
|
|
24580
|
+
const final = {
|
|
24581
|
+
...makeBaseMessage(resolved),
|
|
24582
|
+
content: [{
|
|
24583
|
+
type: "text",
|
|
24584
|
+
text
|
|
24585
|
+
}],
|
|
24586
|
+
stopReason: "error",
|
|
24587
|
+
errorMessage: "worker model-call deadline exceeded"
|
|
24588
|
+
};
|
|
24589
|
+
stream.push({
|
|
24590
|
+
type: "error",
|
|
24591
|
+
reason: "error",
|
|
24592
|
+
error: final
|
|
24593
|
+
});
|
|
24594
|
+
}
|
|
24095
24595
|
function pushBackstopDiagnostic(stream, resolved, assembledTokens, limitTokens) {
|
|
24096
24596
|
const text = `Request too large: the assembled input is ~${assembledTokens} tokens, over the ~${limitTokens}-token budget for ${resolved.modelId}. The run was stopped before an overflow error. Retry with a narrower task — target a specific section / file / element rather than reading everything at once.`;
|
|
24097
24597
|
const final = {
|
|
@@ -25143,7 +25643,7 @@ function shimDefaultsToXhigh(id) {
|
|
|
25143
25643
|
* ALL THREE peer models the consensus protocol needs:
|
|
25144
25644
|
* - an OpenAI frontier model (`gpt-5.6-sol`, else `gpt-5.5` — see
|
|
25145
25645
|
* `resolveOpenAiFrontier`)
|
|
25146
|
-
* - `claude-opus-
|
|
25646
|
+
* - `claude-opus-5` (stand_in's Anthropic slot)
|
|
25147
25647
|
* - any `gemini-3.X.*pro` (gemini_critic's model family — matches the
|
|
25148
25648
|
* same regex `geminiAvailable()` uses, so the gate stays in sync if
|
|
25149
25649
|
* the GA slug renames `gemini-3.1-pro-preview` → `gemini-3.1-pro`)
|
|
@@ -25152,11 +25652,8 @@ function shimDefaultsToXhigh(id) {
|
|
|
25152
25652
|
* fails `tools/call` with -32601 (mirroring the `worker` capability's
|
|
25153
25653
|
* defense-in-depth pattern — the gated tool is functionally invisible).
|
|
25154
25654
|
*
|
|
25155
|
-
*
|
|
25156
|
-
*
|
|
25157
|
-
* slug). For the catalog probe we use the Anthropic-published dashed
|
|
25158
|
-
* slug too — `state.models?.data` mirrors Copilot's catalog where these
|
|
25159
|
-
* land under the dotted slug, so we match by Copilot's actual id shape.
|
|
25655
|
+
* `claude-opus-5` is a single-segment slug (dotted == dashed), so the
|
|
25656
|
+
* catalog probe matches Copilot's actual id shape directly.
|
|
25160
25657
|
*/
|
|
25161
25658
|
function geminiAvailable(source = state) {
|
|
25162
25659
|
const models = source.models?.data;
|
|
@@ -25183,7 +25680,7 @@ function standInToolEnabled() {
|
|
|
25183
25680
|
const models = state.models?.data;
|
|
25184
25681
|
if (!models) return false;
|
|
25185
25682
|
const hasOpenAi = resolveOpenAiFrontier() != null;
|
|
25186
|
-
const hasOpus = models.some((m) => m.id === "claude-opus-
|
|
25683
|
+
const hasOpus = models.some((m) => m.id === "claude-opus-5");
|
|
25187
25684
|
const hasGeminiPro = geminiAvailable();
|
|
25188
25685
|
return hasOpenAi && hasOpus && hasGeminiPro;
|
|
25189
25686
|
}
|
|
@@ -25461,27 +25958,42 @@ function checkAuth(c) {
|
|
|
25461
25958
|
return { ok: true };
|
|
25462
25959
|
}
|
|
25463
25960
|
/**
|
|
25464
|
-
*
|
|
25465
|
-
*
|
|
25466
|
-
*
|
|
25467
|
-
*
|
|
25468
|
-
*
|
|
25469
|
-
*
|
|
25470
|
-
*
|
|
25471
|
-
*
|
|
25472
|
-
*
|
|
25473
|
-
*
|
|
25961
|
+
* opus_critic's effective model, resolved against the live catalog.
|
|
25962
|
+
*
|
|
25963
|
+
* Prefers `claude-opus-5` — a single-segment slug that is natively 1M
|
|
25964
|
+
* (no `-1m` sibling), so it takes large artifacts in one shot (the whole
|
|
25965
|
+
* point of pairing it with gpt-5.6-sol as the big-window peers). When opus-5
|
|
25966
|
+
* isn't in the catalog (e.g. a lesser tier), falls back to the older
|
|
25967
|
+
* 1M-context Opus 4.6 variant (`claude-opus-4.6-1m`, `max_prompt_tokens`
|
|
25968
|
+
* 936K), then to the 200K `claude-opus-4-6`. The 4.6 regex is
|
|
25969
|
+
* version-anchored AND requires a `-1m` suffix boundary (not a permissive
|
|
25970
|
+
* `.*1m`), so it does NOT false-positive on `claude-opus-4.7-1m-internal`,
|
|
25971
|
+
* `claude-opus-4.6-1max`, or a 1M-without-sibling base slug. Tolerates
|
|
25972
|
+
* dotted (`opus-4.6-1m`) and dashed (`opus-4-6-1m`) catalog separators.
|
|
25474
25973
|
*/
|
|
25475
25974
|
const OPUS_1M_RE = /opus-4[.-]6-1m(?:$|-)/i;
|
|
25476
25975
|
function resolveOpusCriticModel() {
|
|
25477
|
-
const
|
|
25976
|
+
const models = state.models?.data;
|
|
25977
|
+
if (models?.some((m) => m.id === "claude-opus-5")) return "claude-opus-5";
|
|
25978
|
+
const oneM = models?.find((m) => OPUS_1M_RE.test(m.id));
|
|
25478
25979
|
return oneM ? oneM.id : "claude-opus-4-6";
|
|
25479
25980
|
}
|
|
25480
25981
|
function activePersonas() {
|
|
25481
|
-
return PERSONAS_READ.filter((p) => !p.requiresGeminiCatalog || geminiAvailable()).map((p) =>
|
|
25482
|
-
|
|
25483
|
-
model
|
|
25484
|
-
|
|
25982
|
+
return PERSONAS_READ.filter((p) => !p.requiresGeminiCatalog || geminiAvailable()).map((p) => {
|
|
25983
|
+
if (p.toolNameHttp !== "opus_critic") return p;
|
|
25984
|
+
const model = resolveOpusCriticModel();
|
|
25985
|
+
const allowedEfforts = model === "claude-opus-5" ? [
|
|
25986
|
+
"low",
|
|
25987
|
+
"medium",
|
|
25988
|
+
"high",
|
|
25989
|
+
"xhigh"
|
|
25990
|
+
] : p.allowedEfforts;
|
|
25991
|
+
return {
|
|
25992
|
+
...p,
|
|
25993
|
+
model,
|
|
25994
|
+
allowedEfforts
|
|
25995
|
+
};
|
|
25996
|
+
});
|
|
25485
25997
|
}
|
|
25486
25998
|
function toolEntries(scope) {
|
|
25487
25999
|
const personaEntries = scope === "all" || scope === "peers" ? activePersonas().map((p) => ({
|
|
@@ -25672,7 +26184,9 @@ async function predictedWindowOverflow(persona, prompt, context) {
|
|
|
25672
26184
|
return;
|
|
25673
26185
|
}
|
|
25674
26186
|
if (tokens <= budget) return void 0;
|
|
25675
|
-
const
|
|
26187
|
+
const criticModel = resolveOpusCriticModel();
|
|
26188
|
+
const criticIs1M = criticModel === "claude-opus-5" || OPUS_1M_RE.test(criticModel);
|
|
26189
|
+
const opusHint = id === "claude-opus-5" || OPUS_1M_RE.test(id) || !criticIs1M ? "" : " / `opus_critic` (Opus 5, 1M context ≈ 1M tokens)";
|
|
25676
26190
|
return `pre-flight rejected: this ${persona.toolNameHttp} brief is ≈${tokens} tokens, over the ${budget}-token budget for ${persona.model} (its ${maxPromptTokens}-token prompt window minus a ${PEER_PROMPT_TOKEN_RESERVE}-token framing reserve). Do NOT summarize or truncate the artifact to fit. Route the full artifact to a larger-window peer — \`codex_critic\` (gpt-5.6-sol ≈ 1M tokens)${opusHint} — or split it into focused sub-calls BY CONCERN and call them in parallel, then aggregate.`;
|
|
25677
26191
|
}
|
|
25678
26192
|
/**
|
|
@@ -25732,7 +26246,7 @@ function jsonPathPreflightCap(body, scope) {
|
|
|
25732
26246
|
* the `stand_in` orchestrator in `src/lib/stand-in.ts` — can reuse the
|
|
25733
26247
|
* same per-endpoint request shaping without re-implementing it. The
|
|
25734
26248
|
* stand_in tool needs to drive its own per-round system prompts across
|
|
25735
|
-
* three concrete models (gpt-5.6-sol, claude-opus-
|
|
26249
|
+
* three concrete models (gpt-5.6-sol, claude-opus-5, gemini-3.1-pro-preview),
|
|
25736
26250
|
* each on a different endpoint; doing that with a `PersonaSpec` would
|
|
25737
26251
|
* require either inventing throwaway personas per round or duplicating
|
|
25738
26252
|
* the dispatch switch.
|
|
@@ -28033,13 +28547,13 @@ function atomicWriteSync(absPath, contents) {
|
|
|
28033
28547
|
throw err;
|
|
28034
28548
|
}
|
|
28035
28549
|
}
|
|
28036
|
-
const READ_PARAMS = Type.Object({
|
|
28037
|
-
path: Type.String({ description: "Workspace-relative or absolute path." }),
|
|
28038
|
-
offset: Type.Optional(Type.Integer({
|
|
28550
|
+
const READ_PARAMS = Type$1.Object({
|
|
28551
|
+
path: Type$1.String({ description: "Workspace-relative or absolute path." }),
|
|
28552
|
+
offset: Type$1.Optional(Type$1.Integer({
|
|
28039
28553
|
minimum: 0,
|
|
28040
28554
|
description: "Line offset (0-indexed)."
|
|
28041
28555
|
})),
|
|
28042
|
-
limit: Type.Optional(Type.Integer({
|
|
28556
|
+
limit: Type$1.Optional(Type$1.Integer({
|
|
28043
28557
|
minimum: 1,
|
|
28044
28558
|
description: "Max lines to return."
|
|
28045
28559
|
}))
|
|
@@ -28064,9 +28578,9 @@ function readTool(workspace) {
|
|
|
28064
28578
|
}
|
|
28065
28579
|
};
|
|
28066
28580
|
}
|
|
28067
|
-
const GLOB_PARAMS = Type.Object({
|
|
28068
|
-
pattern: Type.String({ description: "ripgrep glob pattern, e.g. `src/**/*.ts`." }),
|
|
28069
|
-
limit: Type.Optional(Type.Integer({
|
|
28581
|
+
const GLOB_PARAMS = Type$1.Object({
|
|
28582
|
+
pattern: Type$1.String({ description: "ripgrep glob pattern, e.g. `src/**/*.ts`." }),
|
|
28583
|
+
limit: Type$1.Optional(Type$1.Integer({
|
|
28070
28584
|
minimum: 1,
|
|
28071
28585
|
maximum: SEARCH_HARD_MAX
|
|
28072
28586
|
}))
|
|
@@ -28093,11 +28607,11 @@ function globTool(workspace) {
|
|
|
28093
28607
|
}
|
|
28094
28608
|
};
|
|
28095
28609
|
}
|
|
28096
|
-
const GREP_PARAMS = Type.Object({
|
|
28097
|
-
query: Type.String({ description: "Pattern to search for." }),
|
|
28098
|
-
mode: Type.Optional(Type.Union([Type.Literal("literal"), Type.Literal("regex")], { description: "`literal` (default) = fixed-string; `regex` = PCRE2." })),
|
|
28099
|
-
file_glob: Type.Optional(Type.String({ description: "ripgrep glob filter, e.g. `*.ts`." })),
|
|
28100
|
-
limit: Type.Optional(Type.Integer({
|
|
28610
|
+
const GREP_PARAMS = Type$1.Object({
|
|
28611
|
+
query: Type$1.String({ description: "Pattern to search for." }),
|
|
28612
|
+
mode: Type$1.Optional(Type$1.Union([Type$1.Literal("literal"), Type$1.Literal("regex")], { description: "`literal` (default) = fixed-string; `regex` = PCRE2." })),
|
|
28613
|
+
file_glob: Type$1.Optional(Type$1.String({ description: "ripgrep glob filter, e.g. `*.ts`." })),
|
|
28614
|
+
limit: Type$1.Optional(Type$1.Integer({
|
|
28101
28615
|
minimum: 1,
|
|
28102
28616
|
maximum: SEARCH_HARD_MAX
|
|
28103
28617
|
}))
|
|
@@ -28133,10 +28647,10 @@ function grepTool(workspace) {
|
|
|
28133
28647
|
}
|
|
28134
28648
|
};
|
|
28135
28649
|
}
|
|
28136
|
-
const EDIT_PARAMS = Type.Object({
|
|
28137
|
-
path: Type.String({ description: "Workspace-relative or absolute path." }),
|
|
28138
|
-
old_string: Type.String({ description: "Exact text to find. Must match exactly once; tool returns `not found` (0 matches) or `matches N times` (>1) without editing." }),
|
|
28139
|
-
new_string: Type.String({ description: "Replacement text. May be empty (deletes old_string)." })
|
|
28650
|
+
const EDIT_PARAMS = Type$1.Object({
|
|
28651
|
+
path: Type$1.String({ description: "Workspace-relative or absolute path." }),
|
|
28652
|
+
old_string: Type$1.String({ description: "Exact text to find. Must match exactly once; tool returns `not found` (0 matches) or `matches N times` (>1) without editing." }),
|
|
28653
|
+
new_string: Type$1.String({ description: "Replacement text. May be empty (deletes old_string)." })
|
|
28140
28654
|
});
|
|
28141
28655
|
function editTool(workspace) {
|
|
28142
28656
|
return {
|
|
@@ -28164,9 +28678,9 @@ function editTool(workspace) {
|
|
|
28164
28678
|
}
|
|
28165
28679
|
};
|
|
28166
28680
|
}
|
|
28167
|
-
const WRITE_PARAMS = Type.Object({
|
|
28168
|
-
path: Type.String({ description: "Workspace-relative or absolute path." }),
|
|
28169
|
-
contents: Type.String({ description: "Full file contents. Refused if >10 MiB." })
|
|
28681
|
+
const WRITE_PARAMS = Type$1.Object({
|
|
28682
|
+
path: Type$1.String({ description: "Workspace-relative or absolute path." }),
|
|
28683
|
+
contents: Type$1.String({ description: "Full file contents. Refused if >10 MiB." })
|
|
28170
28684
|
});
|
|
28171
28685
|
function writeTool(workspace) {
|
|
28172
28686
|
return {
|
|
@@ -28182,9 +28696,9 @@ function writeTool(workspace) {
|
|
|
28182
28696
|
}
|
|
28183
28697
|
};
|
|
28184
28698
|
}
|
|
28185
|
-
const BASH_PARAMS = Type.Object({
|
|
28186
|
-
cmd: Type.String({ description: "Shell command line." }),
|
|
28187
|
-
timeout_ms: Type.Optional(Type.Integer({
|
|
28699
|
+
const BASH_PARAMS = Type$1.Object({
|
|
28700
|
+
cmd: Type$1.String({ description: "Shell command line." }),
|
|
28701
|
+
timeout_ms: Type$1.Optional(Type$1.Integer({
|
|
28188
28702
|
minimum: 100,
|
|
28189
28703
|
maximum: BASH_MAX_TIMEOUT_MS,
|
|
28190
28704
|
description: `Per-call timeout (default ${BASH_DEFAULT_TIMEOUT_MS} ms).`
|
|
@@ -28217,7 +28731,7 @@ function bashTool(workspace) {
|
|
|
28217
28731
|
}
|
|
28218
28732
|
};
|
|
28219
28733
|
}
|
|
28220
|
-
const WEB_SEARCH_PARAMS = Type.Object({ query: Type.String({ description: "Natural-language search query." }) });
|
|
28734
|
+
const WEB_SEARCH_PARAMS = Type$1.Object({ query: Type$1.String({ description: "Natural-language search query." }) });
|
|
28221
28735
|
function webSearchTool() {
|
|
28222
28736
|
return {
|
|
28223
28737
|
name: "web_search",
|
|
@@ -28233,7 +28747,7 @@ function webSearchTool() {
|
|
|
28233
28747
|
}
|
|
28234
28748
|
};
|
|
28235
28749
|
}
|
|
28236
|
-
const FETCH_URL_PARAMS = Type.Object({ url: Type.String({ description: "Absolute URL (http/https only)." }) });
|
|
28750
|
+
const FETCH_URL_PARAMS = Type$1.Object({ url: Type$1.String({ description: "Absolute URL (http/https only)." }) });
|
|
28237
28751
|
function fetchUrlTool() {
|
|
28238
28752
|
return {
|
|
28239
28753
|
name: "fetch_url",
|
|
@@ -28287,26 +28801,26 @@ function fetchUrlTool() {
|
|
|
28287
28801
|
}
|
|
28288
28802
|
};
|
|
28289
28803
|
}
|
|
28290
|
-
const CODE_SEARCH_PARAMS = Type.Object({
|
|
28291
|
-
query: Type.String({ description: "Search text. Natural-language intent in the default `semantic` mode; a literal string in `lexical`/`exact`; a PCRE2 regex in `regex`." }),
|
|
28292
|
-
mode: Type.Optional(Type.Union([
|
|
28293
|
-
Type.Literal("semantic"),
|
|
28294
|
-
Type.Literal("lexical"),
|
|
28295
|
-
Type.Literal("exact"),
|
|
28296
|
-
Type.Literal("regex"),
|
|
28297
|
-
Type.Literal("ast")
|
|
28804
|
+
const CODE_SEARCH_PARAMS = Type$1.Object({
|
|
28805
|
+
query: Type$1.String({ description: "Search text. Natural-language intent in the default `semantic` mode; a literal string in `lexical`/`exact`; a PCRE2 regex in `regex`." }),
|
|
28806
|
+
mode: Type$1.Optional(Type$1.Union([
|
|
28807
|
+
Type$1.Literal("semantic"),
|
|
28808
|
+
Type$1.Literal("lexical"),
|
|
28809
|
+
Type$1.Literal("exact"),
|
|
28810
|
+
Type$1.Literal("regex"),
|
|
28811
|
+
Type$1.Literal("ast")
|
|
28298
28812
|
], { description: "Search mode. `semantic` (DEFAULT): ColBERT meaning-based ranking, falls back to lexical when the index isn't ready (response `source` says which engine ran). `lexical`: BM25F + tree-sitter (best for exact symbols). `exact`: fixed-string. `regex`: PCRE2. `ast`: ast-grep structural (needs `ast_pattern` + `ast_lang`)." })),
|
|
28299
|
-
pattern: Type.Optional(Type.String({ description: "Semantic mode only: regex pre-filter (colgrep -e) — grep first, then rank semantically. Ignored in lexical modes." })),
|
|
28300
|
-
file_glob: Type.Optional(Type.String({ description: "ripgrep glob filter." })),
|
|
28301
|
-
limit: Type.Optional(Type.Integer({
|
|
28813
|
+
pattern: Type$1.Optional(Type$1.String({ description: "Semantic mode only: regex pre-filter (colgrep -e) — grep first, then rank semantically. Ignored in lexical modes." })),
|
|
28814
|
+
file_glob: Type$1.Optional(Type$1.String({ description: "ripgrep glob filter." })),
|
|
28815
|
+
limit: Type$1.Optional(Type$1.Integer({
|
|
28302
28816
|
minimum: 1,
|
|
28303
28817
|
description: "Max hits to return."
|
|
28304
28818
|
})),
|
|
28305
|
-
structural: Type.Optional(Type.Union([Type.Literal("full"), Type.Literal("topN")], { description: "Structural-ranking depth (lexical mode only)." })),
|
|
28306
|
-
complete: Type.Optional(Type.Boolean({ description: "Lexical mode: when true, return the COMPLETE match set (every line ripgrep would find, capped only by `limit`) — disables the default precision shoulder cut + per-file cap. Use it when you must not miss any occurrence (every caller of X, a rename, an audit). The default response `notice` says when matches were hidden." })),
|
|
28307
|
-
multiline: Type.Optional(Type.Boolean({ description: "Set true with mode:'regex' to let a pattern span newlines (ripgrep -U), e.g. 'foo[\\s\\S]*?bar' across lines. (literal/lexical queries can't contain a newline.)" })),
|
|
28308
|
-
ast_pattern: Type.Optional(Type.String({ description: "mode:'ast' structural pattern (e.g. 'function $F($$$) { $$$ }'). Matches come from ast-grep instead of ripgrep — for multi-line AST shapes the regex modes can't express. Takes precedence over `query`. REQUIRES `ast_lang`. If ast-grep isn't installed you get a `notice`; it never falls back to regex." })),
|
|
28309
|
-
ast_lang: Type.Optional(Type.String({ description: "Language grammar for `ast_pattern` (REQUIRED with it): 'ts' | 'tsx' | 'js' | 'py' | 'rust' | 'go' | … Without it ast-grep cross-matches every language and returns garbage." }))
|
|
28819
|
+
structural: Type$1.Optional(Type$1.Union([Type$1.Literal("full"), Type$1.Literal("topN")], { description: "Structural-ranking depth (lexical mode only)." })),
|
|
28820
|
+
complete: Type$1.Optional(Type$1.Boolean({ description: "Lexical mode: when true, return the COMPLETE match set (every line ripgrep would find, capped only by `limit`) — disables the default precision shoulder cut + per-file cap. Use it when you must not miss any occurrence (every caller of X, a rename, an audit). The default response `notice` says when matches were hidden." })),
|
|
28821
|
+
multiline: Type$1.Optional(Type$1.Boolean({ description: "Set true with mode:'regex' to let a pattern span newlines (ripgrep -U), e.g. 'foo[\\s\\S]*?bar' across lines. (literal/lexical queries can't contain a newline.)" })),
|
|
28822
|
+
ast_pattern: Type$1.Optional(Type$1.String({ description: "mode:'ast' structural pattern (e.g. 'function $F($$$) { $$$ }'). Matches come from ast-grep instead of ripgrep — for multi-line AST shapes the regex modes can't express. Takes precedence over `query`. REQUIRES `ast_lang`. If ast-grep isn't installed you get a `notice`; it never falls back to regex." })),
|
|
28823
|
+
ast_lang: Type$1.Optional(Type$1.String({ description: "Language grammar for `ast_pattern` (REQUIRED with it): 'ts' | 'tsx' | 'js' | 'py' | 'rust' | 'go' | … Without it ast-grep cross-matches every language and returns garbage." }))
|
|
28310
28824
|
});
|
|
28311
28825
|
function codeSearchTool(workspace) {
|
|
28312
28826
|
return {
|
|
@@ -28465,9 +28979,9 @@ const GIT_DIFF_PRODUCING = new Set([
|
|
|
28465
28979
|
"show",
|
|
28466
28980
|
"diff"
|
|
28467
28981
|
]);
|
|
28468
|
-
const TOOLBELT_PARAMS = Type.Object({
|
|
28469
|
-
tool: Type.Union(TOOLBELT_TOOLS.map((t) => Type.Literal(t)), { description: "Which read-only analysis CLI to run: rg (ripgrep search), fd (file find), sg (ast-grep structural search), jq (JSON), yq (YAML/TOML/XML), gron (flatten JSON to greppable lines), scc (code stats: LOC + complexity), tokei (code stats), difft (difftastic structural diff), git (read-only subcommands only)." }),
|
|
28470
|
-
args: Type.Optional(Type.Array(Type.String(), { description: "Arguments passed LITERALLY to the tool (no shell: no pipes, redirects, chaining, or glob expansion). For git, args[0] must be a read-only subcommand (log/show/diff/blame/ls-files/…)." }))
|
|
28982
|
+
const TOOLBELT_PARAMS = Type$1.Object({
|
|
28983
|
+
tool: Type$1.Union(TOOLBELT_TOOLS.map((t) => Type$1.Literal(t)), { description: "Which read-only analysis CLI to run: rg (ripgrep search), fd (file find), sg (ast-grep structural search), jq (JSON), yq (YAML/TOML/XML), gron (flatten JSON to greppable lines), scc (code stats: LOC + complexity), tokei (code stats), difft (difftastic structural diff), git (read-only subcommands only)." }),
|
|
28984
|
+
args: Type$1.Optional(Type$1.Array(Type$1.String(), { description: "Arguments passed LITERALLY to the tool (no shell: no pipes, redirects, chaining, or glob expansion). For git, args[0] must be a read-only subcommand (log/show/diff/blame/ls-files/…)." }))
|
|
28471
28985
|
});
|
|
28472
28986
|
/**
|
|
28473
28987
|
* True iff `arg` triggers a denied flag. Long flags (`--foo`) match on the
|
|
@@ -28576,10 +29090,10 @@ function toolbeltTool(workspace) {
|
|
|
28576
29090
|
};
|
|
28577
29091
|
}
|
|
28578
29092
|
const PEER_CRITIC_TUPLE = [
|
|
28579
|
-
Type.Literal("codex_critic"),
|
|
28580
|
-
Type.Literal("gemini_critic"),
|
|
28581
|
-
Type.Literal("codex_reviewer"),
|
|
28582
|
-
Type.Literal("opus_critic")
|
|
29093
|
+
Type$1.Literal("codex_critic"),
|
|
29094
|
+
Type$1.Literal("gemini_critic"),
|
|
29095
|
+
Type$1.Literal("codex_reviewer"),
|
|
29096
|
+
Type$1.Literal("opus_critic")
|
|
28583
29097
|
];
|
|
28584
29098
|
/**
|
|
28585
29099
|
* Critic names accepted by `peer_review.critic`. Exported for the
|
|
@@ -28587,17 +29101,17 @@ const PEER_CRITIC_TUPLE = [
|
|
|
28587
29101
|
* against `PERSONAS_READ` from `~/lib/peer-mcp-personas`.
|
|
28588
29102
|
*/
|
|
28589
29103
|
const PEER_CRITIC_NAMES = PEER_CRITIC_TUPLE.map((l) => l.const);
|
|
28590
|
-
const PEER_EFFORT_UNION = Type.Union([
|
|
28591
|
-
Type.Literal("low"),
|
|
28592
|
-
Type.Literal("medium"),
|
|
28593
|
-
Type.Literal("high"),
|
|
28594
|
-
Type.Literal("xhigh")
|
|
29104
|
+
const PEER_EFFORT_UNION = Type$1.Union([
|
|
29105
|
+
Type$1.Literal("low"),
|
|
29106
|
+
Type$1.Literal("medium"),
|
|
29107
|
+
Type$1.Literal("high"),
|
|
29108
|
+
Type$1.Literal("xhigh")
|
|
28595
29109
|
], { description: "Reasoning depth. Per-critic allowedEfforts gate; out-of-band values are clamped to the critic's default." });
|
|
28596
|
-
const PEER_REVIEW_PARAMS = Type.Object({
|
|
28597
|
-
critic: Type.Union([...PEER_CRITIC_TUPLE], { description: "Critic tool name. One of " + PEER_CRITIC_NAMES.map((n) => `\`${n}\``).join(", ") + ". `gemini_critic` is only valid when gemini-3.x is in the Copilot catalog; otherwise the call is refused." }),
|
|
28598
|
-
prompt: Type.String({ description: "The brief — artifact under review plus constraints. Pasted verbatim into the critic's user message." }),
|
|
28599
|
-
context: Type.Optional(Type.String({ description: "Optional extra context concatenated to the brief." })),
|
|
28600
|
-
effort: Type.Optional(PEER_EFFORT_UNION)
|
|
29110
|
+
const PEER_REVIEW_PARAMS = Type$1.Object({
|
|
29111
|
+
critic: Type$1.Union([...PEER_CRITIC_TUPLE], { description: "Critic tool name. One of " + PEER_CRITIC_NAMES.map((n) => `\`${n}\``).join(", ") + ". `gemini_critic` is only valid when gemini-3.x is in the Copilot catalog; otherwise the call is refused." }),
|
|
29112
|
+
prompt: Type$1.String({ description: "The brief — artifact under review plus constraints. Pasted verbatim into the critic's user message." }),
|
|
29113
|
+
context: Type$1.Optional(Type$1.String({ description: "Optional extra context concatenated to the brief." })),
|
|
29114
|
+
effort: Type$1.Optional(PEER_EFFORT_UNION)
|
|
28601
29115
|
});
|
|
28602
29116
|
function lookupPersona(critic) {
|
|
28603
29117
|
const persona = PERSONAS_READ.find((p) => p.toolNameHttp === critic);
|
|
@@ -28618,10 +29132,10 @@ function lookupPersona(critic) {
|
|
|
28618
29132
|
* `acquireInFlightSlot`, and `callPersona` keeps the slot accounting,
|
|
28619
29133
|
* effort clamping, and isError-promotion semantics identical.
|
|
28620
29134
|
*/
|
|
28621
|
-
const CODEX_REVIEW_PARAMS = Type.Object({
|
|
28622
|
-
prompt: Type.String({ description: "The code-review brief — diff or single file under review plus constraints. Pasted verbatim into codex-reviewer's user message." }),
|
|
28623
|
-
context: Type.Optional(Type.String({ description: "Optional extra context concatenated to the brief." })),
|
|
28624
|
-
effort: Type.Optional(PEER_EFFORT_UNION)
|
|
29135
|
+
const CODEX_REVIEW_PARAMS = Type$1.Object({
|
|
29136
|
+
prompt: Type$1.String({ description: "The code-review brief — diff or single file under review plus constraints. Pasted verbatim into codex-reviewer's user message." }),
|
|
29137
|
+
context: Type$1.Optional(Type$1.String({ description: "Optional extra context concatenated to the brief." })),
|
|
29138
|
+
effort: Type$1.Optional(PEER_EFFORT_UNION)
|
|
28625
29139
|
});
|
|
28626
29140
|
function codexReviewTool() {
|
|
28627
29141
|
return {
|
|
@@ -28632,7 +29146,7 @@ function codexReviewTool() {
|
|
|
28632
29146
|
executionMode: "sequential",
|
|
28633
29147
|
async execute(_toolCallId, params, signal) {
|
|
28634
29148
|
if (networkDisabled()) throw new Error("rejected: network disabled");
|
|
28635
|
-
const persona = lookupPersona("
|
|
29149
|
+
const persona = lookupPersona("codex_reviewer");
|
|
28636
29150
|
const requested = params.effort;
|
|
28637
29151
|
const effort = requested && persona.allowedEfforts.includes(requested) ? requested : persona.defaultEffort;
|
|
28638
29152
|
const release = acquireInFlightSlot();
|
|
@@ -28655,7 +29169,7 @@ function geminiInCatalog() {
|
|
|
28655
29169
|
if (!models) return false;
|
|
28656
29170
|
return models.some((m) => /^gemini-3\..*pro/i.test(m.id));
|
|
28657
29171
|
}
|
|
28658
|
-
const ADVISOR_PARAMS = Type.Object({ concern: Type.String({
|
|
29172
|
+
const ADVISOR_PARAMS = Type$1.Object({ concern: Type$1.String({
|
|
28659
29173
|
description: "What you want a second pair of eyes on — your current approach, the blocker you're stuck on, or the decision you're about to commit. Required: the advisor needs a focal point.",
|
|
28660
29174
|
minLength: 1
|
|
28661
29175
|
}) });
|
|
@@ -28770,22 +29284,22 @@ function advisorTool(getMessages) {
|
|
|
28770
29284
|
}
|
|
28771
29285
|
};
|
|
28772
29286
|
}
|
|
28773
|
-
const UPDATE_PLAN_PARAMS = Type.Object({
|
|
28774
|
-
steps: Type.Array(Type.Object({
|
|
28775
|
-
title: Type.String({
|
|
29287
|
+
const UPDATE_PLAN_PARAMS = Type$1.Object({
|
|
29288
|
+
steps: Type$1.Array(Type$1.Object({
|
|
29289
|
+
title: Type$1.String({
|
|
28776
29290
|
minLength: 1,
|
|
28777
29291
|
description: "Short imperative description of the step."
|
|
28778
29292
|
}),
|
|
28779
|
-
status: Type.Union([
|
|
28780
|
-
Type.Literal("pending"),
|
|
28781
|
-
Type.Literal("in_progress"),
|
|
28782
|
-
Type.Literal("completed")
|
|
29293
|
+
status: Type$1.Union([
|
|
29294
|
+
Type$1.Literal("pending"),
|
|
29295
|
+
Type$1.Literal("in_progress"),
|
|
29296
|
+
Type$1.Literal("completed")
|
|
28783
29297
|
], { description: "Current status of this step." })
|
|
28784
29298
|
}), {
|
|
28785
29299
|
minItems: 1,
|
|
28786
29300
|
description: "The FULL ordered plan. Each call replaces the previous plan, so always send every step (not just the changed one)."
|
|
28787
29301
|
}),
|
|
28788
|
-
explanation: Type.Optional(Type.String({ description: "Optional one-line note on what changed this update." }))
|
|
29302
|
+
explanation: Type$1.Optional(Type$1.String({ description: "Optional one-line note on what changed this update." }))
|
|
28789
29303
|
});
|
|
28790
29304
|
function createPlanState() {
|
|
28791
29305
|
return { current: [] };
|
|
@@ -29380,15 +29894,21 @@ registerExitHandlers(WORKTREE_REGISTRY);
|
|
|
29380
29894
|
* mode. */
|
|
29381
29895
|
const DEFAULT_MODEL = "gpt-5.4-mini";
|
|
29382
29896
|
const DEFAULT_THINKING = "xhigh";
|
|
29383
|
-
/** Default model for the READ-ONLY `explore` mode. `
|
|
29384
|
-
* (via `
|
|
29385
|
-
*
|
|
29386
|
-
*
|
|
29387
|
-
* gpt-5.6-sol this is NOT a
|
|
29388
|
-
*
|
|
29389
|
-
*
|
|
29390
|
-
*
|
|
29391
|
-
|
|
29897
|
+
/** Default model for the READ-ONLY `explore` mode. `gemini-3.6-flash` at `high`
|
|
29898
|
+
* (via `EXPLORE_DEFAULT_THINKING`; flash advertises no xhigh) — a fast, cheap,
|
|
29899
|
+
* 1M-context tool-caller for read-only repo research. Routes over
|
|
29900
|
+
* `/chat/completions` via the translation shim (the same proven path the
|
|
29901
|
+
* `review` worker uses for gemini). Like `implement`'s gpt-5.6-sol this is NOT a
|
|
29902
|
+
* `workerToolsEnabled` gate input — if absent (e.g. a non-enterprise tier)
|
|
29903
|
+
* `explore` errors helpfully at call time rather than vanishing the whole worker
|
|
29904
|
+
* surface. The caller (the main model) overrides BOTH the model and the reasoning
|
|
29905
|
+
* per call via the `model` / `thinking` args — see the tier ladder (gpt-5.6-sol
|
|
29906
|
+
* heavy / gpt-5.6-terra moderate / gemini-3.6-flash light) in the MCP tool desc. */
|
|
29907
|
+
const EXPLORE_DEFAULT_MODEL = "gemini-3.6-flash";
|
|
29908
|
+
/** Default thinking for `explore`. `high` (flash has no xhigh); explicit rather
|
|
29909
|
+
* than inherited from `DEFAULT_THINKING` so the explore effort can't drift if the
|
|
29910
|
+
* shared fallback changes. */
|
|
29911
|
+
const EXPLORE_DEFAULT_THINKING = "high";
|
|
29392
29912
|
/** Default model + thinking for the READ-ONLY `review` mode.
|
|
29393
29913
|
* `gemini-3.1-pro-preview` at `xhigh` (clamped to `high` at call time — gemini
|
|
29394
29914
|
* advertises no xhigh). DELIBERATELY DECORRELATED FROM THE IMPLEMENTER: bounded
|
|
@@ -29410,6 +29930,10 @@ const REVIEW_DEFAULT_THINKING = "xhigh";
|
|
|
29410
29930
|
* autonomous implementation. An explicit `opts.model` still wins. */
|
|
29411
29931
|
const IMPLEMENT_DEFAULT_MODEL = "gpt-5.6-sol";
|
|
29412
29932
|
const IMPLEMENT_DEFAULT_THINKING = "xhigh";
|
|
29933
|
+
/** `test` starts with the same built-in pair as `implement`, but remains an
|
|
29934
|
+
* independent mode so either can be overridden without affecting the other. */
|
|
29935
|
+
const TEST_DEFAULT_MODEL = "gpt-5.6-sol";
|
|
29936
|
+
const TEST_DEFAULT_THINKING = "xhigh";
|
|
29413
29937
|
/** Default model for `browse` mode. `gpt-5.4-mini` — the Gate-B-winning
|
|
29414
29938
|
* browse model (small + fast enough to drive a tab at human pace, with
|
|
29415
29939
|
* enough tool-calling discipline to terminate). This is DISTINCT from the
|
|
@@ -29429,14 +29953,53 @@ const BROWSE_DEFAULT_THINKING = "high";
|
|
|
29429
29953
|
/** Default model + thinking for the read-only `plan` mode. `claude-opus-4.8`
|
|
29430
29954
|
* at `xhigh` — planning is the highest-leverage read-only step (the plan
|
|
29431
29955
|
* shapes everything downstream), so it gets the strongest reasoning model
|
|
29432
|
-
* rather than the
|
|
29956
|
+
* rather than the lightweight `gemini-3.6-flash` explore default. Uses the DOTTED
|
|
29433
29957
|
* Copilot catalog id (the worker resolver exact-matches `catalog.id`, it does
|
|
29434
|
-
* NOT translate the Anthropic dashed slug
|
|
29435
|
-
*
|
|
29436
|
-
* non-enterprise tier), exactly like
|
|
29437
|
-
* arg still wins. */
|
|
29438
|
-
const PLAN_DEFAULT_MODEL = "claude-opus-
|
|
29439
|
-
const
|
|
29958
|
+
* NOT translate the Anthropic dashed slug; `claude-opus-5` is a single-segment
|
|
29959
|
+
* slug so dotted == dashed). Falls back to a helpful unknown-model error at call
|
|
29960
|
+
* time if opus-5 isn't in the catalog (e.g. a non-enterprise tier), exactly like
|
|
29961
|
+
* `implement`'s `gpt-5.6-sol`. Caller's `model` arg still wins. */
|
|
29962
|
+
const PLAN_DEFAULT_MODEL = "claude-opus-5";
|
|
29963
|
+
const BUILT_IN_MODE_DEFAULTS = Object.freeze({
|
|
29964
|
+
explore: {
|
|
29965
|
+
model: EXPLORE_DEFAULT_MODEL,
|
|
29966
|
+
thinking: EXPLORE_DEFAULT_THINKING
|
|
29967
|
+
},
|
|
29968
|
+
review: {
|
|
29969
|
+
model: REVIEW_DEFAULT_MODEL,
|
|
29970
|
+
thinking: REVIEW_DEFAULT_THINKING
|
|
29971
|
+
},
|
|
29972
|
+
plan: {
|
|
29973
|
+
model: PLAN_DEFAULT_MODEL,
|
|
29974
|
+
thinking: "xhigh"
|
|
29975
|
+
},
|
|
29976
|
+
implement: {
|
|
29977
|
+
model: IMPLEMENT_DEFAULT_MODEL,
|
|
29978
|
+
thinking: IMPLEMENT_DEFAULT_THINKING
|
|
29979
|
+
},
|
|
29980
|
+
test: {
|
|
29981
|
+
model: TEST_DEFAULT_MODEL,
|
|
29982
|
+
thinking: TEST_DEFAULT_THINKING
|
|
29983
|
+
},
|
|
29984
|
+
browse: {
|
|
29985
|
+
model: BROWSE_DEFAULT_MODEL,
|
|
29986
|
+
thinking: BROWSE_DEFAULT_THINKING
|
|
29987
|
+
}
|
|
29988
|
+
});
|
|
29989
|
+
/** Resolve the effective mode ladder without changing the gate sentinel. */
|
|
29990
|
+
function resolveModeDefaults(mode, ignoreSessionDefaults = false) {
|
|
29991
|
+
const builtIn = BUILT_IN_MODE_DEFAULTS[mode] ?? {
|
|
29992
|
+
model: DEFAULT_MODEL,
|
|
29993
|
+
thinking: DEFAULT_THINKING
|
|
29994
|
+
};
|
|
29995
|
+
const override = ignoreSessionDefaults ? {} : getWorkerSessionDefault(mode);
|
|
29996
|
+
return {
|
|
29997
|
+
model: override.model ?? builtIn.model,
|
|
29998
|
+
thinking: override.thinking ?? builtIn.thinking,
|
|
29999
|
+
modelSource: override.model === void 0 ? "built-in" : "override",
|
|
30000
|
+
thinkingSource: override.thinking === void 0 ? "built-in" : "override"
|
|
30001
|
+
};
|
|
30002
|
+
}
|
|
29440
30003
|
/**
|
|
29441
30004
|
* `Model<any>` shim used to satisfy `Agent.initialState.model` typing.
|
|
29442
30005
|
*
|
|
@@ -29450,6 +30013,7 @@ const PLAN_DEFAULT_THINKING = "xhigh";
|
|
|
29450
30013
|
* diagnostics (e.g. error-message AssistantMessage's `model` field
|
|
29451
30014
|
* if Pi ever inspects it) faithful to what the caller asked for.
|
|
29452
30015
|
*/
|
|
30016
|
+
let agentOptionsObserverForTests;
|
|
29453
30017
|
function makeModelShim(modelId) {
|
|
29454
30018
|
return {
|
|
29455
30019
|
id: modelId,
|
|
@@ -29487,6 +30051,34 @@ function extractAssistantText(content) {
|
|
|
29487
30051
|
for (const part of content) if (part.type === "text") out += part.text;
|
|
29488
30052
|
return out;
|
|
29489
30053
|
}
|
|
30054
|
+
const MAX_EMPTY_OUTPUT_NUDGES = 3;
|
|
30055
|
+
const EMPTY_OUTPUT_NUDGES = [
|
|
30056
|
+
"Summarize your findings so far.",
|
|
30057
|
+
"Your previous reply was empty. Provide the answer now in plain text.",
|
|
30058
|
+
"Reply with plain text only. Do not call any tool."
|
|
30059
|
+
];
|
|
30060
|
+
/**
|
|
30061
|
+
* Resolve the per-run nudge cap. Zero explicitly disables nudging; malformed,
|
|
30062
|
+
* negative, and fractional values fall back to the default.
|
|
30063
|
+
*/
|
|
30064
|
+
function resolveMaxEmptyOutputNudges() {
|
|
30065
|
+
const raw = process$1.env.GH_ROUTER_WORKER_MAX_NUDGES;
|
|
30066
|
+
if (raw === void 0 || raw === "") return MAX_EMPTY_OUTPUT_NUDGES;
|
|
30067
|
+
const parsed = Number(raw);
|
|
30068
|
+
if (!Number.isFinite(parsed) || parsed < 0 || !Number.isInteger(parsed)) return MAX_EMPTY_OUTPUT_NUDGES;
|
|
30069
|
+
return parsed;
|
|
30070
|
+
}
|
|
30071
|
+
function emptyOutputNudge(attempt) {
|
|
30072
|
+
return EMPTY_OUTPUT_NUDGES[Math.min(attempt, EMPTY_OUTPUT_NUDGES.length) - 1];
|
|
30073
|
+
}
|
|
30074
|
+
/** True only for a clean, empty assistant stop with no pending tool calls. */
|
|
30075
|
+
function shouldNudgeForEmptyOutput(message) {
|
|
30076
|
+
if (message.role !== "assistant") return false;
|
|
30077
|
+
const assistant = message;
|
|
30078
|
+
if (assistant.stopReason !== "stop" || !Array.isArray(assistant.content)) return false;
|
|
30079
|
+
if (assistant.content.some((part) => part.type === "toolCall")) return false;
|
|
30080
|
+
return extractAssistantText(assistant.content).trim() === "";
|
|
30081
|
+
}
|
|
29490
30082
|
/**
|
|
29491
30083
|
* Trivial stub for the no-worktree path. `dir` is the workspace
|
|
29492
30084
|
* itself; `finalize` returns an empty diff (the response text won't
|
|
@@ -29527,21 +30119,15 @@ async function runWorkerAgentOnce(opts) {
|
|
|
29527
30119
|
isError: true
|
|
29528
30120
|
};
|
|
29529
30121
|
try {
|
|
29530
|
-
const isBrowse = opts.mode === "browse";
|
|
29531
|
-
const isPlan = opts.mode === "plan";
|
|
29532
|
-
const isReview = opts.mode === "review";
|
|
29533
|
-
const isWriteCapable = opts.mode === "implement" || opts.mode === "test";
|
|
29534
|
-
const isExplore = opts.mode === "explore";
|
|
29535
|
-
const defaultModel = isBrowse ? BROWSE_DEFAULT_MODEL : isPlan ? PLAN_DEFAULT_MODEL : isReview ? REVIEW_DEFAULT_MODEL : isWriteCapable ? IMPLEMENT_DEFAULT_MODEL : isExplore ? EXPLORE_DEFAULT_MODEL : DEFAULT_MODEL;
|
|
29536
|
-
const defaultThinking = isBrowse ? BROWSE_DEFAULT_THINKING : isPlan ? PLAN_DEFAULT_THINKING : isReview ? REVIEW_DEFAULT_THINKING : isWriteCapable ? IMPLEMENT_DEFAULT_THINKING : DEFAULT_THINKING;
|
|
29537
30122
|
const resolved = resolveModelAndThinking({
|
|
29538
|
-
model: opts.model
|
|
29539
|
-
thinking: opts.thinking
|
|
30123
|
+
model: opts.model,
|
|
30124
|
+
thinking: opts.thinking
|
|
29540
30125
|
});
|
|
29541
30126
|
if (!resolved.ok) return {
|
|
29542
30127
|
text: resolved.error,
|
|
29543
30128
|
isError: true
|
|
29544
30129
|
};
|
|
30130
|
+
const isBrowse = opts.mode === "browse";
|
|
29545
30131
|
const ctxBudget = makeContextBudget(resolved.contextWindow);
|
|
29546
30132
|
const workspaceInput = opts.workspace ?? (isBrowse ? process$1.cwd() : void 0);
|
|
29547
30133
|
if (workspaceInput === void 0) return {
|
|
@@ -29581,7 +30167,7 @@ async function runWorkerAgentOnce(opts) {
|
|
|
29581
30167
|
getMessages,
|
|
29582
30168
|
planState
|
|
29583
30169
|
});
|
|
29584
|
-
const
|
|
30170
|
+
const agentOptions = {
|
|
29585
30171
|
initialState: {
|
|
29586
30172
|
systemPrompt: systemPromptFor(opts.mode),
|
|
29587
30173
|
model: makeModelShim(resolved.modelId),
|
|
@@ -29590,7 +30176,8 @@ async function runWorkerAgentOnce(opts) {
|
|
|
29590
30176
|
},
|
|
29591
30177
|
streamFn: createCopilotStreamFn({
|
|
29592
30178
|
resolved,
|
|
29593
|
-
contextBudget: ctxBudget
|
|
30179
|
+
contextBudget: ctxBudget,
|
|
30180
|
+
modelCallTimeoutMs: resolveWorkerModelCallTimeoutMs()
|
|
29594
30181
|
}),
|
|
29595
30182
|
toolExecution: "parallel",
|
|
29596
30183
|
transformContext: async (messages) => {
|
|
@@ -29623,6 +30210,7 @@ async function runWorkerAgentOnce(opts) {
|
|
|
29623
30210
|
if (a.trim()) terminalText = a;
|
|
29624
30211
|
}
|
|
29625
30212
|
},
|
|
30213
|
+
shouldStopAfterTurn: () => budget.hardStopReason !== null,
|
|
29626
30214
|
afterToolCall: async (ctx) => {
|
|
29627
30215
|
budget.recordToolBytes(ctx.result);
|
|
29628
30216
|
if (ctxBudget) {
|
|
@@ -29633,15 +30221,32 @@ async function runWorkerAgentOnce(opts) {
|
|
|
29633
30221
|
prepareNextTurn: async () => {
|
|
29634
30222
|
budget.addTurn();
|
|
29635
30223
|
}
|
|
29636
|
-
}
|
|
30224
|
+
};
|
|
30225
|
+
agentOptionsObserverForTests?.(agentOptions);
|
|
30226
|
+
const agent = new Agent$1(agentOptions);
|
|
29637
30227
|
agentHolder.agent = agent;
|
|
29638
|
-
const abortHandler = () => agent
|
|
29639
|
-
if (opts.signal)
|
|
29640
|
-
else opts.signal.addEventListener("abort", abortHandler, { once: true });
|
|
30228
|
+
const abortHandler = () => agent.abort();
|
|
30229
|
+
if (opts.signal) opts.signal.addEventListener("abort", abortHandler, { once: true });
|
|
29641
30230
|
let finalText = "";
|
|
29642
30231
|
let lastStopReason = null;
|
|
30232
|
+
let nudgeCount = 0;
|
|
30233
|
+
const maxEmptyOutputNudges = resolveMaxEmptyOutputNudges();
|
|
29643
30234
|
let terminalText = null;
|
|
29644
30235
|
const unsubscribe = agent.subscribe((event) => {
|
|
30236
|
+
if (event.type === "turn_end") {
|
|
30237
|
+
if (nudgeCount < maxEmptyOutputNudges && shouldNudgeForEmptyOutput(event.message)) {
|
|
30238
|
+
nudgeCount += 1;
|
|
30239
|
+
agent.followUp({
|
|
30240
|
+
role: "user",
|
|
30241
|
+
content: [{
|
|
30242
|
+
type: "text",
|
|
30243
|
+
text: emptyOutputNudge(nudgeCount)
|
|
30244
|
+
}],
|
|
30245
|
+
timestamp: Date.now()
|
|
30246
|
+
});
|
|
30247
|
+
}
|
|
30248
|
+
return;
|
|
30249
|
+
}
|
|
29645
30250
|
if (event.type !== "message_end") return;
|
|
29646
30251
|
const msg = event.message;
|
|
29647
30252
|
if (typeof msg !== "object" || msg === null) return;
|
|
@@ -29652,11 +30257,14 @@ async function runWorkerAgentOnce(opts) {
|
|
|
29652
30257
|
const sr = msg.stopReason;
|
|
29653
30258
|
if (typeof sr === "string") lastStopReason = sr;
|
|
29654
30259
|
});
|
|
30260
|
+
let wallClockExpired = false;
|
|
29655
30261
|
const wallClockTimer = setTimeout(() => {
|
|
29656
|
-
|
|
30262
|
+
wallClockExpired = true;
|
|
30263
|
+
agent.abort();
|
|
29657
30264
|
}, budget.config.maxWallClockMs);
|
|
29658
30265
|
wallClockTimer.unref?.();
|
|
29659
30266
|
try {
|
|
30267
|
+
if (opts.signal?.aborted) throw new Error("[halted: cancelled]");
|
|
29660
30268
|
await agent.prompt(opts.prompt);
|
|
29661
30269
|
await agent.waitForIdle();
|
|
29662
30270
|
let diff = "";
|
|
@@ -29669,12 +30277,26 @@ async function runWorkerAgentOnce(opts) {
|
|
|
29669
30277
|
await ws.remove();
|
|
29670
30278
|
} catch {}
|
|
29671
30279
|
const text = isBrowse ? terminalText ?? finalText : diff ? `${finalText}\n\n${diff}` : finalText;
|
|
29672
|
-
if (lastStopReason === "error")
|
|
29673
|
-
|
|
30280
|
+
if (lastStopReason === "error" || lastStopReason === "aborted") {
|
|
30281
|
+
const diag = (terminalText ?? finalText).trim();
|
|
30282
|
+
let diagnostic;
|
|
30283
|
+
if (lastStopReason === "aborted") diagnostic = wallClockExpired ? "[halted: wallclock]" : "[halted: cancelled]";
|
|
30284
|
+
else diagnostic = diag || "Worker run failed before producing an answer — the model's input likely overflowed (a large tool result), or the upstream errored. Retry with a narrower task: target a specific section / file / element rather than reading everything at once.";
|
|
30285
|
+
return {
|
|
30286
|
+
text: lastStopReason === "aborted" ? [
|
|
30287
|
+
diag,
|
|
30288
|
+
diff,
|
|
30289
|
+
diagnostic
|
|
30290
|
+
].filter(Boolean).join("\n\n") : [diagnostic, diff].filter(Boolean).join("\n\n"),
|
|
30291
|
+
isError: true
|
|
30292
|
+
};
|
|
30293
|
+
}
|
|
30294
|
+
if (budget.hardStopReason) return {
|
|
30295
|
+
text: [text, `[halted: ${budget.hardStopReason}]`].filter(Boolean).join("\n\n"),
|
|
29674
30296
|
isError: true
|
|
29675
30297
|
};
|
|
29676
30298
|
if (!text.trim()) return {
|
|
29677
|
-
text: `${NO_OUTPUT_PREFIX} (stopReason=${lastStopReason ?? "unknown"}, turns=${budget.turns}, elapsed=${budget.elapsedMs}ms)]
|
|
30299
|
+
text: `${NO_OUTPUT_PREFIX} after ${nudgeCount} nudges (stopReason=${lastStopReason ?? "unknown"}, turns=${budget.turns}, elapsed=${budget.elapsedMs}ms)]; retry with a different model via worker_defaults, or narrow/split the task.`,
|
|
29678
30300
|
isError: true
|
|
29679
30301
|
};
|
|
29680
30302
|
return { text };
|
|
@@ -29708,42 +30330,21 @@ async function runWorkerAgentOnce(opts) {
|
|
|
29708
30330
|
}
|
|
29709
30331
|
/**
|
|
29710
30332
|
* Prefix of the sentinel `runWorkerAgentOnce` returns when a worker stops
|
|
29711
|
-
*
|
|
29712
|
-
*
|
|
29713
|
-
* exactly this case. Distinct from a budget cap (`WorkerAbort` → halt message),
|
|
29714
|
-
* a stream error (`stopReason="error"` → overflow/upstream diagnostic), and a
|
|
29715
|
-
* real failure — none of which carry this prefix, so none are retried.
|
|
30333
|
+
* cleanly but emits no usable text even after its bounded in-run nudges. Kept
|
|
30334
|
+
* stable for callers that recognize the existing sentinel shape.
|
|
29716
30335
|
*/
|
|
29717
30336
|
const NO_OUTPUT_PREFIX = "[worker exited with no output";
|
|
29718
|
-
/**
|
|
29719
|
-
|
|
29720
|
-
|
|
29721
|
-
|
|
29722
|
-
|
|
29723
|
-
|
|
29724
|
-
|
|
29725
|
-
}
|
|
29726
|
-
/**
|
|
29727
|
-
* Run `runOnce`, and on the transient no-output sentinel retry EXACTLY ONCE with
|
|
29728
|
-
* a fresh run before surfacing it. Real errors, budget caps, and stream errors
|
|
29729
|
-
* are returned as-is (they have distinct, actionable messages and a retry would
|
|
29730
|
-
* not help). A consumed abort signal short-circuits the retry. If the retry also
|
|
29731
|
-
* produces no output, the ORIGINAL is returned (one is enough signal; the
|
|
29732
|
-
* failure isn't hidden). Extracted + injected for unit-testability.
|
|
29733
|
-
*/
|
|
29734
|
-
async function withNoOutputRetry(runOnce, opts) {
|
|
29735
|
-
const first = await runOnce(opts);
|
|
29736
|
-
if (!isTransientNoOutput(first) || opts.signal?.aborted) return first;
|
|
29737
|
-
const second = await runOnce(opts);
|
|
29738
|
-
return isTransientNoOutput(second) ? first : second;
|
|
30337
|
+
/** Public entry. Resolve model/thinking once, then run under one transcript and Budget. */
|
|
30338
|
+
function resolveWorkerRunOpts(opts) {
|
|
30339
|
+
const defaults = resolveModeDefaults(opts.mode, opts.ignoreSessionDefaults === true);
|
|
30340
|
+
return {
|
|
30341
|
+
...opts,
|
|
30342
|
+
model: opts.model ?? defaults.model,
|
|
30343
|
+
thinking: opts.thinking ?? defaults.thinking
|
|
30344
|
+
};
|
|
29739
30345
|
}
|
|
29740
|
-
/**
|
|
29741
|
-
* Public entry: a worker run with a single transient-no-output retry. Wraps the
|
|
29742
|
-
* implementation (`runWorkerAgentOnce`); the signature is unchanged so every
|
|
29743
|
-
* caller (MCP dispatch, the orchestration runner) gets the retry for free.
|
|
29744
|
-
*/
|
|
29745
30346
|
async function runWorkerAgent(opts) {
|
|
29746
|
-
return
|
|
30347
|
+
return runWorkerAgentOnce(resolveWorkerRunOpts(opts));
|
|
29747
30348
|
}
|
|
29748
30349
|
/**
|
|
29749
30350
|
* Test-only exports. The public surface of the engine is
|
|
@@ -29794,8 +30395,8 @@ const STAND_IN_MODELS = Object.freeze([
|
|
|
29794
30395
|
effort: "xhigh"
|
|
29795
30396
|
},
|
|
29796
30397
|
{
|
|
29797
|
-
key: "claude-opus-
|
|
29798
|
-
model: "claude-opus-
|
|
30398
|
+
key: "claude-opus-5",
|
|
30399
|
+
model: "claude-opus-5",
|
|
29799
30400
|
endpoint: "/v1/messages",
|
|
29800
30401
|
effort: "xhigh"
|
|
29801
30402
|
},
|
|
@@ -31891,7 +32492,8 @@ async function runWorkflowLive(opts) {
|
|
|
31891
32492
|
mode,
|
|
31892
32493
|
prompt,
|
|
31893
32494
|
workspace,
|
|
31894
|
-
signal: opts.signal
|
|
32495
|
+
signal: opts.signal,
|
|
32496
|
+
ignoreSessionDefaults: true
|
|
31895
32497
|
});
|
|
31896
32498
|
return {
|
|
31897
32499
|
text: r.text,
|
|
@@ -32011,10 +32613,11 @@ function isMcpGroup(s) {
|
|
|
32011
32613
|
* (handler.ts:handleToolsCallSSE). Claude Code's MCP HTTP client honors
|
|
32012
32614
|
* `text/event-stream` responses without applying the ~60s per-tool-call
|
|
32013
32615
|
* timer that previously broke xhigh on gpt-5.5 (~56s wall) and on
|
|
32014
|
-
* Anthropic Opus families (high+ thinking budgets). opus-critic
|
|
32015
|
-
*
|
|
32016
|
-
*
|
|
32017
|
-
*
|
|
32616
|
+
* Anthropic Opus families (high+ thinking budgets). opus-critic caps its
|
|
32617
|
+
* exposed effort at `high` (its effective model can fall back to
|
|
32618
|
+
* claude-opus-4-6, which doesn't advertise xhigh), so the SSE long-tail
|
|
32619
|
+
* concern there is moot; the SSE machinery still applies to the other
|
|
32620
|
+
* personas that do expose xhigh.
|
|
32018
32621
|
*/
|
|
32019
32622
|
const EFFORT_LEVELS = [
|
|
32020
32623
|
"low",
|
|
@@ -32147,7 +32750,7 @@ Reply format (markdown):
|
|
|
32147
32750
|
|
|
32148
32751
|
Resilience reminder:
|
|
32149
32752
|
If your session terminates abnormally before "Status: complete", the lead will retry once. On recovery, ask the lead to confirm what's already been done before re-applying changes — duplicate edits are worse than a slow restart.`;
|
|
32150
|
-
const OPUS_CRITIC_BASE = `You are opus-critic, a fresh-context same-lab adversarial reviewer running on Opus
|
|
32753
|
+
const OPUS_CRITIC_BASE = `You are opus-critic, a fresh-context same-lab adversarial reviewer running on Opus 5. The lead orchestrator that just delegated to you runs Opus-family context too, but you are NOT the lead. You did not see the lead's reasoning trace. You only see the brief.
|
|
32151
32754
|
|
|
32152
32755
|
Your job is to spot what the lead missed because of cognitive momentum, sunk-cost on a plan, or motivated reasoning toward a particular fix. Your blind-spot diversification is LIMITED compared to codex-critic (gpt-5.6-sol) and gemini-critic (gemini-3.1-pro), same lab, adjacent model family, related priors. Use that honestly: don't pretend to find a different perspective when the obvious read is "the lead got it right." Silence on good work is a valid and welcome answer.
|
|
32153
32756
|
|
|
@@ -32232,9 +32835,9 @@ const PERSONAS_READ = Object.freeze([
|
|
|
32232
32835
|
{
|
|
32233
32836
|
agentName: "opus-critic",
|
|
32234
32837
|
toolNameHttp: "opus_critic",
|
|
32235
|
-
model: "claude-opus-
|
|
32838
|
+
model: "claude-opus-5",
|
|
32236
32839
|
endpoint: "/v1/messages",
|
|
32237
|
-
description: "Adversarial same-lab critic backed by fresh-context Opus
|
|
32840
|
+
description: "Adversarial same-lab critic backed by fresh-context Opus 5, with limited blind-spot diversity compared with cross-lab critics. It reviews plans, designs, or code tradeoffs for cognitive momentum, sunk-cost reasoning, and confabulated assumptions, then returns a calibrated objection or no material objection. Use when a same-family sanity check can catch lead-context drift or when comparing against codex_critic / gemini_critic findings. Not a substitute for cross-lab review on security-sensitive or high-risk changes; use codex_critic or gemini_critic for stronger diversity. Runs with the full 1M-context Opus 5 window (native, no -1m sibling needed). Pass artifact verbatim.",
|
|
32238
32841
|
baseInstructions: OPUS_CRITIC_BASE,
|
|
32239
32842
|
agentPrompt: "",
|
|
32240
32843
|
writeCapable: false,
|
|
@@ -32383,7 +32986,7 @@ function buildPeerAwarenessSnippet(opts) {
|
|
|
32383
32986
|
criticList.push("`gemini_reviewer` (gemini-3.1-pro, line-level code review)");
|
|
32384
32987
|
criticList.push("`gemini_critic` (gemini-3.1-pro)");
|
|
32385
32988
|
}
|
|
32386
|
-
criticList.push("`opus_critic` (Opus
|
|
32989
|
+
criticList.push("`opus_critic` (Opus 5)");
|
|
32387
32990
|
const codexCliClause = opts.codexCli ? " `mcp__codex-cli__codex` dispatches to `codex-implementer` (gpt-5.3-codex with workspace-write) for end-to-end coding tasks." : "";
|
|
32388
32991
|
const para2Parts = [`\`mcp__${searchKey}__code\` is the one-stop code search (no extra model call). Its DEFAULT mode (or \`mode:"semantic"\`) ranks by MEANING via ColBERT over a per-workspace index, the first thing to reach for on intent/concept questions ("where is retry/backoff handled", "how does auth work"); when that index isn't ready it transparently falls back to lexical (the response \`source\` says which engine ran). Forced modes cover the rest: \`lexical\` (BM25F-ranked + tree-sitter, best for exact symbols), \`exact\`, \`regex\`, \`complete\` (exhaustive set), \`ast_pattern\`+\`ast_lang\` for multi-line AST shapes, \`scan\` for a whole-workspace symbol outline, \`multiline\` for cross-line regex. Multiple queries can run in a single turn. The index covers code-shaped files; for unstructured files (logs, \`.csv\`, \`.env*\`, config-only wiring), \`grep\`/\`glob\` still apply.`];
|
|
32389
32992
|
if (opts.workerToolsAvailable) para2Parts.push(`\`worker-*\` are background Agent subagents (subagent_type) that run the matching worker in its own context and deliver the result as a completion notification, so a long run never blocks the turn: \`worker-explore\` (read-only research), \`worker-review\` (reads the code to verify a change or claim), \`worker-plan\` (ordered implementation plan), \`worker-implement\` (edit/write/bash; ALWAYS runs in an isolated git worktree and returns the diff via a saved patch file; for in-place edits use the \`implementer\` subagent), \`worker-test\` (independent test author; also always worktree-isolated). The raw \`mcp__${workersKey}__*\` tools they call are guarded (a direct main-thread call is redirected to the matching agent); Workers themselves have \`code_search\`.`);
|
|
@@ -32460,6 +33063,15 @@ function formatWebSearchResult(results) {
|
|
|
32460
33063
|
const refsLine = results.references.map((r) => `- [${r.title}](${r.url})`).join("\n");
|
|
32461
33064
|
return `${results.content}\n\n## References\n${refsLine}`;
|
|
32462
33065
|
}
|
|
33066
|
+
/**
|
|
33067
|
+
* Model-override tier ladder surfaced on the read-heavy workers
|
|
33068
|
+
* (explore / implement / review). The caller picks a model by task weight;
|
|
33069
|
+
* all three tiers are 1M-context, and `high` is the recommended reasoning
|
|
33070
|
+
* depth for the ladder (flash tops out at high; sol/terra go higher if the
|
|
33071
|
+
* caller wants). Appended to those tools' `model` param description so the
|
|
33072
|
+
* lead has actionable override guidance instead of a bare free string.
|
|
33073
|
+
*/
|
|
33074
|
+
const WORKER_TIER_GUIDANCE = " Override by task weight: `gpt-5.6-sol` (heavy/deep), `gpt-5.6-terra` (moderate), `gemini-3.6-flash` (light/cheap) — all 1M context; pair with thinking:'high'.";
|
|
32463
33075
|
const NON_PERSONA_MCP_TOOLS = Object.freeze([
|
|
32464
33076
|
{
|
|
32465
33077
|
toolNameHttp: "web",
|
|
@@ -32647,11 +33259,84 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
|
|
|
32647
33259
|
}
|
|
32648
33260
|
}
|
|
32649
33261
|
},
|
|
33262
|
+
{
|
|
33263
|
+
toolNameHttp: "worker_defaults",
|
|
33264
|
+
group: "workers",
|
|
33265
|
+
capability: "worker",
|
|
33266
|
+
description: "Sets or clears process-wide worker model/reasoning defaults and returns the full effective table. Omit arguments to inspect current values. Per-call worker arguments still take precedence; values are in-memory and apply to every client served by this process.",
|
|
33267
|
+
inputSchema: {
|
|
33268
|
+
type: "object",
|
|
33269
|
+
additionalProperties: false,
|
|
33270
|
+
properties: {
|
|
33271
|
+
mode: {
|
|
33272
|
+
type: "string",
|
|
33273
|
+
enum: WORKER_MODES,
|
|
33274
|
+
description: "Worker mode to set or clear."
|
|
33275
|
+
},
|
|
33276
|
+
model: {
|
|
33277
|
+
type: "string",
|
|
33278
|
+
description: "Copilot catalog model id to use by default for the mode."
|
|
33279
|
+
},
|
|
33280
|
+
thinking: {
|
|
33281
|
+
type: "string",
|
|
33282
|
+
enum: WORKER_THINKING_LEVELS,
|
|
33283
|
+
description: "Requested default reasoning level; clamped per run for the selected model."
|
|
33284
|
+
},
|
|
33285
|
+
clear: {
|
|
33286
|
+
type: "boolean",
|
|
33287
|
+
description: "When true, clears both overrides for the selected mode."
|
|
33288
|
+
},
|
|
33289
|
+
clearAll: {
|
|
33290
|
+
type: "boolean",
|
|
33291
|
+
description: "When true, clears overrides for every worker mode."
|
|
33292
|
+
}
|
|
33293
|
+
}
|
|
33294
|
+
},
|
|
33295
|
+
async handler(args) {
|
|
33296
|
+
const mode = typeof args.mode === "string" && WORKER_MODES.includes(args.mode) ? args.mode : void 0;
|
|
33297
|
+
const model = typeof args.model === "string" ? args.model : void 0;
|
|
33298
|
+
const thinking = typeof args.thinking === "string" && WORKER_THINKING_LEVELS.includes(args.thinking) ? args.thinking : void 0;
|
|
33299
|
+
const clear = args.clear === true;
|
|
33300
|
+
const clearAll = args.clearAll === true;
|
|
33301
|
+
if (args.mode !== void 0 && mode === void 0 || args.model !== void 0 && model === void 0 || args.thinking !== void 0 && thinking === void 0 || args.clear !== void 0 && typeof args.clear !== "boolean" || args.clearAll !== void 0 && typeof args.clearAll !== "boolean" || clearAll && (mode !== void 0 || model !== void 0 || thinking !== void 0 || args.clear !== void 0) || clear && (mode === void 0 || model !== void 0 || thinking !== void 0) || !clearAll && !clear && (model !== void 0 || thinking !== void 0) && mode === void 0) return {
|
|
33302
|
+
content: [{
|
|
33303
|
+
type: "text",
|
|
33304
|
+
text: "worker_defaults: use mode with model/thinking or clear:true; clearAll:true must stand alone"
|
|
33305
|
+
}],
|
|
33306
|
+
isError: true
|
|
33307
|
+
};
|
|
33308
|
+
if (clearAll) resetAllWorkerSessionDefaults();
|
|
33309
|
+
else if (clear && mode) resetWorkerSessionDefault(mode);
|
|
33310
|
+
else if (mode && (model !== void 0 || thinking !== void 0)) {
|
|
33311
|
+
const current = resolveModeDefaults(mode);
|
|
33312
|
+
const validation = resolveModelAndThinking({
|
|
33313
|
+
model: model ?? current.model,
|
|
33314
|
+
thinking: thinking ?? current.thinking
|
|
33315
|
+
});
|
|
33316
|
+
if (!validation.ok) return {
|
|
33317
|
+
content: [{
|
|
33318
|
+
type: "text",
|
|
33319
|
+
text: validation.error
|
|
33320
|
+
}],
|
|
33321
|
+
isError: true
|
|
33322
|
+
};
|
|
33323
|
+
setWorkerSessionDefault(mode, {
|
|
33324
|
+
model,
|
|
33325
|
+
thinking
|
|
33326
|
+
});
|
|
33327
|
+
}
|
|
33328
|
+
const table = Object.fromEntries(WORKER_MODES.map((workerMode) => [workerMode, resolveModeDefaults(workerMode)]));
|
|
33329
|
+
return { content: [{
|
|
33330
|
+
type: "text",
|
|
33331
|
+
text: JSON.stringify(table)
|
|
33332
|
+
}] };
|
|
33333
|
+
}
|
|
33334
|
+
},
|
|
32650
33335
|
{
|
|
32651
33336
|
toolNameHttp: "explore",
|
|
32652
33337
|
group: "workers",
|
|
32653
33338
|
capability: "worker",
|
|
32654
|
-
description: "Runs as the background `worker-explore` agent. Dispatch via the Agent tool (subagent_type: worker-explore) so the turn is never blocked; the result arrives as a completion notification. Read-only investigation by an autonomous worker (Pi runtime; default model `
|
|
33339
|
+
description: "Runs as the background `worker-explore` agent. Dispatch via the Agent tool (subagent_type: worker-explore) so the turn is never blocked; the result arrives as a completion notification. Read-only investigation by an autonomous worker (Pi runtime; default model `gemini-3.6-flash` at high reasoning, override via the `model` arg with any Copilot-catalog model that advertises `tool_calls`). It has read, glob, grep, semantic-first code search, web search, fetch_url, advisor, update_plan, and read-only toolbelt tools, and it returns a single text answer. Use for bounded research, repo discovery, dependency investigation, or multi-file reading that would otherwise consume the lead context window. Not for implementation, test authoring, or verification of a concrete diff; use implement, test, or review for those scopes. Brief the investigation goal and constraints, not step-by-step tool semantics.",
|
|
32655
33340
|
inputSchema: {
|
|
32656
33341
|
type: "object",
|
|
32657
33342
|
required: ["prompt"],
|
|
@@ -32663,19 +33348,12 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
|
|
|
32663
33348
|
},
|
|
32664
33349
|
model: {
|
|
32665
33350
|
type: "string",
|
|
32666
|
-
description: "Optional Copilot catalog model id (defaults to
|
|
33351
|
+
description: "Optional Copilot catalog model id (defaults to gemini-3.6-flash). Must advertise tool_calls support; the engine emits an isError envelope listing the eligible catalog models on mismatch." + WORKER_TIER_GUIDANCE
|
|
32667
33352
|
},
|
|
32668
33353
|
thinking: {
|
|
32669
33354
|
type: "string",
|
|
32670
|
-
enum:
|
|
32671
|
-
|
|
32672
|
-
"minimal",
|
|
32673
|
-
"low",
|
|
32674
|
-
"medium",
|
|
32675
|
-
"high",
|
|
32676
|
-
"xhigh"
|
|
32677
|
-
],
|
|
32678
|
-
description: "Optional reasoning depth (default xhigh). Silently clamped to the model's allowed range; \"off\" drops the parameter entirely."
|
|
33355
|
+
enum: WORKER_THINKING_LEVELS,
|
|
33356
|
+
description: "Optional reasoning depth. Use worker_defaults to inspect the effective value. Silently clamped to the model's allowed range; \"off\" drops the parameter entirely."
|
|
32679
33357
|
},
|
|
32680
33358
|
workspace: {
|
|
32681
33359
|
type: "string",
|
|
@@ -32715,19 +33393,12 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
|
|
|
32715
33393
|
},
|
|
32716
33394
|
model: {
|
|
32717
33395
|
type: "string",
|
|
32718
|
-
description: "Optional Copilot catalog model id (defaults to gpt-5.6-sol). Must advertise tool_calls support; the engine emits an isError envelope listing the eligible catalog models on mismatch."
|
|
33396
|
+
description: "Optional Copilot catalog model id (defaults to gpt-5.6-sol). Must advertise tool_calls support; the engine emits an isError envelope listing the eligible catalog models on mismatch." + WORKER_TIER_GUIDANCE
|
|
32719
33397
|
},
|
|
32720
33398
|
thinking: {
|
|
32721
33399
|
type: "string",
|
|
32722
|
-
enum:
|
|
32723
|
-
|
|
32724
|
-
"minimal",
|
|
32725
|
-
"low",
|
|
32726
|
-
"medium",
|
|
32727
|
-
"high",
|
|
32728
|
-
"xhigh"
|
|
32729
|
-
],
|
|
32730
|
-
description: "Optional reasoning depth (default xhigh). Silently clamped to the model's allowed range; \"off\" drops the parameter entirely."
|
|
33400
|
+
enum: WORKER_THINKING_LEVELS,
|
|
33401
|
+
description: "Optional reasoning depth. Use worker_defaults to inspect the effective value. Silently clamped to the model's allowed range; \"off\" drops the parameter entirely."
|
|
32731
33402
|
},
|
|
32732
33403
|
workspace: {
|
|
32733
33404
|
type: "string",
|
|
@@ -32763,18 +33434,11 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
|
|
|
32763
33434
|
},
|
|
32764
33435
|
model: {
|
|
32765
33436
|
type: "string",
|
|
32766
|
-
description: "Optional Copilot catalog model id (defaults to gemini-3.1-pro-preview). Must advertise tool_calls support; the engine emits an isError envelope listing the eligible catalog models on mismatch."
|
|
33437
|
+
description: "Optional Copilot catalog model id (defaults to gemini-3.1-pro-preview). Must advertise tool_calls support; the engine emits an isError envelope listing the eligible catalog models on mismatch." + WORKER_TIER_GUIDANCE
|
|
32767
33438
|
},
|
|
32768
33439
|
thinking: {
|
|
32769
33440
|
type: "string",
|
|
32770
|
-
enum:
|
|
32771
|
-
"off",
|
|
32772
|
-
"minimal",
|
|
32773
|
-
"low",
|
|
32774
|
-
"medium",
|
|
32775
|
-
"high",
|
|
32776
|
-
"xhigh"
|
|
32777
|
-
],
|
|
33441
|
+
enum: WORKER_THINKING_LEVELS,
|
|
32778
33442
|
description: "Optional reasoning depth (defaults to xhigh, clamped to high for the default review model). Silently clamped to the model's allowed range; \"off\" drops the parameter entirely."
|
|
32779
33443
|
},
|
|
32780
33444
|
workspace: {
|
|
@@ -32799,7 +33463,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
|
|
|
32799
33463
|
toolNameHttp: "plan",
|
|
32800
33464
|
group: "workers",
|
|
32801
33465
|
capability: "worker",
|
|
32802
|
-
description: "Runs as the background `worker-plan` agent. Dispatch via the Agent tool (subagent_type: worker-plan) so the turn is never blocked; the result arrives as a completion notification. Read-only implementation planning by an autonomous worker (Pi runtime; default model `claude-opus-
|
|
33466
|
+
description: "Runs as the background `worker-plan` agent. Dispatch via the Agent tool (subagent_type: worker-plan) so the turn is never blocked; the result arrives as a completion notification. Read-only implementation planning by an autonomous worker (Pi runtime; default model `claude-opus-5` at xhigh reasoning, override via `model` with any Copilot-catalog model that advertises `tool_calls`). It has the same read-only toolset as explore and returns a concrete, ordered implementation plan covering files, approach, risks, and how acceptance criteria will be verified. Use before coding when the task needs repo-grounded sequencing or acceptance criteria translated into implementation steps. Not for editing files, running an implementation, writing tests, or adversarial review; use implement, test, or review for those scopes.",
|
|
32803
33467
|
inputSchema: {
|
|
32804
33468
|
type: "object",
|
|
32805
33469
|
required: ["prompt"],
|
|
@@ -32811,19 +33475,12 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
|
|
|
32811
33475
|
},
|
|
32812
33476
|
model: {
|
|
32813
33477
|
type: "string",
|
|
32814
|
-
description: "Optional Copilot catalog model id (defaults to claude-opus-
|
|
33478
|
+
description: "Optional Copilot catalog model id (defaults to claude-opus-5). Must advertise tool_calls support; the engine emits an isError envelope listing the eligible catalog models on mismatch."
|
|
32815
33479
|
},
|
|
32816
33480
|
thinking: {
|
|
32817
33481
|
type: "string",
|
|
32818
|
-
enum:
|
|
32819
|
-
|
|
32820
|
-
"minimal",
|
|
32821
|
-
"low",
|
|
32822
|
-
"medium",
|
|
32823
|
-
"high",
|
|
32824
|
-
"xhigh"
|
|
32825
|
-
],
|
|
32826
|
-
description: "Optional reasoning depth (default xhigh). Silently clamped to the model's allowed range; \"off\" drops the parameter entirely."
|
|
33482
|
+
enum: WORKER_THINKING_LEVELS,
|
|
33483
|
+
description: "Optional reasoning depth. Use worker_defaults to inspect the effective value. Silently clamped to the model's allowed range; \"off\" drops the parameter entirely."
|
|
32827
33484
|
},
|
|
32828
33485
|
workspace: {
|
|
32829
33486
|
type: "string",
|
|
@@ -32867,15 +33524,8 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
|
|
|
32867
33524
|
},
|
|
32868
33525
|
thinking: {
|
|
32869
33526
|
type: "string",
|
|
32870
|
-
enum:
|
|
32871
|
-
|
|
32872
|
-
"minimal",
|
|
32873
|
-
"low",
|
|
32874
|
-
"medium",
|
|
32875
|
-
"high",
|
|
32876
|
-
"xhigh"
|
|
32877
|
-
],
|
|
32878
|
-
description: "Optional reasoning depth (default xhigh). Silently clamped to the model's allowed range; \"off\" drops the parameter entirely."
|
|
33527
|
+
enum: WORKER_THINKING_LEVELS,
|
|
33528
|
+
description: "Optional reasoning depth. Use worker_defaults to inspect the effective value. Silently clamped to the model's allowed range; \"off\" drops the parameter entirely."
|
|
32879
33529
|
},
|
|
32880
33530
|
workspace: {
|
|
32881
33531
|
type: "string",
|
|
@@ -33248,14 +33898,7 @@ async function runWorkerToolCall(call) {
|
|
|
33248
33898
|
isError: true
|
|
33249
33899
|
};
|
|
33250
33900
|
const thinkingRaw = args.thinking;
|
|
33251
|
-
const ALLOWED_THINKING =
|
|
33252
|
-
"off",
|
|
33253
|
-
"minimal",
|
|
33254
|
-
"low",
|
|
33255
|
-
"medium",
|
|
33256
|
-
"high",
|
|
33257
|
-
"xhigh"
|
|
33258
|
-
];
|
|
33901
|
+
const ALLOWED_THINKING = WORKER_THINKING_LEVELS;
|
|
33259
33902
|
let thinking;
|
|
33260
33903
|
if (thinkingRaw !== void 0) {
|
|
33261
33904
|
if (typeof thinkingRaw !== "string" || !ALLOWED_THINKING.includes(thinkingRaw)) return {
|
|
@@ -33568,5 +34211,5 @@ function enumerateInjectedMcpToolNames(groupKeys, opts = {}) {
|
|
|
33568
34211
|
}
|
|
33569
34212
|
|
|
33570
34213
|
//#endregion
|
|
33571
|
-
export {
|
|
33572
|
-
//# sourceMappingURL=peer-mcp-personas-
|
|
34214
|
+
export { searchWeb as $, UPSTREAM_FETCH_TIMEOUT_MS as $t, trustRepo as A, createResponses as At, TEST_DEFAULT_MODEL as B, warmTreeSitterPool as Bt, fileLastPromptStore as C, copilotBaseUrl as Cn, shimDefaultsToXhigh as Ct, repoRoot as D, assembleResponsesPayload as Dt, repoFingerprint as E, state as En, getTokenCount as Et, EXPLORE_DEFAULT_MODEL as F, provisionBrowserAssets as Ft, buildEnv as G, buildWorkspaceHeaderHelperCommand as Gt, resolveModeDefaults as H, DEFINITION_OF_GREATNESS as Ht, EXPLORE_DEFAULT_THINKING as I, hasSupportedBrowserInstalled as It, toolbeltEnabled as J, toolbeltPathOverride as Jt, availableToolCommands as K, buildWorkspaceHeaderJson as Kt, IMPLEMENT_DEFAULT_MODEL as L, provisionAndIndexColbert as Lt, resolveSealedGate as M, MAX_RESPONSE_BODY_BYTES as Mt, BROWSE_DEFAULT_MODEL as N, readResponseBodyCapped as Nt, stopGateEnabledForRepo as O, resolveMcpToolTimeoutMs as Ot, DEFAULT_MODEL as P, parseJsonOrDiagnose as Pt, assetFor as Q, DEFAULT_PORT as Qt, PLAN_DEFAULT_MODEL as R, extractTarGzMember as Rt, fileFindingsStore as S, GITHUB_API_BASE_URL as Sn, workerToolsEnabled as St, isSubagentContext as T, githubHeaders as Tn, createMessages as Tt, resolveWorkerRunOpts as U, shouldUseInsecureTls as Ut, appendPlanReminder as V, CONDENSED_OPERATING_SEQUENCE as Vt, runWorkerAgent as W, ArtifactClient as Wt, vscodeRipgrepPath as X, DEFAULT_CODEX_MODEL as Xt, toolbeltSkipSet as Y, DEFAULT_CLAUDE_MODEL_FALLBACKS as Yt, TOOLBELT_TOOLS$1 as Z, DEFAULT_CODEX_MODEL_FALLBACKS as Zt, stopGateDisabled as _, getModels as _n, browserToolsEnabled as _t, buildPeerAwarenessSnippet as a, setupCopilotToken as an, buildAnthropicErrorEvent as at, stopReviewEnabled as b, HTTPError as bn, nativeSubagentModel as bt, personasFor as c, tryRefreshAndRetry as cn, logStreamError as ct, buildStopHookCommand as d, cacheVSCodeVersion as dn, handleMcpDelete as dt, UPSTREAM_INACTIVITY_TIMEOUT_MS as en, ADVISOR_INTERNAL_TOOL_NAME as et, captureLaunchBaseline as f, filterBetaHeader as fn, handleMcpPost as ft, launchBaselineKey as g, sleep as gn, browserCompoundToolsEnabled as gt, injectStopHookIntoSettingsFile as h, resolveModel as hn, browseAgentEnabled as ht, buildAgentPrompt as i, withInstallLock as in, isAdvisorRequested as it, liveExec as j, createChatCompletions as jt, stopReviewStateDir as k, pickEndpoint as kt, buildArtifactOpenHookCommand as l, cacheCopilotVersion as ln, readIteratorWithTimeout as lt, fileBlockBudget as m, resolveCodexModel as mn, artifactToolsEnabled as mt, MCP_GROUPS as n, pickClaudeDefault as nn, buildAdvisorStream as nt, buildPeerAwarenessSummary as o, setupGitHubAgentToken as on, buildOpenAIErrorEvent as ot, decideStopHook as p, isNullish as pn, agentToolsEnabled as pt, buildToolbeltAwareness as q, collapsePathKeys as qt, assertMcpToolSurfaceConsistent as r, getPackageVersion as rn, injectAdvisorTool as rt, enumerateInjectedMcpToolNames as s, setupGitHubToken as sn, isControllerClosedError as st, GROUP_META as t, generateRandomPort as tn, ADVISOR_TOOL_INSTRUCTIONS as tt, buildSessionBindHookCommand as u, cacheModels as un, relayAnthropicStream as ut, stopGateId as v, getGitHubUser as vn, fleetToolsEnabled as vt, fileReviewDebounce as w, copilotHeaders as wn, countTokens as wt, fileBaselineStore as x, forwardError as xn, standInToolEnabled as xt, stopGatePlanMode as y, fetchWithTransientRetry as yn, geminiAvailable as yt, REVIEW_DEFAULT_MODEL as z, extractZipMember as zt };
|
|
34215
|
+
//# sourceMappingURL=peer-mcp-personas-BKwMCOsl.js.map
|