github-router 0.3.219 → 0.3.233
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/README.md +12 -0
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{engine-DyY5qdpL.js → engine-BEvlds_1.js} +4 -4
- package/dist/lib/tree-sitter-pool/worker.js +30 -23
- package/dist/{lifecycle-D-1CYr1Y.js → lifecycle-C5ALWmZK.js} +2 -2
- package/dist/{lifecycle-D-1CYr1Y.js.map → lifecycle-C5ALWmZK.js.map} +1 -1
- package/dist/{lifecycle-bPdiXjYB.js → lifecycle-DR4TGEIY.js} +2 -2
- package/dist/{lifecycle-bPdiXjYB.js.map → lifecycle-DR4TGEIY.js.map} +1 -1
- package/dist/{lifecycle-Ci81s6eL.js → lifecycle-Ls5uCcdw.js} +2 -2
- package/dist/{lifecycle-CJV1XBFP.js → lifecycle-f-zGtSyk.js} +2 -2
- package/dist/main.js +163 -33
- package/dist/main.js.map +1 -1
- package/dist/{paths-B5k78n0d.js → paths-D20MaHeo.js} +1 -1
- package/dist/{paths-BO22pMUb.js → paths-ogCi3URX.js} +30 -4
- package/dist/{paths-BO22pMUb.js.map → paths-ogCi3URX.js.map} +1 -1
- package/dist/{peer-mcp-personas-A6PytLD6.js → peer-mcp-personas-B-EPdjSE.js} +802 -436
- package/dist/peer-mcp-personas-B-EPdjSE.js.map +1 -0
- package/package.json +2 -2
- package/dist/peer-mcp-personas-A6PytLD6.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as PATHS } from "./paths-
|
|
2
|
-
import { d as runCommandCapture, l as parseBoolEnv, n as isPidAlive, o as trackChild, p as runManagedExeCapture, r as registerColbertExitHandlers, t as getColbertInstanceUuid, u as resolveExecutable } from "./lifecycle-
|
|
3
|
-
import { i as registerExitHandlers, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-
|
|
1
|
+
import { t as PATHS } from "./paths-ogCi3URX.js";
|
|
2
|
+
import { d as runCommandCapture, l as parseBoolEnv, n as isPidAlive, o as trackChild, p as runManagedExeCapture, r as registerColbertExitHandlers, t as getColbertInstanceUuid, u as resolveExecutable } from "./lifecycle-C5ALWmZK.js";
|
|
3
|
+
import { i as registerExitHandlers, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-DR4TGEIY.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import consola from "consola";
|
|
6
6
|
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -13335,40 +13335,47 @@ function deriveDefinitionName(node) {
|
|
|
13335
13335
|
return null;
|
|
13336
13336
|
}
|
|
13337
13337
|
/**
|
|
13338
|
-
* Collect
|
|
13339
|
-
*
|
|
13340
|
-
*
|
|
13341
|
-
*
|
|
13342
|
-
*
|
|
13343
|
-
|
|
13344
|
-
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
|
|
13348
|
-
|
|
13349
|
-
|
|
13350
|
-
|
|
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
|
+
]);
|
|
13351
13356
|
function collectDefinitions(root, defTypes, signal) {
|
|
13352
13357
|
const out = [];
|
|
13353
|
-
const visit = (node, depth) => {
|
|
13358
|
+
const visit = (node, depth, includeDefinitions) => {
|
|
13354
13359
|
if (signal?.aborted || out.length >= MAX_OUTLINE_ENTRIES) return;
|
|
13355
13360
|
for (const child of node.namedChildren) {
|
|
13356
13361
|
if (signal?.aborted || out.length >= MAX_OUTLINE_ENTRIES) return;
|
|
13357
13362
|
if (defTypes.has(child.type)) {
|
|
13358
|
-
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
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);
|
|
13366
13373
|
continue;
|
|
13367
13374
|
}
|
|
13368
|
-
visit(child, depth);
|
|
13375
|
+
visit(child, depth, includeDefinitions);
|
|
13369
13376
|
}
|
|
13370
13377
|
};
|
|
13371
|
-
visit(root, 0);
|
|
13378
|
+
visit(root, 0, true);
|
|
13372
13379
|
return out;
|
|
13373
13380
|
}
|
|
13374
13381
|
/**
|
|
@@ -13512,10 +13519,10 @@ function confirmDefinitionSites(tree, source, language, hits, signal) {
|
|
|
13512
13519
|
return confirmed;
|
|
13513
13520
|
}
|
|
13514
13521
|
/**
|
|
13515
|
-
*
|
|
13516
|
-
*
|
|
13517
|
-
*
|
|
13518
|
-
*
|
|
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
|
|
13519
13526
|
* shared grammar bundle and the same `Parser` the structural pass uses
|
|
13520
13527
|
* — no second `Parser.init()`.
|
|
13521
13528
|
*
|
|
@@ -13680,6 +13687,14 @@ var TreeSitterPool = class {
|
|
|
13680
13687
|
spawned: this.workersSpawned
|
|
13681
13688
|
};
|
|
13682
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
|
+
}
|
|
13683
13698
|
queue = [];
|
|
13684
13699
|
inflight = /* @__PURE__ */ new Map();
|
|
13685
13700
|
constructor() {
|
|
@@ -13695,18 +13710,19 @@ var TreeSitterPool = class {
|
|
|
13695
13710
|
* callers onto one in-flight ensure so 8 simultaneous searches don't each
|
|
13696
13711
|
* spawn a fresh batch. Returns the live worker count (0 → caller must fall
|
|
13697
13712
|
* back to the in-process path). */
|
|
13698
|
-
ensureWorkers() {
|
|
13713
|
+
ensureWorkers(target = this.size) {
|
|
13699
13714
|
if (this.unavailable || this.shuttingDown) return Promise.resolve(0);
|
|
13715
|
+
const desired = Math.max(1, Math.min(target, this.size));
|
|
13700
13716
|
const liveNow = this.workers.filter((w) => w.ready && w.loaded.size > 0).length;
|
|
13701
|
-
if (liveNow >=
|
|
13702
|
-
if (this.ensuring) return this.ensuring;
|
|
13703
|
-
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(() => {
|
|
13704
13720
|
this.ensuring = null;
|
|
13705
13721
|
});
|
|
13706
|
-
return this.ensuring;
|
|
13722
|
+
return liveNow > 0 ? Promise.resolve(liveNow) : this.ensuring;
|
|
13707
13723
|
}
|
|
13708
|
-
async doEnsureWorkers() {
|
|
13709
|
-
const need =
|
|
13724
|
+
async doEnsureWorkers(target) {
|
|
13725
|
+
const need = target - this.workers.length;
|
|
13710
13726
|
const spawns = [];
|
|
13711
13727
|
for (let i = 0; i < need; i++) spawns.push(this.spawnWorker());
|
|
13712
13728
|
const spawned = await Promise.all(spawns);
|
|
@@ -13927,13 +13943,23 @@ var TreeSitterPool = class {
|
|
|
13927
13943
|
budgetHit: false
|
|
13928
13944
|
};
|
|
13929
13945
|
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
13930
|
-
|
|
13931
|
-
budgetHit = true;
|
|
13932
|
-
stop();
|
|
13933
|
-
}, opts.budgetMs);
|
|
13934
|
-
budgetTimer.unref?.();
|
|
13946
|
+
let budgetTimer;
|
|
13935
13947
|
try {
|
|
13936
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
|
+
}
|
|
13937
13963
|
const dispatchFile = async (job, retried) => {
|
|
13938
13964
|
if (stopped) return;
|
|
13939
13965
|
const id = this.nextJobId++;
|
|
@@ -13964,7 +13990,7 @@ var TreeSitterPool = class {
|
|
|
13964
13990
|
};
|
|
13965
13991
|
await Promise.all(jobs.map((job) => dispatchFile(job, false)));
|
|
13966
13992
|
} finally {
|
|
13967
|
-
clearTimeout(budgetTimer);
|
|
13993
|
+
if (budgetTimer) clearTimeout(budgetTimer);
|
|
13968
13994
|
opts.signal.removeEventListener("abort", onAbort);
|
|
13969
13995
|
}
|
|
13970
13996
|
const stillLive = this.workers.some((w) => w.ready && w.loaded.size > 0);
|
|
@@ -14010,6 +14036,7 @@ function resolveWorkerPath() {
|
|
|
14010
14036
|
let _pool = null;
|
|
14011
14037
|
let _shutdownRegistered = false;
|
|
14012
14038
|
let _testCrashOnceArmed = false;
|
|
14039
|
+
let _warmOverride;
|
|
14013
14040
|
/**
|
|
14014
14041
|
* The pool is ON by default for real (non-CI) runs and OFF under CI.
|
|
14015
14042
|
*
|
|
@@ -14055,6 +14082,24 @@ function getTreeSitterPool() {
|
|
|
14055
14082
|
}
|
|
14056
14083
|
return _pool;
|
|
14057
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
|
+
}
|
|
14058
14103
|
|
|
14059
14104
|
//#endregion
|
|
14060
14105
|
//#region src/lib/worker-agent/paths.ts
|
|
@@ -14257,9 +14302,9 @@ const WALL_TIME_MS = 3e4;
|
|
|
14257
14302
|
* Structural-pass settings. The wall-clock budget is checked between
|
|
14258
14303
|
* files (NOT mid-parse — tree-sitter doesn't surface a usable cancel
|
|
14259
14304
|
* hook in the web-tree-sitter binding we're on), so a single
|
|
14260
|
-
* pathological file can overrun by one file's parse-time.
|
|
14261
|
-
*
|
|
14262
|
-
*
|
|
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.
|
|
14263
14308
|
*/
|
|
14264
14309
|
const STRUCTURAL_BUDGET_MS = 200;
|
|
14265
14310
|
let _structuralBudgetTestOverride = null;
|
|
@@ -14854,7 +14899,7 @@ async function runStructuralPassPooled(opts) {
|
|
|
14854
14899
|
}
|
|
14855
14900
|
return {
|
|
14856
14901
|
confirmedHitIndexes,
|
|
14857
|
-
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,
|
|
14858
14903
|
outlinesByFile
|
|
14859
14904
|
};
|
|
14860
14905
|
}
|
|
@@ -15184,26 +15229,23 @@ function dropAstGrepSecrets(env) {
|
|
|
15184
15229
|
return env;
|
|
15185
15230
|
}
|
|
15186
15231
|
/**
|
|
15187
|
-
* Resolve the ast-grep binary. Checks the
|
|
15188
|
-
*
|
|
15189
|
-
*
|
|
15190
|
-
* is found. `resolveExecutable` honors PATHEXT on Windows and excludes
|
|
15191
|
-
* the cwd (no planted-`sg.exe` vector). The toolbelt dir is searched by
|
|
15192
|
-
* 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.
|
|
15193
15235
|
*/
|
|
15194
15236
|
function resolveAstGrep() {
|
|
15195
15237
|
const toolbeltDir = PATHS.TOOLBELT_BIN_DIR;
|
|
15196
|
-
const
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15200
|
-
|
|
15201
|
-
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
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 });
|
|
15207
15249
|
}
|
|
15208
15250
|
/**
|
|
15209
15251
|
* Test-only override for the ast-grep resolver. `undefined` = use the real
|
|
@@ -15217,11 +15259,6 @@ let _astGrepResolverOverride;
|
|
|
15217
15259
|
function resolveAstGrepForRun() {
|
|
15218
15260
|
return (_astGrepResolverOverride ?? resolveAstGrep)();
|
|
15219
15261
|
}
|
|
15220
|
-
/** Read PATH case-insensitively from the live env. */
|
|
15221
|
-
function pathEnvValue() {
|
|
15222
|
-
for (const key of Object.keys(process.env)) if (key.toLowerCase() === "path") return process.env[key] ?? "";
|
|
15223
|
-
return "";
|
|
15224
|
-
}
|
|
15225
15262
|
/**
|
|
15226
15263
|
* Run ast-grep with `pattern` over `workspaceCanonical` and return its
|
|
15227
15264
|
* matches in `RawHit` shape (relativized, 1-indexed). Read-only,
|
|
@@ -15243,7 +15280,7 @@ async function runAstGrep(opts) {
|
|
|
15243
15280
|
const binary = resolveAstGrepForRun();
|
|
15244
15281
|
if (!binary) return {
|
|
15245
15282
|
hits: [],
|
|
15246
|
-
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`
|
|
15247
15284
|
};
|
|
15248
15285
|
if (!opts.lang || !/^[A-Za-z0-9_+-]{1,20}$/.test(opts.lang)) return {
|
|
15249
15286
|
hits: [],
|
|
@@ -15824,318 +15861,6 @@ function modelDirName() {
|
|
|
15824
15861
|
/** Short model id used in the sidecar metadata `model` field. */
|
|
15825
15862
|
const MODEL_ID = "LateOn-Code-edge";
|
|
15826
15863
|
|
|
15827
|
-
//#endregion
|
|
15828
|
-
//#region src/lib/colbert/index-store.ts
|
|
15829
|
-
const GIT_TIMEOUT_MS = 4e3;
|
|
15830
|
-
/** Grace window after a `building` write before a workspace with no live
|
|
15831
|
-
* build PID is declared `crashed` — covers the cross-process window where
|
|
15832
|
-
* one proxy wrote `building` but hasn't yet recorded the colgrep child PID. */
|
|
15833
|
-
const BUILD_SPAWN_GRACE_MS = 3e4;
|
|
15834
|
-
/**
|
|
15835
|
-
* Hash a workspace path the same way the metadata sidecar is keyed.
|
|
15836
|
-
* NOTE: this is the ROUTER-OWNED meta key, independent of colgrep's
|
|
15837
|
-
* internal xxh3 physical-dir key (we never need to predict colgrep's
|
|
15838
|
-
* key because we pass the workspace as colgrep's PATH arg and let it
|
|
15839
|
-
* route). A stable sha256-prefix of the canonical path is sufficient.
|
|
15840
|
-
*/
|
|
15841
|
-
function metaHashForWorkspace(workspace) {
|
|
15842
|
-
const canonical = process$1.platform === "win32" ? nodePath.resolve(workspace).toLowerCase().replace(/\\/g, "/") : nodePath.resolve(workspace);
|
|
15843
|
-
let h = 2166136261;
|
|
15844
|
-
for (let i = 0; i < canonical.length; i++) {
|
|
15845
|
-
h ^= canonical.charCodeAt(i);
|
|
15846
|
-
h = Math.imul(h, 16777619);
|
|
15847
|
-
}
|
|
15848
|
-
return (h >>> 0).toString(16).padStart(8, "0");
|
|
15849
|
-
}
|
|
15850
|
-
function metaPath(workspace) {
|
|
15851
|
-
return nodePath.join(PATHS.COLBERT_META_DIR, `${metaHashForWorkspace(workspace)}.json`);
|
|
15852
|
-
}
|
|
15853
|
-
/** Read the sidecar metadata for a workspace (null if none yet). */
|
|
15854
|
-
async function readColbertMeta(workspace) {
|
|
15855
|
-
try {
|
|
15856
|
-
const raw = await fs.readFile(metaPath(workspace), "utf8");
|
|
15857
|
-
const parsed = JSON.parse(raw);
|
|
15858
|
-
if (parsed && typeof parsed === "object" && typeof parsed.status === "string") return parsed;
|
|
15859
|
-
return null;
|
|
15860
|
-
} catch {
|
|
15861
|
-
return null;
|
|
15862
|
-
}
|
|
15863
|
-
}
|
|
15864
|
-
/**
|
|
15865
|
-
* Per-workspace write serializer. `runInit` issues a pre-spawn
|
|
15866
|
-
* `building` write, an `onSpawn` write that patches in the colgrep child
|
|
15867
|
-
* PID, and a final `ready`/`failed` write. Chaining them per workspace
|
|
15868
|
-
* guarantees the final write lands AFTER the (fire-and-forget) onSpawn
|
|
15869
|
-
* write, so a `ready` result is never clobbered back to `building` by a
|
|
15870
|
-
* late atomic-rename.
|
|
15871
|
-
*/
|
|
15872
|
-
const _metaWriteChains = /* @__PURE__ */ new Map();
|
|
15873
|
-
/** Atomically write the sidecar metadata for a workspace (serialized). */
|
|
15874
|
-
async function writeColbertMeta(meta) {
|
|
15875
|
-
const key = metaHashForWorkspace(meta.workspace);
|
|
15876
|
-
const next = (_metaWriteChains.get(key) ?? Promise.resolve()).then(() => writeColbertMetaUnchained(meta));
|
|
15877
|
-
_metaWriteChains.set(key, next.then(() => void 0, () => void 0));
|
|
15878
|
-
return next;
|
|
15879
|
-
}
|
|
15880
|
-
async function writeColbertMetaUnchained(meta) {
|
|
15881
|
-
await fs.mkdir(PATHS.COLBERT_META_DIR, { recursive: true });
|
|
15882
|
-
const dest = metaPath(meta.workspace);
|
|
15883
|
-
const tmp = `${dest}.${process$1.pid}.${Math.random().toString(16).slice(2, 10)}.tmp`;
|
|
15884
|
-
try {
|
|
15885
|
-
await fs.writeFile(tmp, JSON.stringify(meta, null, 2));
|
|
15886
|
-
await fs.rename(tmp, dest);
|
|
15887
|
-
} catch (err) {
|
|
15888
|
-
await fs.rm(tmp, { force: true }).catch(() => {});
|
|
15889
|
-
throw err;
|
|
15890
|
-
}
|
|
15891
|
-
}
|
|
15892
|
-
/**
|
|
15893
|
-
* Whether a COMPLETED colgrep index exists on disk for this workspace.
|
|
15894
|
-
* The preflight uses this to distinguish `building`/`absent` (no
|
|
15895
|
-
* completed index → don't spawn a foreground colgrep) from a real
|
|
15896
|
-
* index. We scan COLGREP_DATA_DIR for any per-project dir containing a
|
|
15897
|
-
* `project.json` whose canonical path matches this workspace AND an
|
|
15898
|
-
* `index/metadata.json` marker.
|
|
15899
|
-
*/
|
|
15900
|
-
async function completedIndexOnDisk(workspace) {
|
|
15901
|
-
const indicesDir = PATHS.COLBERT_INDICES_DIR;
|
|
15902
|
-
let names;
|
|
15903
|
-
try {
|
|
15904
|
-
names = await fs.readdir(indicesDir);
|
|
15905
|
-
} catch {
|
|
15906
|
-
return false;
|
|
15907
|
-
}
|
|
15908
|
-
const wantCanonical = await realpathForCompare(workspace);
|
|
15909
|
-
for (const name of names) {
|
|
15910
|
-
if (name === ".gh-router-meta") continue;
|
|
15911
|
-
const projJson = nodePath.join(indicesDir, name, "project.json");
|
|
15912
|
-
let proj;
|
|
15913
|
-
try {
|
|
15914
|
-
proj = JSON.parse(await fs.readFile(projJson, "utf8"));
|
|
15915
|
-
} catch {
|
|
15916
|
-
continue;
|
|
15917
|
-
}
|
|
15918
|
-
const projPath = proj.path ?? proj.project_path;
|
|
15919
|
-
if (!projPath) continue;
|
|
15920
|
-
if (await realpathForCompare(projPath) !== wantCanonical) continue;
|
|
15921
|
-
if (existsSync(nodePath.join(indicesDir, name, "index", "metadata.json"))) return true;
|
|
15922
|
-
if (existsSync(nodePath.join(indicesDir, name, "index"))) try {
|
|
15923
|
-
if ((await fs.readdir(nodePath.join(indicesDir, name, "index"))).length > 0) return true;
|
|
15924
|
-
} catch {}
|
|
15925
|
-
}
|
|
15926
|
-
return false;
|
|
15927
|
-
}
|
|
15928
|
-
function canonicalForCompare(p) {
|
|
15929
|
-
return process$1.platform === "win32" ? nodePath.resolve(p).toLowerCase().replace(/\\/g, "/") : nodePath.resolve(p);
|
|
15930
|
-
}
|
|
15931
|
-
/** Sync realpath-aware canonicalization (sibling of `realpathForCompare`,
|
|
15932
|
-
* for the on-a-timer inactivity probe which must be synchronous). */
|
|
15933
|
-
function canonicalRealpathSync(p) {
|
|
15934
|
-
try {
|
|
15935
|
-
return canonicalForCompare(realpathSync(p));
|
|
15936
|
-
} catch {
|
|
15937
|
-
return canonicalForCompare(p);
|
|
15938
|
-
}
|
|
15939
|
-
}
|
|
15940
|
-
/** Recursive (bytes, fileCount) of a directory; sync + best-effort. A
|
|
15941
|
-
* colgrep index is a bounded set of shards so the walk stays small. */
|
|
15942
|
-
function dirSizeSync(dir) {
|
|
15943
|
-
let bytes = 0;
|
|
15944
|
-
let count = 0;
|
|
15945
|
-
let entries;
|
|
15946
|
-
try {
|
|
15947
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
15948
|
-
} catch {
|
|
15949
|
-
return [0, 0];
|
|
15950
|
-
}
|
|
15951
|
-
for (const e of entries) {
|
|
15952
|
-
const p = nodePath.join(dir, e.name);
|
|
15953
|
-
if (e.isDirectory()) {
|
|
15954
|
-
const [b, c] = dirSizeSync(p);
|
|
15955
|
-
bytes += b;
|
|
15956
|
-
count += c;
|
|
15957
|
-
} else try {
|
|
15958
|
-
bytes += statSync(p).size;
|
|
15959
|
-
count += 1;
|
|
15960
|
-
} catch {}
|
|
15961
|
-
}
|
|
15962
|
-
return [bytes, count];
|
|
15963
|
-
}
|
|
15964
|
-
/**
|
|
15965
|
-
* (sync) Progress signature of a workspace's colgrep index dir for the init
|
|
15966
|
-
* inactivity watchdog: `${totalBytes}:${fileCount}` of the project dir, or
|
|
15967
|
-
* `null` if it isn't on disk yet. colgrep is SILENT on a non-TTY pipe
|
|
15968
|
-
* during the (potentially multi-hour) encode phase, so output is useless as
|
|
15969
|
-
* a progress signal — but it writes index shards incrementally, so a
|
|
15970
|
-
* changing signature means "still progressing" and a frozen one means
|
|
15971
|
-
* "hung". Successive signatures drive the watchdog: change ⇒ re-arm, frozen
|
|
15972
|
-
* ⇒ kill. Sync because it's called from a `setTimeout` (not awaited).
|
|
15973
|
-
*/
|
|
15974
|
-
function indexDirSignature(workspace) {
|
|
15975
|
-
const indicesDir = PATHS.COLBERT_INDICES_DIR;
|
|
15976
|
-
let names;
|
|
15977
|
-
try {
|
|
15978
|
-
names = readdirSync(indicesDir);
|
|
15979
|
-
} catch {
|
|
15980
|
-
return null;
|
|
15981
|
-
}
|
|
15982
|
-
const want = canonicalRealpathSync(workspace);
|
|
15983
|
-
for (const name of names) {
|
|
15984
|
-
if (name === ".gh-router-meta") continue;
|
|
15985
|
-
const dir = nodePath.join(indicesDir, name);
|
|
15986
|
-
let proj;
|
|
15987
|
-
try {
|
|
15988
|
-
proj = JSON.parse(readFileSync(nodePath.join(dir, "project.json"), "utf8"));
|
|
15989
|
-
} catch {
|
|
15990
|
-
continue;
|
|
15991
|
-
}
|
|
15992
|
-
const projPath = proj.path ?? proj.project_path;
|
|
15993
|
-
if (!projPath || canonicalRealpathSync(projPath) !== want) continue;
|
|
15994
|
-
const [bytes, count] = dirSizeSync(dir);
|
|
15995
|
-
return `${bytes}:${count}`;
|
|
15996
|
-
}
|
|
15997
|
-
return null;
|
|
15998
|
-
}
|
|
15999
|
-
/**
|
|
16000
|
-
* Realpath-aware canonicalization for matching a workspace against
|
|
16001
|
-
* colgrep's stored `project_path`. colgrep stores the OS realpath (e.g.
|
|
16002
|
-
* macOS `/tmp` → `/private/tmp`, Windows 8.3 short names), so a plain
|
|
16003
|
-
* `path.resolve` comparison misses. Falls back to `canonicalForCompare`
|
|
16004
|
-
* when realpath fails (path doesn't exist yet).
|
|
16005
|
-
*/
|
|
16006
|
-
async function realpathForCompare(p) {
|
|
16007
|
-
try {
|
|
16008
|
-
return canonicalForCompare(await fs.realpath(p));
|
|
16009
|
-
} catch {
|
|
16010
|
-
return canonicalForCompare(p);
|
|
16011
|
-
}
|
|
16012
|
-
}
|
|
16013
|
-
/**
|
|
16014
|
-
* Compute the freshness verdict for a query against a workspace.
|
|
16015
|
-
*
|
|
16016
|
-
* Routing (per the dropped-fallback contract):
|
|
16017
|
-
* - `failed` — sidecar says failed → caller returns isError.
|
|
16018
|
-
* - `building` — a tracked init is live OR no completed index on disk
|
|
16019
|
-
* → caller returns building notice (NO results).
|
|
16020
|
-
* - `absent` — never indexed → caller kicks a debounced background
|
|
16021
|
-
* init, returns absent (isError).
|
|
16022
|
-
* - `stale` — ready but HEAD moved / tree dirty since index → caller
|
|
16023
|
-
* returns stale notice (NO results, NO re-search).
|
|
16024
|
-
* - `fresh` — ready + completed index + HEAD matches + not newly
|
|
16025
|
-
* dirty → caller spawns colgrep search.
|
|
16026
|
-
*/
|
|
16027
|
-
async function freshnessVerdict(workspace) {
|
|
16028
|
-
const meta = await readColbertMeta(workspace);
|
|
16029
|
-
if (!meta || meta.status === "absent") return {
|
|
16030
|
-
verdict: "absent",
|
|
16031
|
-
meta
|
|
16032
|
-
};
|
|
16033
|
-
if (meta.status === "failed") return {
|
|
16034
|
-
verdict: "failed",
|
|
16035
|
-
meta
|
|
16036
|
-
};
|
|
16037
|
-
if (meta.status === "building") {
|
|
16038
|
-
const pid = typeof meta.buildPid === "number" ? meta.buildPid : 0;
|
|
16039
|
-
if (isInitInFlight(workspace) || pid > 0 && isPidAlive(pid)) return {
|
|
16040
|
-
verdict: "building",
|
|
16041
|
-
meta
|
|
16042
|
-
};
|
|
16043
|
-
const startedMs = meta.lastIndexedAt ? Date.parse(meta.lastIndexedAt) : NaN;
|
|
16044
|
-
if (Number.isFinite(startedMs) && Date.now() - startedMs < BUILD_SPAWN_GRACE_MS) return {
|
|
16045
|
-
verdict: "building",
|
|
16046
|
-
meta
|
|
16047
|
-
};
|
|
16048
|
-
if (!await completedIndexOnDisk(workspace)) return {
|
|
16049
|
-
verdict: "crashed",
|
|
16050
|
-
meta
|
|
16051
|
-
};
|
|
16052
|
-
}
|
|
16053
|
-
if (!await completedIndexOnDisk(workspace)) return {
|
|
16054
|
-
verdict: "building",
|
|
16055
|
-
meta
|
|
16056
|
-
};
|
|
16057
|
-
const git = await gitState(workspace);
|
|
16058
|
-
if (!git.isRepo) return {
|
|
16059
|
-
verdict: "fresh",
|
|
16060
|
-
meta
|
|
16061
|
-
};
|
|
16062
|
-
const headMoved = meta.lastIndexedHead !== void 0 && git.head !== meta.lastIndexedHead;
|
|
16063
|
-
const newlyDirty = git.dirty && meta.lastIndexedDirty !== true;
|
|
16064
|
-
if (headMoved || newlyDirty) return {
|
|
16065
|
-
verdict: "stale",
|
|
16066
|
-
meta,
|
|
16067
|
-
head: git.head,
|
|
16068
|
-
dirty: git.dirty
|
|
16069
|
-
};
|
|
16070
|
-
return {
|
|
16071
|
-
verdict: "fresh",
|
|
16072
|
-
meta,
|
|
16073
|
-
head: git.head,
|
|
16074
|
-
dirty: git.dirty
|
|
16075
|
-
};
|
|
16076
|
-
}
|
|
16077
|
-
/** Cheap, bounded git probe via the native-exe runner. */
|
|
16078
|
-
async function gitState(workspace) {
|
|
16079
|
-
const git = resolveExecutable("git");
|
|
16080
|
-
if (!git) return { isRepo: false };
|
|
16081
|
-
try {
|
|
16082
|
-
const inside = await runManagedExeCapture(git, [
|
|
16083
|
-
"-C",
|
|
16084
|
-
workspace,
|
|
16085
|
-
"rev-parse",
|
|
16086
|
-
"--is-inside-work-tree"
|
|
16087
|
-
], {
|
|
16088
|
-
timeoutMs: GIT_TIMEOUT_MS,
|
|
16089
|
-
maxStdoutBytes: 64 * 1024
|
|
16090
|
-
});
|
|
16091
|
-
if (inside.code !== 0 || inside.stdout.trim() !== "true") return { isRepo: false };
|
|
16092
|
-
const head = await runManagedExeCapture(git, [
|
|
16093
|
-
"-C",
|
|
16094
|
-
workspace,
|
|
16095
|
-
"rev-parse",
|
|
16096
|
-
"HEAD"
|
|
16097
|
-
], {
|
|
16098
|
-
timeoutMs: GIT_TIMEOUT_MS,
|
|
16099
|
-
maxStdoutBytes: 64 * 1024
|
|
16100
|
-
});
|
|
16101
|
-
const status = await runManagedExeCapture(git, [
|
|
16102
|
-
"-C",
|
|
16103
|
-
workspace,
|
|
16104
|
-
"status",
|
|
16105
|
-
"--porcelain"
|
|
16106
|
-
], {
|
|
16107
|
-
timeoutMs: GIT_TIMEOUT_MS,
|
|
16108
|
-
maxStdoutBytes: 1024 * 1024
|
|
16109
|
-
});
|
|
16110
|
-
return {
|
|
16111
|
-
isRepo: true,
|
|
16112
|
-
head: head.code === 0 ? head.stdout.trim() || void 0 : void 0,
|
|
16113
|
-
dirty: status.code === 0 ? status.stdout.trim().length > 0 : void 0
|
|
16114
|
-
};
|
|
16115
|
-
} catch {
|
|
16116
|
-
return { isRepo: false };
|
|
16117
|
-
}
|
|
16118
|
-
}
|
|
16119
|
-
const _initInFlight = /* @__PURE__ */ new Set();
|
|
16120
|
-
/** True iff a background init for this workspace is already in flight. */
|
|
16121
|
-
function isInitInFlight(workspace) {
|
|
16122
|
-
return _initInFlight.has(initKey(workspace));
|
|
16123
|
-
}
|
|
16124
|
-
/** Mark a background init started (debounce). Returns false if already running. */
|
|
16125
|
-
function tryClaimInit(workspace) {
|
|
16126
|
-
const k = initKey(workspace);
|
|
16127
|
-
if (_initInFlight.has(k)) return false;
|
|
16128
|
-
_initInFlight.add(k);
|
|
16129
|
-
return true;
|
|
16130
|
-
}
|
|
16131
|
-
/** Release the debounce claim (call in the init's finally). */
|
|
16132
|
-
function releaseInit(workspace) {
|
|
16133
|
-
_initInFlight.delete(initKey(workspace));
|
|
16134
|
-
}
|
|
16135
|
-
function initKey(workspace) {
|
|
16136
|
-
return `${MODEL_ID}::${canonicalForCompare(workspace)}`;
|
|
16137
|
-
}
|
|
16138
|
-
|
|
16139
15864
|
//#endregion
|
|
16140
15865
|
//#region src/lib/toolbelt/extract.ts
|
|
16141
15866
|
function baseName(p) {
|
|
@@ -16340,6 +16065,14 @@ function colgrepBinaryPath() {
|
|
|
16340
16065
|
function colbertModelDir() {
|
|
16341
16066
|
return nodePath.join(PATHS.COLBERT_MODELS_DIR, "LateOn-Code-edge", modelDirName());
|
|
16342
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
|
+
}
|
|
16343
16076
|
/** Absolute path the provisioned ORT dylib lives at. */
|
|
16344
16077
|
function colbertOrtDylibPath() {
|
|
16345
16078
|
const lib = ortLibAsset()?.member ?? "libonnxruntime.so";
|
|
@@ -16462,7 +16195,7 @@ async function provisionColbert() {
|
|
|
16462
16195
|
result.status = "incomplete";
|
|
16463
16196
|
});
|
|
16464
16197
|
if (result.binaryPath && result.ortDylibPath && result.modelDir) {
|
|
16465
|
-
const smoke = await runSmokeTest(result.binaryPath, result.ortDylibPath
|
|
16198
|
+
const smoke = await runSmokeTest(result.binaryPath, result.ortDylibPath);
|
|
16466
16199
|
if (smoke.ok) {
|
|
16467
16200
|
await writeFile(smokeMarkerPath(), expectedSmokeMarker()).catch(() => {});
|
|
16468
16201
|
result.status = "ready";
|
|
@@ -16591,7 +16324,7 @@ async function sidecarMatches(sidecar, sha256) {
|
|
|
16591
16324
|
* scanning stderr for colgrep's exact "ignoring" warning — if present,
|
|
16592
16325
|
* the dylib didn't load and we fail the smoke test even on exit 0.
|
|
16593
16326
|
*/
|
|
16594
|
-
async function runSmokeTest(binaryPath, ortDylibPath
|
|
16327
|
+
async function runSmokeTest(binaryPath, ortDylibPath) {
|
|
16595
16328
|
const tmp = nodePath.join(PATHS.COLBERT_DIR, `smoke-${process$1.pid}-${randomBytes(4).toString("hex")}`);
|
|
16596
16329
|
const fixtureDir = nodePath.join(tmp, "fixture");
|
|
16597
16330
|
const dataDir = nodePath.join(tmp, "data");
|
|
@@ -16620,7 +16353,7 @@ async function runSmokeTest(binaryPath, ortDylibPath, modelDir) {
|
|
|
16620
16353
|
"never",
|
|
16621
16354
|
"--force-cpu",
|
|
16622
16355
|
"--model",
|
|
16623
|
-
|
|
16356
|
+
canonicalColbertModelDir(),
|
|
16624
16357
|
"-y",
|
|
16625
16358
|
"-k",
|
|
16626
16359
|
"1",
|
|
@@ -16658,6 +16391,395 @@ async function runSmokeTest(binaryPath, ortDylibPath, modelDir) {
|
|
|
16658
16391
|
}
|
|
16659
16392
|
}
|
|
16660
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
|
+
|
|
16661
16783
|
//#endregion
|
|
16662
16784
|
//#region src/lib/colbert/runner.ts
|
|
16663
16785
|
/** Caller responsiveness budget for a search. A warm search is sub-second;
|
|
@@ -16727,6 +16849,10 @@ function makeIndexProgressProbe(workspace) {
|
|
|
16727
16849
|
* colgrep writer per workspace" goal as the init debounce. Cleared when the
|
|
16728
16850
|
* detached search completes. */
|
|
16729
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;
|
|
16730
16856
|
/** Build the isolating env for any colgrep child (search or init). */
|
|
16731
16857
|
function colgrepEnv() {
|
|
16732
16858
|
const ortDir = nodePath.dirname(colbertOrtDylibPath());
|
|
@@ -16764,6 +16890,7 @@ async function runSemanticSearch(opts) {
|
|
|
16764
16890
|
};
|
|
16765
16891
|
case "failed": return handleFailure(workspace, fresh.meta, false);
|
|
16766
16892
|
case "crashed": return handleFailure(workspace, fresh.meta, true);
|
|
16893
|
+
case "corrupt": return repairCorruptIndex(workspace, fresh.meta);
|
|
16767
16894
|
case "building": return {
|
|
16768
16895
|
status: "building",
|
|
16769
16896
|
notice: "semantic index is being built for this workspace; retry shortly (or use code_search now)"
|
|
@@ -16796,6 +16923,64 @@ async function runSemanticSearch(opts) {
|
|
|
16796
16923
|
* by the inactivity watchdog) retries at most once — re-running a hung build
|
|
16797
16924
|
* usually hangs again; transient classes retry up to `MAX_FAILED_ATTEMPTS`.
|
|
16798
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
|
+
}
|
|
16799
16984
|
async function handleFailure(workspace, meta, crashedVerdict) {
|
|
16800
16985
|
const cls = crashedVerdict ? "crashed" : meta?.failureClass ?? "error";
|
|
16801
16986
|
const attempts = crashedVerdict ? (meta?.failedAttempts ?? 0) + 1 : meta?.failedAttempts ?? 1;
|
|
@@ -16812,7 +16997,7 @@ async function handleFailure(workspace, meta, crashedVerdict) {
|
|
|
16812
16997
|
lastIndexedDirty: meta?.lastIndexedDirty,
|
|
16813
16998
|
ownerInstanceId: getColbertInstanceUuid()
|
|
16814
16999
|
}).catch(() => {});
|
|
16815
|
-
const cap = cls === "stuck" ? 2 : MAX_FAILED_ATTEMPTS;
|
|
17000
|
+
const cap = cls === "stuck" || cls === "corrupt" ? 2 : MAX_FAILED_ATTEMPTS;
|
|
16816
17001
|
const lastMs = lastAt ? Date.parse(lastAt) : NaN;
|
|
16817
17002
|
const backoffElapsed = !Number.isFinite(lastMs) || Date.now() - lastMs >= FAILED_RETRY_BACKOFF_MS;
|
|
16818
17003
|
if (attempts < cap && backoffElapsed) {
|
|
@@ -16855,7 +17040,7 @@ async function spawnSearch(opts) {
|
|
|
16855
17040
|
"never",
|
|
16856
17041
|
"--force-cpu",
|
|
16857
17042
|
"--model",
|
|
16858
|
-
|
|
17043
|
+
canonicalColbertModelDir(),
|
|
16859
17044
|
"-y",
|
|
16860
17045
|
"-k",
|
|
16861
17046
|
String(opts.limit)
|
|
@@ -16870,7 +17055,7 @@ async function spawnSearch(opts) {
|
|
|
16870
17055
|
_searchIndexInFlight.add(wsKey);
|
|
16871
17056
|
let searchPromise;
|
|
16872
17057
|
try {
|
|
16873
|
-
searchPromise =
|
|
17058
|
+
searchPromise = _runManagedExeCapture(binary, args, {
|
|
16874
17059
|
env: colgrepEnv(),
|
|
16875
17060
|
inactivityTimeoutMs: INIT_STALL_MS,
|
|
16876
17061
|
onInactivityCheck: makeIndexProgressProbe(opts.workspace),
|
|
@@ -16986,14 +17171,24 @@ function buildSnippet(unit) {
|
|
|
16986
17171
|
const sig = typeof unit.signature === "string" ? unit.signature.trim() : "";
|
|
16987
17172
|
const code = typeof unit.code === "string" ? unit.code : "";
|
|
16988
17173
|
if (!code) return sig;
|
|
16989
|
-
const
|
|
16990
|
-
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;
|
|
16991
17178
|
return snippet.length > 600 ? snippet.slice(0, 600) + "…" : snippet;
|
|
16992
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
|
+
}
|
|
16993
17185
|
function relativize(file, workspace, workspaceReal) {
|
|
16994
|
-
|
|
16995
|
-
|
|
16996
|
-
|
|
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;
|
|
16997
17192
|
} catch {}
|
|
16998
17193
|
return file;
|
|
16999
17194
|
}
|
|
@@ -17020,9 +17215,31 @@ function clampLimit(limit) {
|
|
|
17020
17215
|
function kickBackgroundInit(workspace) {
|
|
17021
17216
|
if (isInitInFlight(workspace)) return;
|
|
17022
17217
|
if (!tryClaimInit(workspace)) return;
|
|
17023
|
-
|
|
17024
|
-
|
|
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);
|
|
17025
17241
|
});
|
|
17242
|
+
_initPromises.set(key, promise);
|
|
17026
17243
|
}
|
|
17027
17244
|
/**
|
|
17028
17245
|
* Whether the STARTUP auto-kick should fire for a workspace. Skips a build
|
|
@@ -17035,25 +17252,72 @@ function kickBackgroundInit(workspace) {
|
|
|
17035
17252
|
async function startupKickAllowed(workspace) {
|
|
17036
17253
|
const meta = await readColbertMeta(workspace);
|
|
17037
17254
|
if (!meta || meta.status !== "failed") return true;
|
|
17038
|
-
|
|
17255
|
+
const cap = meta.failureClass === "stuck" || meta.failureClass === "corrupt" ? 2 : MAX_FAILED_ATTEMPTS;
|
|
17256
|
+
if ((meta.failedAttempts ?? 0) >= cap) return false;
|
|
17039
17257
|
if (meta.failureClass === "stuck") return false;
|
|
17040
17258
|
return true;
|
|
17041
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
|
+
}
|
|
17042
17309
|
async function runInit(workspace) {
|
|
17043
17310
|
const binary = colgrepBinaryPath();
|
|
17044
|
-
if (!existsSync(binary))
|
|
17045
|
-
|
|
17046
|
-
|
|
17047
|
-
}
|
|
17048
|
-
if (!existsSync(colbertOrtDylibPath())) {
|
|
17049
|
-
releaseInit(workspace);
|
|
17050
|
-
return;
|
|
17051
|
-
}
|
|
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);
|
|
17052
17314
|
const prior = await readColbertMeta(workspace);
|
|
17053
17315
|
const baseMeta = {
|
|
17054
17316
|
workspace,
|
|
17055
17317
|
model: MODEL_ID,
|
|
17056
17318
|
modelRev: MODEL_REVISION,
|
|
17319
|
+
binarySha: colgrepBinAsset()?.sha256,
|
|
17320
|
+
ortSha: ortLibAsset()?.sha256,
|
|
17057
17321
|
status: "building",
|
|
17058
17322
|
buildPid: void 0,
|
|
17059
17323
|
ownerInstanceId: getColbertInstanceUuid(),
|
|
@@ -17075,7 +17339,7 @@ async function runInit(workspace) {
|
|
|
17075
17339
|
"never",
|
|
17076
17340
|
"--force-cpu",
|
|
17077
17341
|
"--model",
|
|
17078
|
-
|
|
17342
|
+
canonicalColbertModelDir(),
|
|
17079
17343
|
workspace
|
|
17080
17344
|
];
|
|
17081
17345
|
const onInactivityCheck = makeIndexProgressProbe(workspace);
|
|
@@ -17083,7 +17347,7 @@ async function runInit(workspace) {
|
|
|
17083
17347
|
let ok$3 = false;
|
|
17084
17348
|
let failureClass;
|
|
17085
17349
|
try {
|
|
17086
|
-
const res = await
|
|
17350
|
+
const res = await _runManagedExeCapture(binary, args, {
|
|
17087
17351
|
env: colgrepEnv(),
|
|
17088
17352
|
timeoutMs: INIT_TIMEOUT_MS,
|
|
17089
17353
|
inactivityTimeoutMs: INIT_STALL_MS,
|
|
@@ -17098,10 +17362,18 @@ async function runInit(workspace) {
|
|
|
17098
17362
|
}
|
|
17099
17363
|
});
|
|
17100
17364
|
ok$3 = !res.stalled && !res.timedOut && res.code === 0;
|
|
17101
|
-
if (
|
|
17102
|
-
|
|
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) {
|
|
17103
17374
|
ok$3 = false;
|
|
17104
17375
|
failureClass = "launch";
|
|
17376
|
+
consola.error("colbert: init failed to launch:", err);
|
|
17105
17377
|
} finally {
|
|
17106
17378
|
releaseInit(workspace);
|
|
17107
17379
|
}
|
|
@@ -17204,8 +17476,10 @@ function lexicalSearchCodeMode(mode) {
|
|
|
17204
17476
|
* it both levers: retry `mode:"semantic"` shortly (the index is self-healing
|
|
17205
17477
|
* in the background) OR re-query now with specific symbol/keyword terms.
|
|
17206
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`;
|
|
17207
17481
|
function fallbackNoticeFor(status) {
|
|
17208
|
-
const tail =
|
|
17482
|
+
const tail = FALLBACK_GUIDANCE;
|
|
17209
17483
|
switch (status) {
|
|
17210
17484
|
case "building": return `semantic index is building; returned lexical keyword matches — ${tail}`;
|
|
17211
17485
|
case "stale": return `semantic index predates the current HEAD/tree (a background re-index was started); returned lexical keyword matches — ${tail}`;
|
|
@@ -17224,6 +17498,38 @@ function joinNotice(primary, secondary) {
|
|
|
17224
17498
|
if (primary && secondary) return `${primary} (${secondary})`;
|
|
17225
17499
|
return primary || secondary || void 0;
|
|
17226
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
|
+
}
|
|
17227
17533
|
async function runLexical(input, mode, source, signal) {
|
|
17228
17534
|
const isAst = mode === "ast";
|
|
17229
17535
|
const resp = await searchCode({
|
|
@@ -17285,22 +17591,26 @@ async function runUnifiedCodeSearch(input, signal) {
|
|
|
17285
17591
|
notice: joinNotice(r$1.notice, "semantic search errored; returned lexical results")
|
|
17286
17592
|
};
|
|
17287
17593
|
}
|
|
17288
|
-
if (sem.status === "ready")
|
|
17289
|
-
|
|
17290
|
-
results: (sem.results ?? []).map((r$1) => ({
|
|
17594
|
+
if (sem.status === "ready") {
|
|
17595
|
+
const results = (sem.results ?? []).map((r$1) => ({
|
|
17291
17596
|
file: r$1.file,
|
|
17292
17597
|
line: r$1.line,
|
|
17293
17598
|
snippet: r$1.snippet,
|
|
17294
17599
|
...r$1.endLine !== void 0 ? { endLine: r$1.endLine } : {},
|
|
17295
17600
|
...r$1.name !== void 0 ? { name: r$1.name } : {},
|
|
17296
17601
|
...r$1.score !== void 0 ? { score: r$1.score } : {}
|
|
17297
|
-
}))
|
|
17298
|
-
|
|
17299
|
-
|
|
17602
|
+
}));
|
|
17603
|
+
return {
|
|
17604
|
+
source: "semantic",
|
|
17605
|
+
results,
|
|
17606
|
+
outlines: await outlinesForSemanticResults(input, results, signal),
|
|
17607
|
+
...sem.notice ? { notice: sem.notice } : {}
|
|
17608
|
+
};
|
|
17609
|
+
}
|
|
17300
17610
|
const r = await runLexical(input, "lexical", "lexical-fallback", signal);
|
|
17301
17611
|
return {
|
|
17302
17612
|
...r,
|
|
17303
|
-
notice: joinNotice(r.notice,
|
|
17613
|
+
notice: joinNotice(r.notice, semanticFallbackNotice(sem))
|
|
17304
17614
|
};
|
|
17305
17615
|
}
|
|
17306
17616
|
|
|
@@ -18498,7 +18808,7 @@ function logAudit$1(record) {
|
|
|
18498
18808
|
try {
|
|
18499
18809
|
const fs$2 = await import("node:fs/promises");
|
|
18500
18810
|
const path$1 = await import("node:path");
|
|
18501
|
-
const { PATHS: PATHS$1 } = await import("./paths-
|
|
18811
|
+
const { PATHS: PATHS$1 } = await import("./paths-D20MaHeo.js");
|
|
18502
18812
|
const dir = path$1.join(PATHS$1.APP_DIR, "browser-mcp");
|
|
18503
18813
|
await fs$2.mkdir(dir, { recursive: true });
|
|
18504
18814
|
const line = JSON.stringify({
|
|
@@ -25311,7 +25621,14 @@ async function countTokens(body, extraHeaders, callerSignal, retryTransient = fa
|
|
|
25311
25621
|
*/
|
|
25312
25622
|
/** Preference-ordered OpenAI frontier reasoning models (SELECTION list). */
|
|
25313
25623
|
const OPENAI_FRONTIER_MODELS = ["gpt-5.6-sol", "gpt-5.5"];
|
|
25314
|
-
/** Models whose shim
|
|
25624
|
+
/** Models whose shim reasoning effort becomes xhigh when the operator opts in
|
|
25625
|
+
* with `GH_ROUTER_FRONTIER_XHIGH_DEFAULT=1` (effort POLICY set).
|
|
25626
|
+
*
|
|
25627
|
+
* This is opt-IN, not the default. The shim maps a client's level to the
|
|
25628
|
+
* identical provider level and injects only `high` when the client sends no
|
|
25629
|
+
* `thinking` block at all; forcing xhigh here would silently override the level
|
|
25630
|
+
* the user chose. The set is retained so the opt-in restores the previous
|
|
25631
|
+
* behavior exactly, targeting the same models it used to. */
|
|
25315
25632
|
const XHIGH_DEFAULT_SHIM_MODELS = ["gpt-5.6-sol", "gpt-5.5"];
|
|
25316
25633
|
/** Normalize a model id for policy comparison: strip a leading `vendor/`
|
|
25317
25634
|
* prefix and any trailing `[...]` decoration(s) (e.g. `[1m]`, `[1m][beta]`)
|
|
@@ -25319,7 +25636,8 @@ const XHIGH_DEFAULT_SHIM_MODELS = ["gpt-5.6-sol", "gpt-5.5"];
|
|
|
25319
25636
|
function normalizeModelId(id) {
|
|
25320
25637
|
return (id.includes("/") ? id.slice(id.lastIndexOf("/") + 1) : id).replace(/(?:\[[^\]]*\])+\s*$/, "");
|
|
25321
25638
|
}
|
|
25322
|
-
/** True iff `id` (after normalization) is in the xhigh effort-policy set.
|
|
25639
|
+
/** True iff `id` (after normalization) is in the xhigh effort-policy set. Only
|
|
25640
|
+
* consulted when `GH_ROUTER_FRONTIER_XHIGH_DEFAULT=1` opts in. */
|
|
25323
25641
|
function shimDefaultsToXhigh(id) {
|
|
25324
25642
|
return XHIGH_DEFAULT_SHIM_MODELS.includes(normalizeModelId(id));
|
|
25325
25643
|
}
|
|
@@ -25351,21 +25669,35 @@ function geminiAvailable(source = state) {
|
|
|
25351
25669
|
return models.some((m) => /^gemini-3\..*pro/i.test(m.id));
|
|
25352
25670
|
}
|
|
25353
25671
|
/**
|
|
25354
|
-
* First
|
|
25355
|
-
* `
|
|
25356
|
-
*
|
|
25357
|
-
*
|
|
25672
|
+
* First id in `chain` that is present in the live catalog. With
|
|
25673
|
+
* `requireToolCalls`, skips an entry whose catalog record does not advertise
|
|
25674
|
+
* `tool_calls` (strict `!== true`, so absent metadata fails closed). Returns
|
|
25675
|
+
* undefined when the catalog is unavailable or nothing in the chain matches, so
|
|
25676
|
+
* every caller degrades gracefully rather than throwing on a thin catalog.
|
|
25677
|
+
*
|
|
25678
|
+
* Extracted from `resolveOpenAiFrontier` so the per-agent resolvers below share
|
|
25679
|
+
* one walk instead of hand-copying it. Ids are matched EXACTLY against
|
|
25680
|
+
* `catalog.id` — no slug translation, matching the pre-existing behavior.
|
|
25358
25681
|
*/
|
|
25359
|
-
function
|
|
25682
|
+
function firstPresentInCatalog(chain, opts) {
|
|
25360
25683
|
const models = state.models?.data;
|
|
25361
25684
|
if (!models) return void 0;
|
|
25362
|
-
for (const id of
|
|
25685
|
+
for (const id of chain) {
|
|
25363
25686
|
const found = models.find((m) => m.id === id);
|
|
25364
25687
|
if (!found) continue;
|
|
25365
25688
|
if (opts?.requireToolCalls && found.capabilities?.supports?.tool_calls !== true) continue;
|
|
25366
25689
|
return id;
|
|
25367
25690
|
}
|
|
25368
25691
|
}
|
|
25692
|
+
/**
|
|
25693
|
+
* First available OpenAI frontier model in the live catalog (prefer
|
|
25694
|
+
* `gpt-5.6-sol`, fall back to `gpt-5.5`). Returns undefined when neither is
|
|
25695
|
+
* present. With `requireToolCalls`, only returns a model whose catalog entry
|
|
25696
|
+
* advertises `tool_calls`.
|
|
25697
|
+
*/
|
|
25698
|
+
function resolveOpenAiFrontier(opts) {
|
|
25699
|
+
return firstPresentInCatalog(OPENAI_FRONTIER_MODELS, opts);
|
|
25700
|
+
}
|
|
25369
25701
|
function standInToolEnabled() {
|
|
25370
25702
|
const models = state.models?.data;
|
|
25371
25703
|
if (!models) return false;
|
|
@@ -25374,13 +25706,44 @@ function standInToolEnabled() {
|
|
|
25374
25706
|
const hasGeminiPro = geminiAvailable();
|
|
25375
25707
|
return hasOpenAi && hasOpus && hasGeminiPro;
|
|
25376
25708
|
}
|
|
25377
|
-
/**
|
|
25378
|
-
*
|
|
25379
|
-
* back to `gpt-5.5`.
|
|
25380
|
-
* model. */
|
|
25709
|
+
/** Model for the native subagents that want the OpenAI frontier coder
|
|
25710
|
+
* (`implementer`, `reviewer`) iff it is live with tool calls. Prefers
|
|
25711
|
+
* `gpt-5.6-sol`, falls back to `gpt-5.5`. Absent → those agents omit their
|
|
25712
|
+
* `model:` line and inherit the lead's model. */
|
|
25381
25713
|
function nativeSubagentModel() {
|
|
25382
25714
|
return resolveOpenAiFrontier({ requireToolCalls: true });
|
|
25383
25715
|
}
|
|
25716
|
+
/** Model for `brainstorm`. Absent → inherits the lead's model.
|
|
25717
|
+
*
|
|
25718
|
+
* Leads with Google so the options it generates come from a third lab: the
|
|
25719
|
+
* Anthropic lead is the producer and the OpenAI frontier already backs
|
|
25720
|
+
* `implementer`/`reviewer`, so a same-lab brainstormer would mostly restate
|
|
25721
|
+
* what the lead already thought of. */
|
|
25722
|
+
function brainstormModel() {
|
|
25723
|
+
return firstPresentInCatalog([REVIEW_DEFAULT_MODEL, ...OPENAI_FRONTIER_MODELS], { requireToolCalls: true });
|
|
25724
|
+
}
|
|
25725
|
+
/** Model for `scribe`. Absent → inherits the lead's model.
|
|
25726
|
+
*
|
|
25727
|
+
* Leads with the mid tier: documentation is verifiable prose, not frontier
|
|
25728
|
+
* reasoning. */
|
|
25729
|
+
function scribeModel() {
|
|
25730
|
+
return firstPresentInCatalog(["gpt-5.6-terra", ...OPENAI_FRONTIER_MODELS], { requireToolCalls: true });
|
|
25731
|
+
}
|
|
25732
|
+
/**
|
|
25733
|
+
* Model for `scout` — CHEAP TIER ONLY, with no frontier fallback on purpose.
|
|
25734
|
+
*
|
|
25735
|
+
* `scout` exists so a foreground repository lookup does not run at the lead's
|
|
25736
|
+
* model rates. The usual "absent → omit `model:` and inherit the lead" fallback
|
|
25737
|
+
* would therefore defeat the agent: on a thin or briefly-unavailable catalog it
|
|
25738
|
+
* would silently start answering grep-and-summarize questions on Opus, which is
|
|
25739
|
+
* the exact cost it was added to avoid. Returning undefined here makes the
|
|
25740
|
+
* caller drop the agent instead, so the lead falls back to the CLI's `Explore`
|
|
25741
|
+
* (same behavior as before `scout` existed) rather than to an expensive
|
|
25742
|
+
* impostor wearing the cheap agent's name.
|
|
25743
|
+
*/
|
|
25744
|
+
function scoutModel() {
|
|
25745
|
+
return firstPresentInCatalog([EXPLORE_DEFAULT_MODEL, DEFAULT_MODEL], { requireToolCalls: true });
|
|
25746
|
+
}
|
|
25384
25747
|
/**
|
|
25385
25748
|
* Gate for the worker tools (`explore`, `review`, `implement`).
|
|
25386
25749
|
*
|
|
@@ -32646,9 +33009,12 @@ function buildAgentPrompt(persona, opts) {
|
|
|
32646
33009
|
* of the live catalog). The raw `mcp__<workers>__*` tools are named only
|
|
32647
33010
|
* as the guarded plumbing the dispatchers call, never as a main-agent
|
|
32648
33011
|
* interface.
|
|
32649
|
-
* - Always names the implementer/
|
|
32650
|
-
* (they are injected unconditionally
|
|
32651
|
-
*
|
|
33012
|
+
* - Always names the implementer/reviewer/brainstorm/scribe native subagents
|
|
33013
|
+
* (they are injected unconditionally, degrading to the lead's model rather
|
|
33014
|
+
* than disappearing); `scout` is named only when `scoutAvailable` is not
|
|
33015
|
+
* false, because it is dropped outright when no cheap-tier model resolves.
|
|
33016
|
+
* The implementer-vs-`worker-implement` contrast is added only when worker
|
|
33017
|
+
* tools are available.
|
|
32652
33018
|
* - Conditionally lists stand_in only when `standInAvailable`
|
|
32653
33019
|
* (mirrors `standInToolEnabled()`).
|
|
32654
33020
|
* - Conditionally lists gh-first-mate only when `agentToolsAvailable`
|
|
@@ -32680,7 +33046,7 @@ function buildPeerAwarenessSnippet(opts) {
|
|
|
32680
33046
|
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." : "";
|
|
32681
33047
|
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.`];
|
|
32682
33048
|
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\`.`);
|
|
32683
|
-
para2Parts.push(`
|
|
33049
|
+
para2Parts.push(`Native subagents (Task), each in its own context so heavy work never fills yours: \`implementer\` (you know what to build), \`reviewer\` (something exists and you want it assessed, including reproducing and root-causing a failure), \`brainstorm\` (you do not yet know which approach to take)${opts.scoutAvailable === false ? "" : ", `scout` (find or understand something in the repo, cheap)"}, \`scribe\` (docs and ADRs that trail the code).`);
|
|
32684
33050
|
if (opts.workerToolsAvailable) para2Parts.push(`For a bounded, well-scoped implementation, prefer the \`implementer\` subagent over \`worker-implement\`; reach for \`worker-implement\` only when you specifically need git-worktree isolation, parallel variants, or a throwaway experiment.`);
|
|
32685
33051
|
if (opts.workerToolsAvailable) para2Parts.push(`\`mcp__${orchestrateKey}__decompose\` composes an open-ended ask into a typed, VERIFIED workflow IR (a strong driver decorrelated by a cross-lab critic, so the decompose step isn't a single point of failure), and \`mcp__${orchestrateKey}__run_workflow\` executes that IR through a frozen kernel delivering max(orchestrated, baseline) over a sealed executable gate, so it never ships worse than a plain single-model run. \`mcp__${orchestrateKey}__verify_workflow\` checks an IR's floor invariants before you run it, and \`mcp__${orchestrateKey}__attest_step\` audits that a finished run's producers were each checked by a different lab. They suit non-trivial, role-separated asks; a trivial ask does not need them.`);
|
|
32686
33052
|
else para2Parts.push(`\`mcp__${orchestrateKey}__verify_workflow\` statically checks a workflow IR's floor invariants and \`mcp__${orchestrateKey}__attest_step\` audits a run's cross-lab lineage (the \`decompose\`/\`run_workflow\` composer + kernel need the worker backend, unavailable here).`);
|
|
@@ -33901,5 +34267,5 @@ function enumerateInjectedMcpToolNames(groupKeys, opts = {}) {
|
|
|
33901
34267
|
}
|
|
33902
34268
|
|
|
33903
34269
|
//#endregion
|
|
33904
|
-
export { searchWeb as $,
|
|
33905
|
-
//# sourceMappingURL=peer-mcp-personas-
|
|
34270
|
+
export { searchWeb as $, DEFAULT_CODEX_MODEL as $t, trustRepo as A, assembleResponsesPayload as At, TEST_DEFAULT_MODEL as B, provisionAndIndexColbert as Bt, fileLastPromptStore as C, HTTPError as Cn, scribeModel as Ct, repoRoot as D, copilotHeaders as Dn, countTokens as Dt, repoFingerprint as E, copilotBaseUrl as En, shimDefaultsToXhigh as Et, EXPLORE_DEFAULT_MODEL as F, MAX_RESPONSE_BODY_BYTES as Ft, buildEnv as G, DEFINITION_OF_GREATNESS as Gt, resolveModeDefaults as H, extractZipMember as Ht, EXPLORE_DEFAULT_THINKING as I, readResponseBodyCapped as It, toolbeltEnabled as J, buildWorkspaceHeaderHelperCommand as Jt, availableToolCommands as K, shouldUseInsecureTls as Kt, IMPLEMENT_DEFAULT_MODEL as L, parseJsonOrDiagnose as Lt, resolveSealedGate as M, pickEndpoint as Mt, BROWSE_DEFAULT_MODEL as N, createResponses as Nt, stopGateEnabledForRepo as O, githubHeaders as On, createMessages as Ot, DEFAULT_MODEL as P, createChatCompletions as Pt, assetFor as Q, DEFAULT_CLAUDE_MODEL_FALLBACKS as Qt, PLAN_DEFAULT_MODEL as R, provisionBrowserAssets as Rt, fileFindingsStore as S, fetchWithTransientRetry as Sn, scoutModel as St, isSubagentContext as T, GITHUB_API_BASE_URL as Tn, workerToolsEnabled as Tt, resolveWorkerRunOpts as U, warmTreeSitterPool as Ut, appendPlanReminder as V, extractTarGzMember as Vt, runWorkerAgent as W, CONDENSED_OPERATING_SEQUENCE as Wt, vscodeRipgrepPath as X, collapsePathKeys as Xt, toolbeltSkipSet as Y, buildWorkspaceHeaderJson as Yt, TOOLBELT_TOOLS$1 as Z, toolbeltPathOverride as Zt, stopGateDisabled as _, resolveCodexModel as _n, browserCompoundToolsEnabled as _t, buildPeerAwarenessSnippet as a, pickClaudeDefault as an, buildAnthropicErrorEvent as at, stopReviewEnabled as b, getModels as bn, geminiAvailable as bt, personasFor as c, setupCopilotToken as cn, logStreamError as ct, buildStopHookCommand as d, tryRefreshAndRetry as dn, handleMcpDelete as dt, DEFAULT_CODEX_MODEL_FALLBACKS as en, ADVISOR_INTERNAL_TOOL_NAME as et, captureLaunchBaseline as f, cacheCopilotVersion as fn, handleMcpPost as ft, launchBaselineKey as g, isNullish as gn, browseAgentEnabled as gt, injectStopHookIntoSettingsFile as h, filterBetaHeader as hn, brainstormModel as ht, buildAgentPrompt as i, generateRandomPort as in, isAdvisorRequested as it, liveExec as j, resolveMcpToolTimeoutMs as jt, stopReviewStateDir as k, state as kn, getTokenCount as kt, buildArtifactOpenHookCommand as l, setupGitHubAgentToken as ln, readIteratorWithTimeout as lt, fileBlockBudget as m, cacheVSCodeVersion as mn, artifactToolsEnabled as mt, MCP_GROUPS as n, UPSTREAM_FETCH_TIMEOUT_MS as nn, buildAdvisorStream as nt, buildPeerAwarenessSummary as o, getPackageVersion as on, buildOpenAIErrorEvent as ot, decideStopHook as p, cacheModels as pn, agentToolsEnabled as pt, buildToolbeltAwareness as q, ArtifactClient as qt, assertMcpToolSurfaceConsistent as r, UPSTREAM_INACTIVITY_TIMEOUT_MS as rn, injectAdvisorTool as rt, enumerateInjectedMcpToolNames as s, withInstallLock as sn, isControllerClosedError as st, GROUP_META as t, DEFAULT_PORT as tn, ADVISOR_TOOL_INSTRUCTIONS as tt, buildSessionBindHookCommand as u, setupGitHubToken as un, relayAnthropicStream as ut, stopGateId as v, resolveModel as vn, browserToolsEnabled as vt, fileReviewDebounce as w, forwardError as wn, standInToolEnabled as wt, fileBaselineStore as x, getGitHubUser as xn, nativeSubagentModel as xt, stopGatePlanMode as y, sleep as yn, fleetToolsEnabled as yt, REVIEW_DEFAULT_MODEL as z, hasSupportedBrowserInstalled as zt };
|
|
34271
|
+
//# sourceMappingURL=peer-mcp-personas-B-EPdjSE.js.map
|