@mrciphersmith/keryx 0.2.75 → 0.2.76
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/cli.js +777 -161
- package/package.json +1 -1
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +100 -10
- package/src/gdskills/bundled/skills/review/review-orchestrator/review-context.schema.json +175 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +7 -0
- package/src/gdskills/contracts/review-finding.schema.json +4 -0
package/dist/cli.js
CHANGED
|
@@ -11214,9 +11214,9 @@ function resolvePointer(doc, fragment) {
|
|
|
11214
11214
|
if (!fragment.startsWith("/")) {
|
|
11215
11215
|
throw new Error(`Unsupported JSON Pointer fragment (must start with "/"): ${fragment}`);
|
|
11216
11216
|
}
|
|
11217
|
-
const
|
|
11217
|
+
const segments = fragment.split("/").slice(1).map(decodePointerSegment);
|
|
11218
11218
|
let node = doc;
|
|
11219
|
-
for (const segment of
|
|
11219
|
+
for (const segment of segments) {
|
|
11220
11220
|
if (node === null || typeof node !== "object" || Array.isArray(node)) {
|
|
11221
11221
|
throw new Error(`Pointer segment "${segment}" not resolvable in fragment #${fragment}`);
|
|
11222
11222
|
}
|
|
@@ -12495,12 +12495,12 @@ class FakeProvider {
|
|
|
12495
12495
|
}
|
|
12496
12496
|
const { attemptId } = opts;
|
|
12497
12497
|
let sequence = 0;
|
|
12498
|
-
const
|
|
12498
|
+
const emit2 = (body) => ({
|
|
12499
12499
|
...body,
|
|
12500
12500
|
sequence: sequence++,
|
|
12501
12501
|
attemptId
|
|
12502
12502
|
});
|
|
12503
|
-
yield
|
|
12503
|
+
yield emit2({ kind: "model_start" });
|
|
12504
12504
|
for (const raw of transcript.events) {
|
|
12505
12505
|
const payload = raw.payload ?? {};
|
|
12506
12506
|
switch (raw.kind) {
|
|
@@ -12513,13 +12513,13 @@ class FakeProvider {
|
|
|
12513
12513
|
if (extensions !== undefined) {
|
|
12514
12514
|
body.unknownExtensions = extensions;
|
|
12515
12515
|
}
|
|
12516
|
-
yield
|
|
12516
|
+
yield emit2(body);
|
|
12517
12517
|
break;
|
|
12518
12518
|
}
|
|
12519
12519
|
case "tool_call": {
|
|
12520
12520
|
const input2 = payload.input;
|
|
12521
12521
|
if (!isPlainObject6(input2)) {
|
|
12522
|
-
yield
|
|
12522
|
+
yield emit2({
|
|
12523
12523
|
kind: "provider_error",
|
|
12524
12524
|
error: {
|
|
12525
12525
|
kind: "malformed",
|
|
@@ -12538,21 +12538,21 @@ class FakeProvider {
|
|
|
12538
12538
|
if (typeof payload.toolName === "string") {
|
|
12539
12539
|
startBody.toolName = payload.toolName;
|
|
12540
12540
|
}
|
|
12541
|
-
yield
|
|
12542
|
-
yield
|
|
12541
|
+
yield emit2(startBody);
|
|
12542
|
+
yield emit2(endBody);
|
|
12543
12543
|
break;
|
|
12544
12544
|
}
|
|
12545
12545
|
case "finish": {
|
|
12546
12546
|
const usage = payload.usage;
|
|
12547
12547
|
if (isPlainObject6(usage)) {
|
|
12548
|
-
yield
|
|
12548
|
+
yield emit2({ kind: "usage_update", usage: mapUsage(usage) });
|
|
12549
12549
|
}
|
|
12550
12550
|
const body = { kind: "model_end" };
|
|
12551
12551
|
const extensions = extractUnknownExtensions(payload);
|
|
12552
12552
|
if (extensions !== undefined) {
|
|
12553
12553
|
body.unknownExtensions = extensions;
|
|
12554
12554
|
}
|
|
12555
|
-
yield
|
|
12555
|
+
yield emit2(body);
|
|
12556
12556
|
break;
|
|
12557
12557
|
}
|
|
12558
12558
|
case "error": {
|
|
@@ -12561,7 +12561,7 @@ class FakeProvider {
|
|
|
12561
12561
|
retryable: payload.retryable,
|
|
12562
12562
|
message: payload.message
|
|
12563
12563
|
};
|
|
12564
|
-
yield
|
|
12564
|
+
yield emit2({ kind: "provider_error", error });
|
|
12565
12565
|
return;
|
|
12566
12566
|
}
|
|
12567
12567
|
}
|
|
@@ -13660,7 +13660,10 @@ async function runModelTurn(input2) {
|
|
|
13660
13660
|
let reasoning = false;
|
|
13661
13661
|
const startedAt = performance.now();
|
|
13662
13662
|
let firstByteAt;
|
|
13663
|
-
for await (const event of port.stream(request, {
|
|
13663
|
+
for await (const event of port.stream(request, {
|
|
13664
|
+
attemptId: request.requestId,
|
|
13665
|
+
...input2.signal !== undefined ? { signal: input2.signal } : {}
|
|
13666
|
+
})) {
|
|
13664
13667
|
if (firstByteAt === undefined) {
|
|
13665
13668
|
firstByteAt = performance.now();
|
|
13666
13669
|
}
|
|
@@ -13849,14 +13852,14 @@ function hasUnquotedMetacharacter(command) {
|
|
|
13849
13852
|
return state.unbalanced || state.metacharacter;
|
|
13850
13853
|
}
|
|
13851
13854
|
function splitSegments(command) {
|
|
13852
|
-
const
|
|
13855
|
+
const segments = [];
|
|
13853
13856
|
let buf = "";
|
|
13854
13857
|
let pending;
|
|
13855
13858
|
let quote;
|
|
13856
13859
|
const flush = (next) => {
|
|
13857
13860
|
const raw = buf.trim();
|
|
13858
13861
|
if (raw.length > 0) {
|
|
13859
|
-
|
|
13862
|
+
segments.push({
|
|
13860
13863
|
raw,
|
|
13861
13864
|
words: splitWords(raw),
|
|
13862
13865
|
...pending !== undefined ? { precededBy: pending } : {}
|
|
@@ -13898,7 +13901,7 @@ function splitSegments(command) {
|
|
|
13898
13901
|
buf += ch;
|
|
13899
13902
|
}
|
|
13900
13903
|
flush();
|
|
13901
|
-
return
|
|
13904
|
+
return segments;
|
|
13902
13905
|
}
|
|
13903
13906
|
function separatorAt(ch, next) {
|
|
13904
13907
|
if (ch === "&" && next === "&")
|
|
@@ -14002,18 +14005,18 @@ function classifySegment(seg) {
|
|
|
14002
14005
|
function classifyCommand2(command) {
|
|
14003
14006
|
if (command.trim().length === 0)
|
|
14004
14007
|
return "shell";
|
|
14005
|
-
const
|
|
14006
|
-
for (const seg of
|
|
14008
|
+
const segments = splitSegments(command);
|
|
14009
|
+
for (const seg of segments) {
|
|
14007
14010
|
if (classifySegment(seg))
|
|
14008
14011
|
return "destructive";
|
|
14009
14012
|
}
|
|
14010
|
-
for (let i = 1;i <
|
|
14011
|
-
const seg =
|
|
14013
|
+
for (let i = 1;i < segments.length; i++) {
|
|
14014
|
+
const seg = segments[i];
|
|
14012
14015
|
if (seg.precededBy !== "|")
|
|
14013
14016
|
continue;
|
|
14014
14017
|
if (!INTERPRETERS.has(head(seg.words)))
|
|
14015
14018
|
continue;
|
|
14016
|
-
const upstream =
|
|
14019
|
+
const upstream = segments.slice(0, i);
|
|
14017
14020
|
if (upstream.some((s) => DOWNLOADERS.has(head(s.words))))
|
|
14018
14021
|
return "destructive";
|
|
14019
14022
|
}
|
|
@@ -29245,6 +29248,32 @@ var init_metaproject_operations = __esm(() => {
|
|
|
29245
29248
|
|
|
29246
29249
|
// src/wiki/deep-enrich.ts
|
|
29247
29250
|
import { randomUUID as randomUUID15, createHash as createHash22 } from "crypto";
|
|
29251
|
+
function composeAbortSignals(external, timeout) {
|
|
29252
|
+
if (external === undefined) {
|
|
29253
|
+
return { signal: timeout, dispose: () => {} };
|
|
29254
|
+
}
|
|
29255
|
+
const controller = new AbortController;
|
|
29256
|
+
const abortFrom = (signal) => {
|
|
29257
|
+
if (!controller.signal.aborted) {
|
|
29258
|
+
controller.abort(signal.reason);
|
|
29259
|
+
}
|
|
29260
|
+
};
|
|
29261
|
+
const onExternalAbort = () => abortFrom(external);
|
|
29262
|
+
const onTimeoutAbort = () => abortFrom(timeout);
|
|
29263
|
+
external.addEventListener("abort", onExternalAbort, { once: true });
|
|
29264
|
+
timeout.addEventListener("abort", onTimeoutAbort, { once: true });
|
|
29265
|
+
if (external.aborted)
|
|
29266
|
+
abortFrom(external);
|
|
29267
|
+
if (timeout.aborted)
|
|
29268
|
+
abortFrom(timeout);
|
|
29269
|
+
return {
|
|
29270
|
+
signal: controller.signal,
|
|
29271
|
+
dispose: () => {
|
|
29272
|
+
external.removeEventListener("abort", onExternalAbort);
|
|
29273
|
+
timeout.removeEventListener("abort", onTimeoutAbort);
|
|
29274
|
+
}
|
|
29275
|
+
};
|
|
29276
|
+
}
|
|
29248
29277
|
function errorMessage2(cause) {
|
|
29249
29278
|
return cause instanceof Error ? cause.message : String(cause);
|
|
29250
29279
|
}
|
|
@@ -29283,6 +29312,9 @@ async function enrichPageDeep(input2) {
|
|
|
29283
29312
|
const idSeq = input2.idSeq ?? (() => randomUUID15());
|
|
29284
29313
|
const clock = input2.clock ?? (() => new Date().toISOString());
|
|
29285
29314
|
const toolCalls = [];
|
|
29315
|
+
if (input2.signal?.aborted) {
|
|
29316
|
+
return { fallback: true, reason: "deep enrich cancelled", toolCalls };
|
|
29317
|
+
}
|
|
29286
29318
|
try {
|
|
29287
29319
|
const env = envWithSavedApiKeys(input2.env ?? process.env);
|
|
29288
29320
|
const credentialAvailable = hasCredential(input2.provider, env);
|
|
@@ -29358,6 +29390,7 @@ async function enrichPageDeep(input2) {
|
|
|
29358
29390
|
let assistant = "";
|
|
29359
29391
|
let pending;
|
|
29360
29392
|
const abort = new AbortController;
|
|
29393
|
+
const composed = composeAbortSignals(input2.signal, abort.signal);
|
|
29361
29394
|
const io = {
|
|
29362
29395
|
write: (s) => {
|
|
29363
29396
|
assistant += s;
|
|
@@ -29389,11 +29422,12 @@ async function enrichPageDeep(input2) {
|
|
|
29389
29422
|
};
|
|
29390
29423
|
const history = [];
|
|
29391
29424
|
const userLine = buildDeepUserPrompt(input2.page, input2.original, input2.extraInstruction);
|
|
29392
|
-
const turn = runAgentTurn(io, deps, history, userLine, { signal:
|
|
29425
|
+
const turn = runAgentTurn(io, deps, history, userLine, { signal: composed.signal });
|
|
29393
29426
|
const deadlineMs = spawned.reservation.maxRuntimeMs;
|
|
29394
29427
|
if (deadlineMs <= 0) {
|
|
29395
29428
|
abort.abort();
|
|
29396
29429
|
turn.catch(() => {});
|
|
29430
|
+
composed.dispose();
|
|
29397
29431
|
releaseBudget();
|
|
29398
29432
|
return {
|
|
29399
29433
|
fallback: true,
|
|
@@ -29405,16 +29439,31 @@ async function enrichPageDeep(input2) {
|
|
|
29405
29439
|
const expired = new Promise((resolve2) => {
|
|
29406
29440
|
timer = setTimeout(() => resolve2("timeout"), deadlineMs);
|
|
29407
29441
|
});
|
|
29442
|
+
let onExternalAbort;
|
|
29443
|
+
const cancelled = new Promise((resolve2) => {
|
|
29444
|
+
if (input2.signal === undefined)
|
|
29445
|
+
return;
|
|
29446
|
+
onExternalAbort = () => resolve2("cancelled");
|
|
29447
|
+
if (input2.signal.aborted) {
|
|
29448
|
+
onExternalAbort();
|
|
29449
|
+
} else {
|
|
29450
|
+
input2.signal.addEventListener("abort", onExternalAbort, { once: true });
|
|
29451
|
+
}
|
|
29452
|
+
});
|
|
29408
29453
|
let outcome;
|
|
29409
29454
|
try {
|
|
29410
|
-
outcome = await Promise.race([turn.then(() => "done"), expired]);
|
|
29455
|
+
outcome = await Promise.race([turn.then(() => "done"), expired, cancelled]);
|
|
29411
29456
|
} finally {
|
|
29412
29457
|
if (timer !== undefined)
|
|
29413
29458
|
clearTimeout(timer);
|
|
29459
|
+
if (onExternalAbort !== undefined) {
|
|
29460
|
+
input2.signal?.removeEventListener("abort", onExternalAbort);
|
|
29461
|
+
}
|
|
29414
29462
|
}
|
|
29415
29463
|
if (outcome === "timeout") {
|
|
29416
29464
|
abort.abort();
|
|
29417
29465
|
turn.catch(() => {});
|
|
29466
|
+
composed.dispose();
|
|
29418
29467
|
releaseBudget();
|
|
29419
29468
|
const partial = assistant.trim();
|
|
29420
29469
|
return {
|
|
@@ -29424,7 +29473,15 @@ async function enrichPageDeep(input2) {
|
|
|
29424
29473
|
toolCalls
|
|
29425
29474
|
};
|
|
29426
29475
|
}
|
|
29476
|
+
composed.dispose();
|
|
29427
29477
|
releaseBudget();
|
|
29478
|
+
if (outcome === "cancelled" || input2.signal?.aborted) {
|
|
29479
|
+
return {
|
|
29480
|
+
fallback: true,
|
|
29481
|
+
reason: "deep enrich cancelled",
|
|
29482
|
+
toolCalls
|
|
29483
|
+
};
|
|
29484
|
+
}
|
|
29428
29485
|
const raw = assistant.trim().length > 0 ? assistant.trim() : history.filter((m) => m.role === "assistant").map((m) => m.content).join(`
|
|
29429
29486
|
`).trim();
|
|
29430
29487
|
if (raw.length === 0) {
|
|
@@ -29611,6 +29668,9 @@ function isWikiEnrichIntent(line) {
|
|
|
29611
29668
|
if (t.length === 0) {
|
|
29612
29669
|
return false;
|
|
29613
29670
|
}
|
|
29671
|
+
if (/^keryx\s+wiki\s+enrich(?:\s|$)/.test(t)) {
|
|
29672
|
+
return false;
|
|
29673
|
+
}
|
|
29614
29674
|
const ru = t.includes("\u0432\u0438\u043A\u0438") && (t.includes("\u043E\u0431\u043E\u0433\u0430\u0442") || t.includes("\u043E\u0431\u043E\u0433\u0430\u0449"));
|
|
29615
29675
|
const en = /\benrich\b/.test(t) && /\bwiki\b/.test(t) || /\bwiki\s+enrich\b/.test(t);
|
|
29616
29676
|
return ru || en;
|
|
@@ -29889,6 +29949,7 @@ async function wikiEnrich(input2) {
|
|
|
29889
29949
|
enriched: 0,
|
|
29890
29950
|
dryRun: 0,
|
|
29891
29951
|
skipped: 0,
|
|
29952
|
+
cancelled: 0,
|
|
29892
29953
|
failed: 0
|
|
29893
29954
|
};
|
|
29894
29955
|
if (input2.refreshGraph) {
|
|
@@ -29929,9 +29990,15 @@ async function wikiEnrich(input2) {
|
|
|
29929
29990
|
pageResults = await mapPool(pages, concurrency, async (page, i) => {
|
|
29930
29991
|
const index = i + 1;
|
|
29931
29992
|
const status = page.status ?? "draft";
|
|
29993
|
+
if (input2.signal?.aborted) {
|
|
29994
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled" };
|
|
29995
|
+
}
|
|
29932
29996
|
onPage({ index, total, path: page.relativePath, status, phase: "start" });
|
|
29933
29997
|
try {
|
|
29934
29998
|
const originalRaw = await readFile62(page.absolutePath, "utf8");
|
|
29999
|
+
if (input2.signal?.aborted) {
|
|
30000
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled" };
|
|
30001
|
+
}
|
|
29935
30002
|
const ensured = ensureWikiFrontmatter(originalRaw, {
|
|
29936
30003
|
title: page.title,
|
|
29937
30004
|
pageType: page.pageType
|
|
@@ -29948,8 +30015,12 @@ async function wikiEnrich(input2) {
|
|
|
29948
30015
|
env,
|
|
29949
30016
|
...input2.fetch ? { fetch: input2.fetch } : {},
|
|
29950
30017
|
...input2.baseUrl !== undefined ? { baseUrl: input2.baseUrl } : {},
|
|
29951
|
-
...input2.providerFactory ? { providerFactory: input2.providerFactory } : {}
|
|
30018
|
+
...input2.providerFactory ? { providerFactory: input2.providerFactory } : {},
|
|
30019
|
+
...input2.signal !== undefined ? { signal: input2.signal } : {}
|
|
29952
30020
|
});
|
|
30021
|
+
if (input2.signal?.aborted) {
|
|
30022
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled" };
|
|
30023
|
+
}
|
|
29953
30024
|
if (turn.error) {
|
|
29954
30025
|
onPage({ index, total, path: page.relativePath, status, phase: "failed" });
|
|
29955
30026
|
return {
|
|
@@ -29986,7 +30057,16 @@ async function wikiEnrich(input2) {
|
|
|
29986
30057
|
const output2 = enriched.endsWith(`
|
|
29987
30058
|
`) ? enriched : `${enriched}
|
|
29988
30059
|
`;
|
|
29989
|
-
const materialized = prepareOutputForPersistence(await
|
|
30060
|
+
const materialized = prepareOutputForPersistence(await (input2.guardOutput ?? guardOutput)({
|
|
30061
|
+
cwd: input2.cwd,
|
|
30062
|
+
content: output2,
|
|
30063
|
+
target: "wiki",
|
|
30064
|
+
source: "generated",
|
|
30065
|
+
path: page.relativePath
|
|
30066
|
+
}), output2);
|
|
30067
|
+
if (input2.signal?.aborted) {
|
|
30068
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled" };
|
|
30069
|
+
}
|
|
29990
30070
|
if (!materialized.allowed) {
|
|
29991
30071
|
onPage({ index, total, path: page.relativePath, status, phase: "failed" });
|
|
29992
30072
|
return { path: page.relativePath, action: "failed", reason: materialized.reason };
|
|
@@ -30038,6 +30118,8 @@ async function wikiEnrich(input2) {
|
|
|
30038
30118
|
}
|
|
30039
30119
|
} else if (entry.action === "dry-run") {
|
|
30040
30120
|
result.dryRun += 1;
|
|
30121
|
+
} else if (entry.action === "cancelled") {
|
|
30122
|
+
result.cancelled += 1;
|
|
30041
30123
|
} else if (entry.action === "failed") {
|
|
30042
30124
|
result.failed += 1;
|
|
30043
30125
|
resumeState.failed.push({ path: entry.path, reason: entry.reason ?? "failed" });
|
|
@@ -30108,6 +30190,9 @@ function finalizeEnrichedText(original, rawText, options) {
|
|
|
30108
30190
|
async function finishSuccess(ctx, page, originalRaw, content, keyFiles, extra) {
|
|
30109
30191
|
const index = ctx.pageIndex(page.relativePath);
|
|
30110
30192
|
const status = page.status ?? "draft";
|
|
30193
|
+
if (ctx.input.signal?.aborted) {
|
|
30194
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled", tier: extra.tier };
|
|
30195
|
+
}
|
|
30111
30196
|
const nodeHash = keyFiles.length > 0 ? await computePageNodeHash(ctx.cwd, keyFiles, ctx.graph) : undefined;
|
|
30112
30197
|
if (ctx.input.dryRun) {
|
|
30113
30198
|
ctx.onPage({ index, total: ctx.total, path: page.relativePath, status, phase: "done" });
|
|
@@ -30124,7 +30209,16 @@ async function finishSuccess(ctx, page, originalRaw, content, keyFiles, extra) {
|
|
|
30124
30209
|
const output2 = content.endsWith(`
|
|
30125
30210
|
`) ? content : `${content}
|
|
30126
30211
|
`;
|
|
30127
|
-
const materialized = prepareOutputForPersistence(await
|
|
30212
|
+
const materialized = prepareOutputForPersistence(await (ctx.input.guardOutput ?? guardOutput)({
|
|
30213
|
+
cwd: ctx.cwd,
|
|
30214
|
+
content: output2,
|
|
30215
|
+
target: "wiki",
|
|
30216
|
+
source: "generated",
|
|
30217
|
+
path: page.relativePath
|
|
30218
|
+
}), output2);
|
|
30219
|
+
if (ctx.input.signal?.aborted) {
|
|
30220
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled", tier: extra.tier };
|
|
30221
|
+
}
|
|
30128
30222
|
if (!materialized.allowed) {
|
|
30129
30223
|
ctx.onPage({ index, total: ctx.total, path: page.relativePath, status, phase: "failed" });
|
|
30130
30224
|
return {
|
|
@@ -30157,6 +30251,9 @@ async function runDeepSingle(ctx, item) {
|
|
|
30157
30251
|
const { page, original, originalRaw, keyFiles } = item;
|
|
30158
30252
|
const index = ctx.pageIndex(page.relativePath);
|
|
30159
30253
|
const status = page.status ?? "draft";
|
|
30254
|
+
if (ctx.input.signal?.aborted) {
|
|
30255
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled", tier: "deep" };
|
|
30256
|
+
}
|
|
30160
30257
|
ctx.onPage({ index, total: ctx.total, path: page.relativePath, status, phase: "model" });
|
|
30161
30258
|
try {
|
|
30162
30259
|
const deepResult = await enrichPageDeep({
|
|
@@ -30169,11 +30266,15 @@ async function runDeepSingle(ctx, item) {
|
|
|
30169
30266
|
model: ctx.model,
|
|
30170
30267
|
maxToolCalls: ctx.wikiConfig.rlm.deep.maxToolCalls,
|
|
30171
30268
|
maxRuntimeMs: ctx.wikiConfig.rlm.deep.maxRuntimeMs,
|
|
30269
|
+
...ctx.input.signal !== undefined ? { signal: ctx.input.signal } : {},
|
|
30172
30270
|
env: ctx.env,
|
|
30173
30271
|
...ctx.input.fetch ? { fetch: ctx.input.fetch } : {},
|
|
30174
30272
|
...ctx.input.baseUrl !== undefined ? { baseUrl: ctx.input.baseUrl } : {},
|
|
30175
30273
|
...ctx.input.providerFactory ? { providerFactory: ctx.input.providerFactory } : {}
|
|
30176
30274
|
});
|
|
30275
|
+
if (ctx.input.signal?.aborted) {
|
|
30276
|
+
return { path: page.relativePath, action: "cancelled", reason: "enrichment cancelled", tier: "deep" };
|
|
30277
|
+
}
|
|
30177
30278
|
const deepToolCalls = deepResult.toolCalls.length;
|
|
30178
30279
|
let rawText = null;
|
|
30179
30280
|
if ("enriched" in deepResult) {
|
|
@@ -30251,9 +30352,9 @@ function batchGroupKey(item) {
|
|
|
30251
30352
|
const primaryKeyFile = item.keyFiles[0];
|
|
30252
30353
|
if (primaryKeyFile !== undefined) {
|
|
30253
30354
|
const dir = primaryKeyFile.split("/").slice(0, -1).join("/");
|
|
30254
|
-
const
|
|
30255
|
-
if (
|
|
30256
|
-
return
|
|
30355
|
+
const segments = dir.split("/").filter((segment) => segment.length > 0);
|
|
30356
|
+
if (segments.length > 0) {
|
|
30357
|
+
return segments.slice(0, 2).join("/");
|
|
30257
30358
|
}
|
|
30258
30359
|
}
|
|
30259
30360
|
return item.page.pageType;
|
|
@@ -30290,6 +30391,14 @@ function groupLightPagesIntoBatches(items, batchConfig, tokenBudget) {
|
|
|
30290
30391
|
return batches;
|
|
30291
30392
|
}
|
|
30292
30393
|
async function runLightBatch(ctx, items) {
|
|
30394
|
+
if (ctx.input.signal?.aborted) {
|
|
30395
|
+
return items.map((item) => ({
|
|
30396
|
+
path: item.page.relativePath,
|
|
30397
|
+
action: "cancelled",
|
|
30398
|
+
reason: "enrichment cancelled",
|
|
30399
|
+
tier: "light"
|
|
30400
|
+
}));
|
|
30401
|
+
}
|
|
30293
30402
|
for (const item of items) {
|
|
30294
30403
|
ctx.onPage({
|
|
30295
30404
|
index: ctx.pageIndex(item.page.relativePath),
|
|
@@ -30314,7 +30423,8 @@ async function runLightBatch(ctx, items) {
|
|
|
30314
30423
|
env: ctx.env,
|
|
30315
30424
|
...ctx.input.fetch ? { fetch: ctx.input.fetch } : {},
|
|
30316
30425
|
...ctx.input.baseUrl !== undefined ? { baseUrl: ctx.input.baseUrl } : {},
|
|
30317
|
-
...ctx.input.providerFactory ? { providerFactory: ctx.input.providerFactory } : {}
|
|
30426
|
+
...ctx.input.providerFactory ? { providerFactory: ctx.input.providerFactory } : {},
|
|
30427
|
+
...ctx.input.signal !== undefined ? { signal: ctx.input.signal } : {}
|
|
30318
30428
|
});
|
|
30319
30429
|
} catch (cause) {
|
|
30320
30430
|
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -30334,6 +30444,14 @@ async function runLightBatch(ctx, items) {
|
|
|
30334
30444
|
};
|
|
30335
30445
|
});
|
|
30336
30446
|
}
|
|
30447
|
+
if (ctx.input.signal?.aborted) {
|
|
30448
|
+
return items.map((item) => ({
|
|
30449
|
+
path: item.page.relativePath,
|
|
30450
|
+
action: "cancelled",
|
|
30451
|
+
reason: "enrichment cancelled",
|
|
30452
|
+
tier: "light"
|
|
30453
|
+
}));
|
|
30454
|
+
}
|
|
30337
30455
|
if (turn.error) {
|
|
30338
30456
|
const errorReason = `${turn.error.kind}: ${turn.error.message}`;
|
|
30339
30457
|
return items.map((item) => {
|
|
@@ -30397,8 +30515,18 @@ async function runLightBatch(ctx, items) {
|
|
|
30397
30515
|
}
|
|
30398
30516
|
async function runRlmPipeline(ctxInput) {
|
|
30399
30517
|
const { pages, wikiConfig, input: input2 } = ctxInput;
|
|
30518
|
+
const cancelled = () => pages.map((page) => ({ path: page.relativePath, action: "cancelled", reason: "enrichment cancelled" }));
|
|
30519
|
+
if (input2.signal?.aborted) {
|
|
30520
|
+
return cancelled();
|
|
30521
|
+
}
|
|
30400
30522
|
const graph = await loadGraph(ctxInput.cwd);
|
|
30523
|
+
if (input2.signal?.aborted) {
|
|
30524
|
+
return cancelled();
|
|
30525
|
+
}
|
|
30401
30526
|
const gdgraphConfig = await loadGdgraphConfig(ctxInput.cwd);
|
|
30527
|
+
if (input2.signal?.aborted) {
|
|
30528
|
+
return cancelled();
|
|
30529
|
+
}
|
|
30402
30530
|
const fileNodes = graph.nodes.filter((node) => node.kind === "file").map((node) => node.path);
|
|
30403
30531
|
const rankEdges = buildRankEdges(graph, gdgraphConfig);
|
|
30404
30532
|
const ranked = personalizedPageRank(fileNodes, rankEdges, {
|
|
@@ -30429,11 +30557,21 @@ async function runRlmPipeline(ctxInput) {
|
|
|
30429
30557
|
};
|
|
30430
30558
|
const prepared = [];
|
|
30431
30559
|
for (const page of pages) {
|
|
30560
|
+
if (input2.signal?.aborted) {
|
|
30561
|
+
return cancelled();
|
|
30562
|
+
}
|
|
30432
30563
|
const index = pageIndex(page.relativePath);
|
|
30433
30564
|
const status = page.status ?? "draft";
|
|
30434
30565
|
ctx.onPage({ index, total: ctx.total, path: page.relativePath, status, phase: "start" });
|
|
30566
|
+
if (input2.signal?.aborted) {
|
|
30567
|
+
return cancelled();
|
|
30568
|
+
}
|
|
30569
|
+
input2.onRlmPreparation?.(page.relativePath);
|
|
30435
30570
|
try {
|
|
30436
30571
|
const originalRaw = await readFile62(page.absolutePath, "utf8");
|
|
30572
|
+
if (input2.signal?.aborted) {
|
|
30573
|
+
return cancelled();
|
|
30574
|
+
}
|
|
30437
30575
|
const ensured = ensureWikiFrontmatter(originalRaw, { title: page.title, pageType: page.pageType });
|
|
30438
30576
|
const original = ensured.markdown;
|
|
30439
30577
|
const keyFiles = keyFilesForPage(moduleKeyFilesIndex, page);
|
|
@@ -30441,6 +30579,9 @@ async function runRlmPipeline(ctxInput) {
|
|
|
30441
30579
|
let unchanged = false;
|
|
30442
30580
|
if (previousHash !== undefined && keyFiles.length > 0) {
|
|
30443
30581
|
const currentHash = await computePageNodeHash(ctx.cwd, keyFiles, graph);
|
|
30582
|
+
if (input2.signal?.aborted) {
|
|
30583
|
+
return cancelled();
|
|
30584
|
+
}
|
|
30444
30585
|
unchanged = isPageUnchangedSinceLastEnrich(page.relativePath, currentHash, resumeState.completedNodeHashes);
|
|
30445
30586
|
}
|
|
30446
30587
|
if (unchanged) {
|
|
@@ -30474,6 +30615,9 @@ async function runRlmPipeline(ctxInput) {
|
|
|
30474
30615
|
const item = { page, original, originalRaw, keyFiles };
|
|
30475
30616
|
prepared.push({ kind: tier === "deep" ? "deep" : "light", item });
|
|
30476
30617
|
} catch (cause) {
|
|
30618
|
+
if (input2.signal?.aborted) {
|
|
30619
|
+
return cancelled();
|
|
30620
|
+
}
|
|
30477
30621
|
ctx.onPage({ index, total: ctx.total, path: page.relativePath, status, phase: "failed" });
|
|
30478
30622
|
prepared.push({
|
|
30479
30623
|
kind: "resolved",
|
|
@@ -30485,6 +30629,9 @@ async function runRlmPipeline(ctxInput) {
|
|
|
30485
30629
|
});
|
|
30486
30630
|
}
|
|
30487
30631
|
}
|
|
30632
|
+
if (input2.signal?.aborted) {
|
|
30633
|
+
return cancelled();
|
|
30634
|
+
}
|
|
30488
30635
|
const units = [];
|
|
30489
30636
|
let pendingLight = [];
|
|
30490
30637
|
const flushLight = () => {
|
|
@@ -30514,6 +30661,9 @@ async function runRlmPipeline(ctxInput) {
|
|
|
30514
30661
|
}
|
|
30515
30662
|
}
|
|
30516
30663
|
flushLight();
|
|
30664
|
+
if (input2.signal?.aborted) {
|
|
30665
|
+
return cancelled();
|
|
30666
|
+
}
|
|
30517
30667
|
const unitResults = await mapPool(units, ctxInput.concurrency, async (unit) => {
|
|
30518
30668
|
try {
|
|
30519
30669
|
if (unit.kind === "resolved") {
|
|
@@ -30799,12 +30949,12 @@ async function narrate(opts) {
|
|
|
30799
30949
|
});
|
|
30800
30950
|
if (!turn.credentialAvailable) {
|
|
30801
30951
|
const message2 = formatMissingCredentialHint(turn.provider);
|
|
30802
|
-
|
|
30952
|
+
emit2(asJson, { provider: turn.provider, model: turn.model, credentialAvailable: false, error: message2 }, message2, true);
|
|
30803
30953
|
return;
|
|
30804
30954
|
}
|
|
30805
30955
|
if (turn.error) {
|
|
30806
30956
|
const message2 = `${turn.error.kind}: ${turn.error.message}`;
|
|
30807
|
-
|
|
30957
|
+
emit2(asJson, { provider: turn.provider, model: turn.model, error: message2 }, message2, true);
|
|
30808
30958
|
return;
|
|
30809
30959
|
}
|
|
30810
30960
|
const text = turn.text.trim();
|
|
@@ -30814,7 +30964,7 @@ async function narrate(opts) {
|
|
|
30814
30964
|
console.log(text);
|
|
30815
30965
|
}
|
|
30816
30966
|
}
|
|
30817
|
-
function
|
|
30967
|
+
function emit2(asJson, payload, message2, isError) {
|
|
30818
30968
|
if (asJson) {
|
|
30819
30969
|
console.log(JSON.stringify(payload, null, 2));
|
|
30820
30970
|
} else if (isError) {
|
|
@@ -32904,7 +33054,7 @@ var BUNDLED_GDSKILLS = [
|
|
|
32904
33054
|
"Use gdgraph affected context for exported symbols and shared surfaces.",
|
|
32905
33055
|
"Dispatch specialized review passes conceptually or as separate skill loads.",
|
|
32906
33056
|
"Report findings first, ordered by severity, with concrete file references."
|
|
32907
|
-
], ["review code", "full review", "review changes"]),
|
|
33057
|
+
], ["review", "\u0440\u0435\u0432\u044C\u044E", "review code", "full review", "review changes"]),
|
|
32908
33058
|
skill("review-logic", "review", ["recommended", "full"], "Review logic correctness, contracts, edge cases, nullability, and async behavior.", [
|
|
32909
33059
|
"Trace behavior through call sites and affected context.",
|
|
32910
33060
|
"Look for incorrect assumptions, missing branches, race conditions, and error paths.",
|
|
@@ -32919,7 +33069,13 @@ var BUNDLED_GDSKILLS = [
|
|
|
32919
33069
|
"Map inputs, trust boundaries, and sensitive outputs.",
|
|
32920
33070
|
"Check injection, auth, crypto, secrets, and unsafe filesystem/network behavior.",
|
|
32921
33071
|
"Prioritize exploitable findings with concrete remediation."
|
|
32922
|
-
], [
|
|
33072
|
+
], [
|
|
33073
|
+
"security review",
|
|
33074
|
+
"\u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0441\u0442\u044C",
|
|
33075
|
+
"\u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0441\u0442\u044C \u043A\u043E\u0434\u0430",
|
|
33076
|
+
"secure code",
|
|
33077
|
+
"vulnerability"
|
|
33078
|
+
]),
|
|
32923
33079
|
skill("review-performance", "review", ["recommended", "full"], "Review hot paths, unnecessary work, bundle/perf regressions, blocking operations, and memory risk.", [
|
|
32924
33080
|
"Find changed hot paths and repeated operations.",
|
|
32925
33081
|
"Check loops, rendering, async blocking, large imports, and caching behavior.",
|
|
@@ -33055,7 +33211,12 @@ var BUNDLED_GDSKILLS = [
|
|
|
33055
33211
|
"Detect deployment target and required pre-flight checks.",
|
|
33056
33212
|
"Run verification before deployment unless explicitly skipped.",
|
|
33057
33213
|
"Summarize deployment status, rollback path, and health checks."
|
|
33058
|
-
], [
|
|
33214
|
+
], [
|
|
33215
|
+
"deploy",
|
|
33216
|
+
"deployment",
|
|
33217
|
+
"ship",
|
|
33218
|
+
"release"
|
|
33219
|
+
]),
|
|
33059
33220
|
skill("commit", "quality", ["full"], "Prepare conventional commits with scope, summary, and verification notes.", [
|
|
33060
33221
|
"Inspect staged and unstaged changes.",
|
|
33061
33222
|
"Group related changes without staging unrelated work.",
|
|
@@ -39526,8 +39687,56 @@ init_guard();
|
|
|
39526
39687
|
import { mkdir as mkdir16, readFile as readFile23, writeFile as writeFile18 } from "fs/promises";
|
|
39527
39688
|
import path46 from "path";
|
|
39528
39689
|
|
|
39690
|
+
// src/lib/bounded-stdin.ts
|
|
39691
|
+
async function readStdinBounded(deadlineMs) {
|
|
39692
|
+
if (process.stdin.isTTY) {
|
|
39693
|
+
return null;
|
|
39694
|
+
}
|
|
39695
|
+
const reader = Bun.stdin.stream().getReader();
|
|
39696
|
+
const timer = setTimeout(() => void reader.cancel().catch(() => {}), deadlineMs);
|
|
39697
|
+
timer.unref?.();
|
|
39698
|
+
const chunks = [];
|
|
39699
|
+
try {
|
|
39700
|
+
for (;; ) {
|
|
39701
|
+
const { done, value } = await reader.read();
|
|
39702
|
+
if (done)
|
|
39703
|
+
break;
|
|
39704
|
+
if (value)
|
|
39705
|
+
chunks.push(Buffer.from(value));
|
|
39706
|
+
}
|
|
39707
|
+
} catch {
|
|
39708
|
+
return null;
|
|
39709
|
+
} finally {
|
|
39710
|
+
clearTimeout(timer);
|
|
39711
|
+
reader.releaseLock?.();
|
|
39712
|
+
}
|
|
39713
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
39714
|
+
}
|
|
39715
|
+
|
|
39529
39716
|
// src/ctx/hook-classify.ts
|
|
39530
39717
|
var ESCAPE_MARKER = /#\s*keryx:raw\b[ \t]*([^\n]*)/i;
|
|
39718
|
+
function escapeReasonOf(command) {
|
|
39719
|
+
let quote = null;
|
|
39720
|
+
for (let i = 0;i < command.length; i += 1) {
|
|
39721
|
+
const char = command[i];
|
|
39722
|
+
if (quote) {
|
|
39723
|
+
if (char === quote)
|
|
39724
|
+
quote = null;
|
|
39725
|
+
continue;
|
|
39726
|
+
}
|
|
39727
|
+
if (char === "'" || char === '"') {
|
|
39728
|
+
quote = char;
|
|
39729
|
+
continue;
|
|
39730
|
+
}
|
|
39731
|
+
if (char !== "#")
|
|
39732
|
+
continue;
|
|
39733
|
+
const match = ESCAPE_MARKER.exec(command.slice(i));
|
|
39734
|
+
if (match && match.index === 0) {
|
|
39735
|
+
return (match[1] ?? "").trim();
|
|
39736
|
+
}
|
|
39737
|
+
}
|
|
39738
|
+
return null;
|
|
39739
|
+
}
|
|
39531
39740
|
var LEADING_SKIP = new Set(["sudo", "command", "time", "nice", "env", "builtin"]);
|
|
39532
39741
|
var ALREADY_ROUTED = new Set(["keryx", "rtk"]);
|
|
39533
39742
|
var ROUTES = [
|
|
@@ -39535,11 +39744,118 @@ var ROUTES = [
|
|
|
39535
39744
|
{ names: /^(cat|head|tail)$/, suggestion: "keryx ctx read <file> --mode compact" }
|
|
39536
39745
|
];
|
|
39537
39746
|
var GIT_ROUTABLE = /^(diff|log|show)$/;
|
|
39538
|
-
function
|
|
39539
|
-
return command.split(
|
|
39747
|
+
function statements(command) {
|
|
39748
|
+
return command.split(/\|\||&&|;|\n/).map((s) => s.trim()).filter(Boolean);
|
|
39749
|
+
}
|
|
39750
|
+
function stages(command) {
|
|
39751
|
+
const out = [];
|
|
39752
|
+
for (const statement of statements(command)) {
|
|
39753
|
+
const parts = splitPipeline(statement);
|
|
39754
|
+
parts.forEach((text, index) => {
|
|
39755
|
+
const trimmed = text.trim();
|
|
39756
|
+
if (trimmed)
|
|
39757
|
+
out.push({ text: trimmed, isFirst: index === 0 });
|
|
39758
|
+
});
|
|
39759
|
+
}
|
|
39760
|
+
return out;
|
|
39761
|
+
}
|
|
39762
|
+
function splitPipeline(statement) {
|
|
39763
|
+
const parts = [];
|
|
39764
|
+
let current = "";
|
|
39765
|
+
let quote = null;
|
|
39766
|
+
for (const char of statement) {
|
|
39767
|
+
if (quote) {
|
|
39768
|
+
if (char === quote)
|
|
39769
|
+
quote = null;
|
|
39770
|
+
current += char;
|
|
39771
|
+
continue;
|
|
39772
|
+
}
|
|
39773
|
+
if (char === "'" || char === '"') {
|
|
39774
|
+
quote = char;
|
|
39775
|
+
current += char;
|
|
39776
|
+
continue;
|
|
39777
|
+
}
|
|
39778
|
+
if (char === "|") {
|
|
39779
|
+
parts.push(current);
|
|
39780
|
+
current = "";
|
|
39781
|
+
continue;
|
|
39782
|
+
}
|
|
39783
|
+
current += char;
|
|
39784
|
+
}
|
|
39785
|
+
parts.push(current);
|
|
39786
|
+
return parts;
|
|
39787
|
+
}
|
|
39788
|
+
function stageTokens(stage) {
|
|
39789
|
+
return (stage.match(/'[^']*'|"[^"]*"|\S+/g) ?? []).filter(Boolean);
|
|
39790
|
+
}
|
|
39791
|
+
var VALUE_FLAGS = {
|
|
39792
|
+
search: new Set(["-e", "--regexp", "-f", "--file", "-m", "--max-count", "-A", "-B", "-C", "--context"]),
|
|
39793
|
+
head: new Set(["-n", "--lines", "-c", "--bytes"]),
|
|
39794
|
+
tail: new Set(["-n", "--lines", "-c", "--bytes"]),
|
|
39795
|
+
sed: new Set(["-e", "--expression", "-f", "--file"]),
|
|
39796
|
+
awk: new Set(["-f", "-v"])
|
|
39797
|
+
};
|
|
39798
|
+
function valueFlagsFor(command, searchLike) {
|
|
39799
|
+
return searchLike ? VALUE_FLAGS.search : VALUE_FLAGS[command] ?? new Set;
|
|
39800
|
+
}
|
|
39801
|
+
function readsStdin(tokens, isFirst) {
|
|
39802
|
+
const [command, ...rest] = tokens;
|
|
39803
|
+
if (!command) {
|
|
39804
|
+
return true;
|
|
39805
|
+
}
|
|
39806
|
+
if (/^(find|ls|git)$/.test(command)) {
|
|
39807
|
+
return false;
|
|
39808
|
+
}
|
|
39809
|
+
if (/^(rg|ripgrep)$/.test(command) && isFirst) {
|
|
39810
|
+
return false;
|
|
39811
|
+
}
|
|
39812
|
+
const searchLike = /^(rg|grep|egrep|fgrep|ripgrep)$/.test(command);
|
|
39813
|
+
const valueFlags = valueFlagsFor(command, searchLike);
|
|
39814
|
+
let operandsAllowed = searchLike || /^(sed|awk)$/.test(command) ? 1 : 0;
|
|
39815
|
+
for (let i = 0;i < rest.length; i += 1) {
|
|
39816
|
+
const token = rest[i] ?? "";
|
|
39817
|
+
const [flag, attached] = splitFlagValue(token);
|
|
39818
|
+
if (operandsAllowed > 0 && PATTERN_FLAGS.has(flag)) {
|
|
39819
|
+
operandsAllowed = 0;
|
|
39820
|
+
if (attached === null)
|
|
39821
|
+
i += 1;
|
|
39822
|
+
continue;
|
|
39823
|
+
}
|
|
39824
|
+
if (valueFlags.has(flag)) {
|
|
39825
|
+
if (attached === null)
|
|
39826
|
+
i += 1;
|
|
39827
|
+
continue;
|
|
39828
|
+
}
|
|
39829
|
+
if (token.startsWith("-")) {
|
|
39830
|
+
if (searchLike && isRecursiveFlag(token)) {
|
|
39831
|
+
return false;
|
|
39832
|
+
}
|
|
39833
|
+
continue;
|
|
39834
|
+
}
|
|
39835
|
+
if (operandsAllowed > 0) {
|
|
39836
|
+
operandsAllowed -= 1;
|
|
39837
|
+
continue;
|
|
39838
|
+
}
|
|
39839
|
+
return false;
|
|
39840
|
+
}
|
|
39841
|
+
return true;
|
|
39842
|
+
}
|
|
39843
|
+
var PATTERN_FLAGS = new Set(["-e", "--regexp", "-f", "--file", "--expression"]);
|
|
39844
|
+
function splitFlagValue(token) {
|
|
39845
|
+
if (token.startsWith("--")) {
|
|
39846
|
+
const eq = token.indexOf("=");
|
|
39847
|
+
return eq === -1 ? [token, null] : [token.slice(0, eq), token.slice(eq + 1)];
|
|
39848
|
+
}
|
|
39849
|
+
if (token.startsWith("-") && token.length > 2) {
|
|
39850
|
+
return [token.slice(0, 2), token.slice(2)];
|
|
39851
|
+
}
|
|
39852
|
+
return [token, null];
|
|
39853
|
+
}
|
|
39854
|
+
function isRecursiveFlag(token) {
|
|
39855
|
+
return token === "--recursive" || token === "--dereference-recursive" || /^-[a-zA-Z]*[rR]/.test(token);
|
|
39540
39856
|
}
|
|
39541
39857
|
function leadingTokens(segment) {
|
|
39542
|
-
const tokens = segment
|
|
39858
|
+
const tokens = stageTokens(segment);
|
|
39543
39859
|
let i = 0;
|
|
39544
39860
|
while (i < tokens.length) {
|
|
39545
39861
|
const token = tokens[i] ?? "";
|
|
@@ -39555,12 +39871,15 @@ function classifyCommand(command) {
|
|
|
39555
39871
|
if (!command.trim()) {
|
|
39556
39872
|
return { block: false };
|
|
39557
39873
|
}
|
|
39558
|
-
const
|
|
39559
|
-
if (
|
|
39560
|
-
return { block: false, escapeReason
|
|
39874
|
+
const escapeReason = escapeReasonOf(command);
|
|
39875
|
+
if (escapeReason !== null) {
|
|
39876
|
+
return { block: false, escapeReason };
|
|
39561
39877
|
}
|
|
39562
|
-
for (const
|
|
39563
|
-
const tokens = leadingTokens(
|
|
39878
|
+
for (const stage of stages(command)) {
|
|
39879
|
+
const tokens = leadingTokens(stage.text);
|
|
39880
|
+
if (readsStdin(tokens, stage.isFirst)) {
|
|
39881
|
+
continue;
|
|
39882
|
+
}
|
|
39564
39883
|
const first = tokens[0];
|
|
39565
39884
|
if (!first || ALREADY_ROUTED.has(first)) {
|
|
39566
39885
|
continue;
|
|
@@ -39663,18 +39982,30 @@ function stripFromHookArray(settings, key) {
|
|
|
39663
39982
|
removeSentinel(settings);
|
|
39664
39983
|
return settings;
|
|
39665
39984
|
}
|
|
39666
|
-
function
|
|
39667
|
-
const
|
|
39668
|
-
const
|
|
39669
|
-
return
|
|
39670
|
-
|
|
39985
|
+
function groupContainer(settings, runtime) {
|
|
39986
|
+
const container = runtime.groupContainer ? settings[runtime.groupContainer] : settings.hooks;
|
|
39987
|
+
const list2 = container?.[runtime.groupKey];
|
|
39988
|
+
return Array.isArray(list2) ? list2 : [];
|
|
39989
|
+
}
|
|
39990
|
+
function managedGroupsFor(settings, runtime) {
|
|
39991
|
+
const command = hookCommand(runtime.id);
|
|
39992
|
+
return groupContainer(settings, runtime).filter((group) => {
|
|
39993
|
+
if (!isManagedGroup2(group))
|
|
39671
39994
|
return false;
|
|
39672
|
-
const
|
|
39673
|
-
if (
|
|
39674
|
-
return
|
|
39675
|
-
|
|
39995
|
+
const entry = group;
|
|
39996
|
+
if (runtime.groupShape === "flat") {
|
|
39997
|
+
return entry.command === command;
|
|
39998
|
+
}
|
|
39999
|
+
return Array.isArray(entry.hooks) && entry.hooks.some((hook) => hook?.command === command && hook?.type === "command");
|
|
39676
40000
|
});
|
|
39677
40001
|
}
|
|
40002
|
+
function hasRunnableGuard(settings, runtime) {
|
|
40003
|
+
return managedGroupsFor(settings, runtime).length > 0;
|
|
40004
|
+
}
|
|
40005
|
+
function hasStalePreToolUseMatcher(settings, runtime) {
|
|
40006
|
+
const expected = preToolUseMatcher(runtime);
|
|
40007
|
+
return managedGroupsFor(settings, runtime).some((group) => typeof group.matcher !== "string" || group.matcher !== expected);
|
|
40008
|
+
}
|
|
39678
40009
|
function parseJson(payload) {
|
|
39679
40010
|
try {
|
|
39680
40011
|
const parsed = JSON.parse(payload);
|
|
@@ -39693,6 +40024,22 @@ function parseToolInputCommand(payload) {
|
|
|
39693
40024
|
const command = input2.command;
|
|
39694
40025
|
return typeof command === "string" ? command : null;
|
|
39695
40026
|
}
|
|
40027
|
+
function parseToolName(payload) {
|
|
40028
|
+
const record = parseJson(payload);
|
|
40029
|
+
const name = record?.tool_name;
|
|
40030
|
+
return typeof name === "string" ? name : null;
|
|
40031
|
+
}
|
|
40032
|
+
function nativeSearchMessage(tool) {
|
|
40033
|
+
return [
|
|
40034
|
+
`[keryx ctx] The \`${tool}\` tool searches project code without the gdctx routing layer.`,
|
|
40035
|
+
`Use instead: keryx ctx rg "<pattern>" [path]`,
|
|
40036
|
+
`The routed form is compressed and recorded in the routing audit (ctx_used).`,
|
|
40037
|
+
`Structural question (callers, usages, blast radius)? Use gdgraph first.`,
|
|
40038
|
+
`If raw output is genuinely required, run it through Bash with an escape marker:`,
|
|
40039
|
+
` rg "<pattern>" <path> # keryx:raw <why raw is needed>`
|
|
40040
|
+
].join(`
|
|
40041
|
+
`);
|
|
40042
|
+
}
|
|
39696
40043
|
function parseCursorCommand(payload) {
|
|
39697
40044
|
const record = parseJson(payload);
|
|
39698
40045
|
const command = record?.command;
|
|
@@ -39763,41 +40110,73 @@ function antigravityAllow(_c) {
|
|
|
39763
40110
|
return { exitCode: 0, stdout: `${JSON.stringify({ allow_tool: true })}
|
|
39764
40111
|
` };
|
|
39765
40112
|
}
|
|
39766
|
-
function
|
|
40113
|
+
function preToolUseMatcher(runtime) {
|
|
40114
|
+
return ["Bash", ...runtime.nativeSearchTools ?? []].join("|");
|
|
40115
|
+
}
|
|
40116
|
+
function validatePreToolUse(settings, runtime) {
|
|
40117
|
+
const command = hookCommand(runtime.id);
|
|
40118
|
+
const expected = preToolUseMatcher(runtime);
|
|
40119
|
+
if (!hasRunnableGuard(settings, runtime)) {
|
|
40120
|
+
return [`${runtime.id}: missing PreToolUse(${expected}) guard`];
|
|
40121
|
+
}
|
|
40122
|
+
if (hasStalePreToolUseMatcher(settings, runtime)) {
|
|
40123
|
+
return [
|
|
40124
|
+
`${runtime.id}: PreToolUse guard does not match ${expected}; a tool it should cover bypasses it. ` + `Re-run \`keryx ctx install-hook --runtime ${runtime.id}\`.`
|
|
40125
|
+
];
|
|
40126
|
+
}
|
|
40127
|
+
return [];
|
|
40128
|
+
}
|
|
40129
|
+
function describeExistingGuard(settings, runtime) {
|
|
40130
|
+
const command = hookCommand(runtime.id);
|
|
40131
|
+
if (!hasRunnableGuard(settings, runtime))
|
|
40132
|
+
return null;
|
|
40133
|
+
if (!hasStalePreToolUseMatcher(settings, runtime))
|
|
40134
|
+
return null;
|
|
40135
|
+
const found = managedGroupsFor(settings, runtime).map((group) => typeof group.matcher === "string" ? group.matcher : "(no matcher)").join(", ");
|
|
40136
|
+
return `upgraded an existing guard: ${found} -> ${preToolUseMatcher(runtime)}`;
|
|
40137
|
+
}
|
|
40138
|
+
function preToolUseGroup(runtime) {
|
|
39767
40139
|
return {
|
|
39768
|
-
matcher:
|
|
39769
|
-
hooks: [{ type: "command", command: hookCommand(id) }],
|
|
40140
|
+
matcher: preToolUseMatcher(runtime),
|
|
40141
|
+
hooks: [{ type: "command", command: hookCommand(runtime.id) }],
|
|
39770
40142
|
[MANAGED_KEY2]: CTX_HOOK_SENTINEL
|
|
39771
40143
|
};
|
|
39772
40144
|
}
|
|
39773
40145
|
var CLAUDE_RUNTIME3 = {
|
|
39774
40146
|
id: "claude",
|
|
39775
|
-
label: ".claude/settings.json (PreToolUse
|
|
40147
|
+
label: ".claude/settings.json (PreToolUse)",
|
|
39776
40148
|
confidence: "verified",
|
|
40149
|
+
groupShape: "nested",
|
|
40150
|
+
groupKey: "PreToolUse",
|
|
40151
|
+
nativeSearchTools: ["Grep"],
|
|
39777
40152
|
parseCommand: parseToolInputCommand,
|
|
39778
40153
|
block: exitCodeBlock,
|
|
39779
40154
|
allow: exitCodeAllow,
|
|
39780
40155
|
locate: (root) => path44.join(root, ".claude", "settings.json"),
|
|
39781
|
-
merge: (s) => mergeIntoHookArray(s, "PreToolUse", preToolUseGroup(
|
|
40156
|
+
merge: (s) => mergeIntoHookArray(s, "PreToolUse", preToolUseGroup(CLAUDE_RUNTIME3)),
|
|
39782
40157
|
strip: (s) => stripFromHookArray(s, "PreToolUse"),
|
|
39783
|
-
validate: (s) =>
|
|
40158
|
+
validate: (s) => validatePreToolUse(s, CLAUDE_RUNTIME3)
|
|
39784
40159
|
};
|
|
39785
40160
|
var CODEX_RUNTIME = {
|
|
39786
40161
|
id: "codex",
|
|
39787
|
-
label: ".codex/hooks.json (PreToolUse
|
|
40162
|
+
label: ".codex/hooks.json (PreToolUse)",
|
|
39788
40163
|
confidence: "verified",
|
|
40164
|
+
groupShape: "nested",
|
|
40165
|
+
groupKey: "PreToolUse",
|
|
39789
40166
|
parseCommand: parseToolInputCommand,
|
|
39790
40167
|
block: exitCodeBlock,
|
|
39791
40168
|
allow: exitCodeAllow,
|
|
39792
40169
|
locate: (root) => path44.join(root, ".codex", "hooks.json"),
|
|
39793
|
-
merge: (s) => mergeIntoHookArray(s, "PreToolUse", preToolUseGroup(
|
|
40170
|
+
merge: (s) => mergeIntoHookArray(s, "PreToolUse", preToolUseGroup(CODEX_RUNTIME)),
|
|
39794
40171
|
strip: (s) => stripFromHookArray(s, "PreToolUse"),
|
|
39795
|
-
validate: (s) =>
|
|
40172
|
+
validate: (s) => validatePreToolUse(s, CODEX_RUNTIME)
|
|
39796
40173
|
};
|
|
39797
40174
|
var CURSOR_RUNTIME2 = {
|
|
39798
40175
|
id: "cursor",
|
|
39799
40176
|
label: ".cursor/hooks.json (beforeShellExecution)",
|
|
39800
40177
|
confidence: "verified",
|
|
40178
|
+
groupShape: "flat",
|
|
40179
|
+
groupKey: "beforeShellExecution",
|
|
39801
40180
|
parseCommand: parseCursorCommand,
|
|
39802
40181
|
block: cursorBlock,
|
|
39803
40182
|
allow: cursorAllow,
|
|
@@ -39810,12 +40189,14 @@ var CURSOR_RUNTIME2 = {
|
|
|
39810
40189
|
});
|
|
39811
40190
|
},
|
|
39812
40191
|
strip: (s) => stripFromHookArray(s, "beforeShellExecution"),
|
|
39813
|
-
validate: (s) =>
|
|
40192
|
+
validate: (s) => hasRunnableGuard(s, CURSOR_RUNTIME2) ? [] : ["cursor: missing beforeShellExecution guard"]
|
|
39814
40193
|
};
|
|
39815
40194
|
var WINDSURF_RUNTIME = {
|
|
39816
40195
|
id: "windsurf",
|
|
39817
40196
|
label: ".windsurf/hooks.json (pre_run_command)",
|
|
39818
40197
|
confidence: "verified",
|
|
40198
|
+
groupShape: "flat",
|
|
40199
|
+
groupKey: "pre_run_command",
|
|
39819
40200
|
parseCommand: parseWindsurfCommand,
|
|
39820
40201
|
block: exitCodeBlock,
|
|
39821
40202
|
allow: exitCodeAllow,
|
|
@@ -39826,12 +40207,15 @@ var WINDSURF_RUNTIME = {
|
|
|
39826
40207
|
[MANAGED_KEY2]: CTX_HOOK_SENTINEL
|
|
39827
40208
|
}),
|
|
39828
40209
|
strip: (s) => stripFromHookArray(s, "pre_run_command"),
|
|
39829
|
-
validate: (s) =>
|
|
40210
|
+
validate: (s) => hasRunnableGuard(s, WINDSURF_RUNTIME) ? [] : ["windsurf: missing pre_run_command guard"]
|
|
39830
40211
|
};
|
|
39831
40212
|
var ANTIGRAVITY_RUNTIME = {
|
|
39832
40213
|
id: "antigravity",
|
|
39833
40214
|
label: ".agents/hooks.json (PreToolUse/run_command)",
|
|
39834
40215
|
confidence: "experimental",
|
|
40216
|
+
groupShape: "nested",
|
|
40217
|
+
groupKey: "PreToolUse",
|
|
40218
|
+
groupContainer: "keryx-ctx-guard",
|
|
39835
40219
|
parseCommand: parseAntigravityCommand,
|
|
39836
40220
|
block: antigravityBlock,
|
|
39837
40221
|
allow: antigravityAllow,
|
|
@@ -39864,12 +40248,7 @@ var ANTIGRAVITY_RUNTIME = {
|
|
|
39864
40248
|
removeSentinel(s);
|
|
39865
40249
|
return s;
|
|
39866
40250
|
},
|
|
39867
|
-
validate: (s) =>
|
|
39868
|
-
const group = s["keryx-ctx-guard"];
|
|
39869
|
-
const list2 = Array.isArray(group?.PreToolUse) ? group?.PreToolUse : [];
|
|
39870
|
-
const ok = list2.some((g) => isManagedGroup2(g) && Array.isArray(g.hooks) && g.hooks.some((h) => h?.command === hookCommand("antigravity")));
|
|
39871
|
-
return ok ? [] : ["antigravity: missing run_command guard"];
|
|
39872
|
-
}
|
|
40251
|
+
validate: (s) => hasRunnableGuard(s, ANTIGRAVITY_RUNTIME) ? [] : ["antigravity: missing run_command guard"]
|
|
39873
40252
|
};
|
|
39874
40253
|
var OPENCODE_PLUGIN = `// keryx gdctx routing guard \u2014 generated by \`keryx ctx install-hook --runtime opencode\`.
|
|
39875
40254
|
// Bridges OpenCode's tool.execute.before to \`keryx ctx hook opencode\`.
|
|
@@ -39892,6 +40271,8 @@ var OPENCODE_RUNTIME2 = {
|
|
|
39892
40271
|
id: "opencode",
|
|
39893
40272
|
label: ".opencode/plugin/keryx-ctx-guard.js",
|
|
39894
40273
|
confidence: "experimental",
|
|
40274
|
+
groupShape: "nested",
|
|
40275
|
+
groupKey: "PreToolUse",
|
|
39895
40276
|
parseCommand: parseToolInputCommand,
|
|
39896
40277
|
block: exitCodeBlock,
|
|
39897
40278
|
allow: exitCodeAllow,
|
|
@@ -39947,15 +40328,9 @@ function resolveRuntimes2(ids) {
|
|
|
39947
40328
|
return { runtimes, unknown, unsupported };
|
|
39948
40329
|
}
|
|
39949
40330
|
// src/ctx/hook.ts
|
|
40331
|
+
var STDIN_DEADLINE_MS = 2000;
|
|
39950
40332
|
async function readStdin() {
|
|
39951
|
-
|
|
39952
|
-
return "";
|
|
39953
|
-
}
|
|
39954
|
-
const chunks = [];
|
|
39955
|
-
for await (const chunk of process.stdin) {
|
|
39956
|
-
chunks.push(Buffer.from(chunk));
|
|
39957
|
-
}
|
|
39958
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
40333
|
+
return await readStdinBounded(STDIN_DEADLINE_MS) ?? "";
|
|
39959
40334
|
}
|
|
39960
40335
|
async function runCtxHook(runtimeId) {
|
|
39961
40336
|
const runtime = getRuntime2(runtimeId ?? "claude");
|
|
@@ -39965,10 +40340,17 @@ async function runCtxHook(runtimeId) {
|
|
|
39965
40340
|
const payload = await readStdin();
|
|
39966
40341
|
const command = runtime.parseCommand(payload);
|
|
39967
40342
|
if (command === null) {
|
|
40343
|
+
const nativeSearch = matchedNativeSearch(runtime.nativeSearchTools, payload);
|
|
40344
|
+
if (nativeSearch) {
|
|
40345
|
+
emit(refusalAction(runtime.id, nativeSearchMessage(nativeSearch)));
|
|
40346
|
+
}
|
|
39968
40347
|
return;
|
|
39969
40348
|
}
|
|
39970
40349
|
const classification = classifyCommand(command);
|
|
39971
40350
|
const action = classification.block ? runtime.block(command, classification) : runtime.allow(classification);
|
|
40351
|
+
emit(action);
|
|
40352
|
+
}
|
|
40353
|
+
function emit(action) {
|
|
39972
40354
|
if (action.stdout)
|
|
39973
40355
|
process.stdout.write(action.stdout);
|
|
39974
40356
|
if (action.stderr)
|
|
@@ -39976,6 +40358,13 @@ async function runCtxHook(runtimeId) {
|
|
|
39976
40358
|
if (action.exitCode)
|
|
39977
40359
|
process.exitCode = action.exitCode;
|
|
39978
40360
|
}
|
|
40361
|
+
function matchedNativeSearch(tools, payload) {
|
|
40362
|
+
if (!tools || tools.length === 0) {
|
|
40363
|
+
return null;
|
|
40364
|
+
}
|
|
40365
|
+
const name = parseToolName(payload);
|
|
40366
|
+
return name && tools.includes(name) ? name : null;
|
|
40367
|
+
}
|
|
39979
40368
|
|
|
39980
40369
|
// src/ctx/hook-install.ts
|
|
39981
40370
|
init_fs();
|
|
@@ -40012,9 +40401,10 @@ async function installRuntimeHook(projectRoot, runtime) {
|
|
|
40012
40401
|
return { path: file, errors: [`${runtime.id}: no installer defined`] };
|
|
40013
40402
|
}
|
|
40014
40403
|
const settings = await readSettings3(file);
|
|
40404
|
+
const upgraded = describeExistingGuard(settings, runtime);
|
|
40015
40405
|
await writeSettings3(file, merge(settings));
|
|
40016
40406
|
const errors = validate(await readSettings3(file));
|
|
40017
|
-
return { path: file, errors };
|
|
40407
|
+
return { path: file, errors, ...upgraded ? { upgraded } : {} };
|
|
40018
40408
|
}
|
|
40019
40409
|
async function uninstallRuntimeHook(projectRoot, runtime) {
|
|
40020
40410
|
if (runtime.customUninstall) {
|
|
@@ -40257,7 +40647,7 @@ async function handleInstallHook(args) {
|
|
|
40257
40647
|
console.log("escape: append `# keryx:raw <reason>` to run a raw command anyway");
|
|
40258
40648
|
console.log("");
|
|
40259
40649
|
for (const runtime of runtimes) {
|
|
40260
|
-
const { path: file, errors } = await installRuntimeHook(cwd, runtime);
|
|
40650
|
+
const { path: file, errors, upgraded } = await installRuntimeHook(cwd, runtime);
|
|
40261
40651
|
const tag = runtime.confidence === "experimental" ? " (experimental \u2014 verify on a live install)" : "";
|
|
40262
40652
|
if (errors.length > 0) {
|
|
40263
40653
|
for (const error of errors)
|
|
@@ -40265,6 +40655,8 @@ async function handleInstallHook(args) {
|
|
|
40265
40655
|
process.exitCode = 1;
|
|
40266
40656
|
} else {
|
|
40267
40657
|
console.log(` \u2713 ${runtime.id} -> ${path46.relative(cwd, file)}${tag}`);
|
|
40658
|
+
if (upgraded)
|
|
40659
|
+
console.log(` ${upgraded}`);
|
|
40268
40660
|
}
|
|
40269
40661
|
}
|
|
40270
40662
|
reportUnsupported(unsupported);
|
|
@@ -43719,6 +44111,47 @@ async function routeProjectSkills(args2) {
|
|
|
43719
44111
|
}
|
|
43720
44112
|
console.log(`Next: ${matches2[0]?.next}`);
|
|
43721
44113
|
}
|
|
44114
|
+
var INFLECTIONS = ["s", "es", "ed", "d", "ing", "er", "ers", "ion", "ions", "ation"];
|
|
44115
|
+
var MIN_STEM = 4;
|
|
44116
|
+
function containsPhrase(text, phrase) {
|
|
44117
|
+
const words = text.split(" ").filter(Boolean);
|
|
44118
|
+
const target = phrase.split(" ").filter(Boolean);
|
|
44119
|
+
if (target.length === 0)
|
|
44120
|
+
return false;
|
|
44121
|
+
for (let i = 0;i + target.length <= words.length; i += 1) {
|
|
44122
|
+
let matched = true;
|
|
44123
|
+
for (let j = 0;j < target.length; j += 1) {
|
|
44124
|
+
const word = words[i + j];
|
|
44125
|
+
const want = target[j];
|
|
44126
|
+
const last = j === target.length - 1;
|
|
44127
|
+
if (word === want)
|
|
44128
|
+
continue;
|
|
44129
|
+
if (last && matchesInflected(word, want))
|
|
44130
|
+
continue;
|
|
44131
|
+
matched = false;
|
|
44132
|
+
break;
|
|
44133
|
+
}
|
|
44134
|
+
if (matched)
|
|
44135
|
+
return true;
|
|
44136
|
+
}
|
|
44137
|
+
return false;
|
|
44138
|
+
}
|
|
44139
|
+
function matchesInflected(word, stem) {
|
|
44140
|
+
if (stem.length < MIN_STEM || !word.startsWith(stem))
|
|
44141
|
+
return false;
|
|
44142
|
+
return INFLECTIONS.includes(word.slice(stem.length));
|
|
44143
|
+
}
|
|
44144
|
+
function triggerFires(trigger, normalizedQuery, queryWords, queryTokens) {
|
|
44145
|
+
if (containsPhrase(normalizedQuery, trigger)) {
|
|
44146
|
+
return true;
|
|
44147
|
+
}
|
|
44148
|
+
const rawWords = trigger.split(" ").filter(Boolean);
|
|
44149
|
+
if (rawWords.length === 0) {
|
|
44150
|
+
return false;
|
|
44151
|
+
}
|
|
44152
|
+
const meaningful = routeTokens(trigger);
|
|
44153
|
+
return rawWords.every((word) => meaningful.has(word) ? queryTokens.has(word) : queryWords.has(word));
|
|
44154
|
+
}
|
|
43722
44155
|
function scoreBundledSkillRoute(entry, query) {
|
|
43723
44156
|
const normalizedQuery = normalizeRouteText(query);
|
|
43724
44157
|
if (!normalizedQuery) {
|
|
@@ -43741,18 +44174,13 @@ function scoreBundledSkillRoute(entry, query) {
|
|
|
43741
44174
|
score += 100;
|
|
43742
44175
|
reasons.push("exact skill");
|
|
43743
44176
|
}
|
|
43744
|
-
const
|
|
43745
|
-
|
|
43746
|
-
return true;
|
|
43747
|
-
}
|
|
43748
|
-
const triggerTokens = [...routeTokens(trigger)];
|
|
43749
|
-
return triggerTokens.length > 0 && triggerTokens.every((token) => queryTokens.has(token));
|
|
43750
|
-
});
|
|
44177
|
+
const queryWords = new Set(normalizedQuery.split(" ").filter(Boolean));
|
|
44178
|
+
const triggerHit = triggers.some((trigger) => triggerFires(trigger, normalizedQuery, queryWords, queryTokens));
|
|
43751
44179
|
if (triggerHit) {
|
|
43752
44180
|
score += 55;
|
|
43753
44181
|
reasons.push("trigger");
|
|
43754
44182
|
}
|
|
43755
|
-
if (name && normalizedQuery
|
|
44183
|
+
if (name && containsPhrase(normalizedQuery, name)) {
|
|
43756
44184
|
score += 30;
|
|
43757
44185
|
reasons.push("skill name");
|
|
43758
44186
|
}
|
|
@@ -43858,7 +44286,8 @@ var ROUTE_STOPWORDS = new Set([
|
|
|
43858
44286
|
var RU_SYNONYM_PREFIXES = [
|
|
43859
44287
|
["\u0440\u0435\u0432\u044C\u044E", ["review"]],
|
|
43860
44288
|
["\u0440\u0435\u0432\u044C\u044E\u0435\u0440", ["review", "reviewer"]],
|
|
43861
|
-
["\u043F\u0440\u043E\u0432\u0435\u0440\u043A", ["
|
|
44289
|
+
["\u043F\u0440\u043E\u0432\u0435\u0440\u043A", ["verify", "check"]],
|
|
44290
|
+
["\u043F\u0440\u043E\u0432\u0435\u0440", ["verify", "check"]],
|
|
43862
44291
|
["\u0440\u0435\u0430\u043B\u0438\u0437", ["implement"]],
|
|
43863
44292
|
["\u0438\u043C\u043F\u043B\u0435\u043C\u0435\u043D\u0442", ["implement"]],
|
|
43864
44293
|
["\u0432\u043D\u0435\u0434\u0440", ["implement"]],
|
|
@@ -43894,6 +44323,7 @@ var RU_SYNONYM_PREFIXES = [
|
|
|
43894
44323
|
["\u0441\u043E\u0437\u0434\u0430", ["create"]],
|
|
43895
44324
|
["\u0434\u0435\u043F\u043B\u043E\u0439", ["deploy"]],
|
|
43896
44325
|
["\u0440\u0430\u0437\u0432\u0435\u0440\u0442", ["deploy"]],
|
|
44326
|
+
["\u0440\u0430\u0437\u0432\u0435\u0440\u043D", ["deploy"]],
|
|
43897
44327
|
["\u0437\u0430\u0432\u0438\u0441\u0438\u043C", ["dependency", "dependencies"]],
|
|
43898
44328
|
["\u0438\u043D\u0442\u0435\u0440\u0432\u044C\u044E", ["interview"]],
|
|
43899
44329
|
["\u043E\u043F\u0440\u043E\u0441", ["interview"]],
|
|
@@ -43903,6 +44333,7 @@ var RU_SYNONYM_PREFIXES = [
|
|
|
43903
44333
|
["\u0444\u043B\u043E", ["flow"]],
|
|
43904
44334
|
["\u043E\u0440\u043A\u0435\u0441\u0442\u0440", ["orchestrator", "orchestrate"]],
|
|
43905
44335
|
["\u0430\u043D\u0430\u043B\u0438\u0437", ["analyze", "analysis"]],
|
|
44336
|
+
["\u043F\u0440\u043E\u0430\u043D\u0430\u043B\u0438\u0437", ["analyze", "analysis"]],
|
|
43906
44337
|
["\u0440\u0435\u0432\u044C\u044E\u0438\u0440", ["review"]]
|
|
43907
44338
|
];
|
|
43908
44339
|
function routeTokens(normalized, expand = false) {
|
|
@@ -46890,9 +47321,9 @@ var SNAPSHOT_DIRECTORIES = new Set(["__snapshots__"]);
|
|
|
46890
47321
|
var SNAPSHOT_SUFFIXES = [".snap"];
|
|
46891
47322
|
function classifyPath(path133, config = DEFAULT_REVIEW_SCOPE_CONFIG) {
|
|
46892
47323
|
const normalized = path133.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
46893
|
-
const
|
|
46894
|
-
const basename =
|
|
46895
|
-
const directories =
|
|
47324
|
+
const segments = normalized.split("/").filter((segment) => segment.length > 0);
|
|
47325
|
+
const basename = segments.at(-1) ?? normalized;
|
|
47326
|
+
const directories = segments.slice(0, -1);
|
|
46896
47327
|
for (const segment of directories) {
|
|
46897
47328
|
if (VENDOR_DIRECTORIES.has(segment)) {
|
|
46898
47329
|
return { reason: "vendored", detail: `vendored: path segment "${segment}/"` };
|
|
@@ -53489,9 +53920,9 @@ async function readContent(file) {
|
|
|
53489
53920
|
}
|
|
53490
53921
|
return readStdin2();
|
|
53491
53922
|
}
|
|
53492
|
-
function surfaceWarnings(warnings,
|
|
53923
|
+
function surfaceWarnings(warnings, emit3 = console.log) {
|
|
53493
53924
|
for (const warning of warnings) {
|
|
53494
|
-
|
|
53925
|
+
emit3(` ${style.yellow(symbols.bullet)} ${warning}`);
|
|
53495
53926
|
}
|
|
53496
53927
|
}
|
|
53497
53928
|
async function handleStatus(cwd) {
|
|
@@ -53835,14 +54266,14 @@ async function handleEval(cwd, args2) {
|
|
|
53835
54266
|
}
|
|
53836
54267
|
process.exitCode = gate.status === "fail" ? 1 : 0;
|
|
53837
54268
|
}
|
|
53838
|
-
function renderDecision(decision,
|
|
53839
|
-
|
|
53840
|
-
|
|
53841
|
-
|
|
53842
|
-
|
|
54269
|
+
function renderDecision(decision, emit3 = console.log) {
|
|
54270
|
+
emit3("");
|
|
54271
|
+
emit3(` gate: ${gateLabel(decision.gate)}`);
|
|
54272
|
+
emit3(` action: ${decision.action}`);
|
|
54273
|
+
emit3(` findings: ${decision.findings.length}`);
|
|
53843
54274
|
for (const finding of decision.findings.slice(0, 20)) {
|
|
53844
54275
|
const loc = finding.location?.line ? ` (line ${finding.location.line})` : "";
|
|
53845
|
-
|
|
54276
|
+
emit3(` ${severityMarker(finding.severity)} ${finding.category}/${finding.policyId} \u2192 ${finding.action}${loc}`);
|
|
53846
54277
|
}
|
|
53847
54278
|
}
|
|
53848
54279
|
function severityMarker(severity) {
|
|
@@ -53891,7 +54322,7 @@ function applyRuntimeDecision(args2, code, message2, decision, policyIsKnown) {
|
|
|
53891
54322
|
if (runtime === undefined) {
|
|
53892
54323
|
return code;
|
|
53893
54324
|
}
|
|
53894
|
-
const
|
|
54325
|
+
const emit3 = (action) => {
|
|
53895
54326
|
if (action.stdout !== undefined) {
|
|
53896
54327
|
process.stdout.write(action.stdout);
|
|
53897
54328
|
}
|
|
@@ -53903,9 +54334,9 @@ function applyRuntimeDecision(args2, code, message2, decision, policyIsKnown) {
|
|
|
53903
54334
|
const outcome = decideHookOutcome(code !== 0, decision, policyIsKnown);
|
|
53904
54335
|
switch (outcome.kind) {
|
|
53905
54336
|
case "refuse":
|
|
53906
|
-
return
|
|
54337
|
+
return emit3(refusalAction(runtime, message2));
|
|
53907
54338
|
case "approve":
|
|
53908
|
-
return
|
|
54339
|
+
return emit3(allowAction(runtime));
|
|
53909
54340
|
case "silent":
|
|
53910
54341
|
return 0;
|
|
53911
54342
|
}
|
|
@@ -63382,7 +63813,7 @@ import { spawnSync as spawnSync2 } from "child_process";
|
|
|
63382
63813
|
// package.json
|
|
63383
63814
|
var package_default = {
|
|
63384
63815
|
name: "@mrciphersmith/keryx",
|
|
63385
|
-
version: "0.2.
|
|
63816
|
+
version: "0.2.76",
|
|
63386
63817
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
63387
63818
|
private: false,
|
|
63388
63819
|
publishConfig: {
|
|
@@ -63878,9 +64309,9 @@ function createAssistantMessageStream(otui, renderer, parent) {
|
|
|
63878
64309
|
parent.add(container);
|
|
63879
64310
|
return { container, segmenter: createStreamSegmenter(), views: [], frozen: 0 };
|
|
63880
64311
|
};
|
|
63881
|
-
const paint = (m,
|
|
63882
|
-
for (let i = m.frozen;i <
|
|
63883
|
-
const segment =
|
|
64312
|
+
const paint = (m, segments, frozen) => {
|
|
64313
|
+
for (let i = m.frozen;i < segments.length; i++) {
|
|
64314
|
+
const segment = segments[i];
|
|
63884
64315
|
if (segment === undefined) {
|
|
63885
64316
|
continue;
|
|
63886
64317
|
}
|
|
@@ -63892,11 +64323,11 @@ function createAssistantMessageStream(otui, renderer, parent) {
|
|
|
63892
64323
|
existing?.destroy();
|
|
63893
64324
|
m.views[i] = createSegmentView(otui, renderer, m.container, segment);
|
|
63894
64325
|
}
|
|
63895
|
-
for (const stale of m.views.splice(
|
|
64326
|
+
for (const stale of m.views.splice(segments.length)) {
|
|
63896
64327
|
stale.destroy();
|
|
63897
64328
|
}
|
|
63898
64329
|
m.frozen = frozen;
|
|
63899
|
-
for (const segment of
|
|
64330
|
+
for (const segment of segments) {
|
|
63900
64331
|
if (segment.kind === "code") {
|
|
63901
64332
|
lastCode = { lang: segment.lang, body: segment.body };
|
|
63902
64333
|
}
|
|
@@ -63908,8 +64339,8 @@ function createAssistantMessageStream(otui, renderer, parent) {
|
|
|
63908
64339
|
return;
|
|
63909
64340
|
}
|
|
63910
64341
|
const m = message2 ??= start();
|
|
63911
|
-
const { segments
|
|
63912
|
-
paint(m,
|
|
64342
|
+
const { segments, frozen } = m.segmenter.push(chunk);
|
|
64343
|
+
paint(m, segments, frozen);
|
|
63913
64344
|
},
|
|
63914
64345
|
finalize: (text) => {
|
|
63915
64346
|
const m = message2 ?? start();
|
|
@@ -63917,9 +64348,9 @@ function createAssistantMessageStream(otui, renderer, parent) {
|
|
|
63917
64348
|
view.destroy();
|
|
63918
64349
|
}
|
|
63919
64350
|
m.frozen = 0;
|
|
63920
|
-
const
|
|
63921
|
-
paint(m,
|
|
63922
|
-
if (
|
|
64351
|
+
const segments = segmentMarkdown(text);
|
|
64352
|
+
paint(m, segments, segments.length);
|
|
64353
|
+
if (segments.length === 0) {
|
|
63923
64354
|
try {
|
|
63924
64355
|
parent.remove(m.container);
|
|
63925
64356
|
} catch {}
|
|
@@ -66702,20 +67133,20 @@ function buildContextUsage(source) {
|
|
|
66702
67133
|
const hasEstimate = source.estimateTokens !== undefined;
|
|
66703
67134
|
const total = hasEstimate ? source.estimateTokens ?? 0 : last ?? 0;
|
|
66704
67135
|
const estimated = hasEstimate || last === undefined;
|
|
66705
|
-
const
|
|
67136
|
+
const segments = [];
|
|
66706
67137
|
if (hasEstimate) {
|
|
66707
|
-
|
|
67138
|
+
segments.push({ id: "history", label: "history (est.)", tokens: source.estimateTokens ?? 0 });
|
|
66708
67139
|
}
|
|
66709
67140
|
if (lastIn !== undefined) {
|
|
66710
|
-
|
|
67141
|
+
segments.push({ id: "last-in", label: "last in", tokens: lastIn });
|
|
66711
67142
|
}
|
|
66712
67143
|
if (lastOut !== undefined) {
|
|
66713
|
-
|
|
67144
|
+
segments.push({ id: "last-out", label: "last out", tokens: lastOut });
|
|
66714
67145
|
}
|
|
66715
67146
|
return {
|
|
66716
67147
|
total,
|
|
66717
67148
|
estimated,
|
|
66718
|
-
segments
|
|
67149
|
+
segments,
|
|
66719
67150
|
bar: renderUsageBar(total),
|
|
66720
67151
|
note: total === 0 ? "No context usage yet." : "No model context window is known. The bar is relative to used tokens, not a billed limit."
|
|
66721
67152
|
};
|
|
@@ -68331,6 +68762,161 @@ function setProjectPermissionMode(projectPath, mode, dir) {
|
|
|
68331
68762
|
init_permission_mode();
|
|
68332
68763
|
init_enrich2();
|
|
68333
68764
|
|
|
68765
|
+
// src/tui/foreground-operation.ts
|
|
68766
|
+
class ForegroundOperationOwner {
|
|
68767
|
+
active;
|
|
68768
|
+
disposed = false;
|
|
68769
|
+
get signal() {
|
|
68770
|
+
if (this.active === undefined) {
|
|
68771
|
+
throw new Error("foreground operation has not started");
|
|
68772
|
+
}
|
|
68773
|
+
return this.active.controller.signal;
|
|
68774
|
+
}
|
|
68775
|
+
get isDisposed() {
|
|
68776
|
+
return this.disposed;
|
|
68777
|
+
}
|
|
68778
|
+
get isActive() {
|
|
68779
|
+
return this.active !== undefined;
|
|
68780
|
+
}
|
|
68781
|
+
accepts(token) {
|
|
68782
|
+
return !this.disposed && this.active?.token === token;
|
|
68783
|
+
}
|
|
68784
|
+
begin() {
|
|
68785
|
+
if (this.disposed) {
|
|
68786
|
+
throw new Error("foreground operation owner is disposed");
|
|
68787
|
+
}
|
|
68788
|
+
if (this.active !== undefined) {
|
|
68789
|
+
throw new Error("a foreground operation is already active");
|
|
68790
|
+
}
|
|
68791
|
+
const token = Symbol("foreground-operation");
|
|
68792
|
+
let resolveSettled;
|
|
68793
|
+
const settled = new Promise((resolve3) => {
|
|
68794
|
+
resolveSettled = resolve3;
|
|
68795
|
+
});
|
|
68796
|
+
this.active = { token, controller: new AbortController, settled, resolveSettled };
|
|
68797
|
+
return token;
|
|
68798
|
+
}
|
|
68799
|
+
cancel(reason) {
|
|
68800
|
+
const controller = this.active?.controller;
|
|
68801
|
+
if (controller !== undefined && !controller.signal.aborted) {
|
|
68802
|
+
controller.abort(reason);
|
|
68803
|
+
}
|
|
68804
|
+
}
|
|
68805
|
+
settled() {
|
|
68806
|
+
return this.active?.settled ?? Promise.resolve();
|
|
68807
|
+
}
|
|
68808
|
+
settle(token) {
|
|
68809
|
+
if (this.active?.token !== token) {
|
|
68810
|
+
return;
|
|
68811
|
+
}
|
|
68812
|
+
const active = this.active;
|
|
68813
|
+
this.active = undefined;
|
|
68814
|
+
active.resolveSettled();
|
|
68815
|
+
}
|
|
68816
|
+
dispose() {
|
|
68817
|
+
this.disposed = true;
|
|
68818
|
+
this.cancel("renderer disposed");
|
|
68819
|
+
}
|
|
68820
|
+
}
|
|
68821
|
+
function createForegroundOperationOwner() {
|
|
68822
|
+
return new ForegroundOperationOwner;
|
|
68823
|
+
}
|
|
68824
|
+
|
|
68825
|
+
class ForegroundForceHandoff {
|
|
68826
|
+
pending = [];
|
|
68827
|
+
awaitingSettlement = false;
|
|
68828
|
+
enqueue(item) {
|
|
68829
|
+
this.pending.push(item);
|
|
68830
|
+
if (this.awaitingSettlement)
|
|
68831
|
+
return false;
|
|
68832
|
+
this.awaitingSettlement = true;
|
|
68833
|
+
return true;
|
|
68834
|
+
}
|
|
68835
|
+
get isAwaitingSettlement() {
|
|
68836
|
+
return this.awaitingSettlement;
|
|
68837
|
+
}
|
|
68838
|
+
get hasPending() {
|
|
68839
|
+
return this.pending.length > 0;
|
|
68840
|
+
}
|
|
68841
|
+
takeAfterSettlement() {
|
|
68842
|
+
this.awaitingSettlement = false;
|
|
68843
|
+
return this.pending.shift();
|
|
68844
|
+
}
|
|
68845
|
+
takeNext() {
|
|
68846
|
+
return this.pending.shift();
|
|
68847
|
+
}
|
|
68848
|
+
}
|
|
68849
|
+
function createForegroundForceHandoff() {
|
|
68850
|
+
return new ForegroundForceHandoff;
|
|
68851
|
+
}
|
|
68852
|
+
function finalizeWikiForegroundOperation(state, callbacks) {
|
|
68853
|
+
if (state.disposed)
|
|
68854
|
+
return "disposed";
|
|
68855
|
+
callbacks.stopBusy();
|
|
68856
|
+
if (state.aborted)
|
|
68857
|
+
return "aborted";
|
|
68858
|
+
callbacks.complete();
|
|
68859
|
+
return "completed";
|
|
68860
|
+
}
|
|
68861
|
+
async function runAfterForegroundSettlement(owner, action) {
|
|
68862
|
+
await owner.settled();
|
|
68863
|
+
if (!owner.isDisposed) {
|
|
68864
|
+
action();
|
|
68865
|
+
}
|
|
68866
|
+
}
|
|
68867
|
+
function createForegroundAgentIoFacade(owner, token, io) {
|
|
68868
|
+
const accepts = () => owner.accepts(token);
|
|
68869
|
+
return {
|
|
68870
|
+
write: (text) => {
|
|
68871
|
+
if (accepts())
|
|
68872
|
+
io.write(text);
|
|
68873
|
+
},
|
|
68874
|
+
onHistoryChange: (kind2) => {
|
|
68875
|
+
if (accepts())
|
|
68876
|
+
io.onHistoryChange?.(kind2);
|
|
68877
|
+
},
|
|
68878
|
+
onAssistantText: (text) => {
|
|
68879
|
+
if (accepts())
|
|
68880
|
+
io.onAssistantText?.(text);
|
|
68881
|
+
},
|
|
68882
|
+
onReasoning: (text) => {
|
|
68883
|
+
if (accepts())
|
|
68884
|
+
io.onReasoning?.(text);
|
|
68885
|
+
},
|
|
68886
|
+
onUsage: (usage) => {
|
|
68887
|
+
if (accepts())
|
|
68888
|
+
io.onUsage?.(usage);
|
|
68889
|
+
},
|
|
68890
|
+
onToolCall: (name, input2) => {
|
|
68891
|
+
if (accepts())
|
|
68892
|
+
io.onToolCall?.(name, input2);
|
|
68893
|
+
},
|
|
68894
|
+
onToolResult: (name, result) => {
|
|
68895
|
+
if (accepts())
|
|
68896
|
+
io.onToolResult?.(name, result);
|
|
68897
|
+
},
|
|
68898
|
+
onSystem: (text) => {
|
|
68899
|
+
if (accepts())
|
|
68900
|
+
io.onSystem?.(text);
|
|
68901
|
+
},
|
|
68902
|
+
onTerminalState: (state) => {
|
|
68903
|
+
if (accepts())
|
|
68904
|
+
io.onTerminalState?.(state);
|
|
68905
|
+
},
|
|
68906
|
+
requestApproval: async (tool, input2, meta) => {
|
|
68907
|
+
if (!accepts())
|
|
68908
|
+
return false;
|
|
68909
|
+
const response = await io.requestApproval?.(tool, input2, meta);
|
|
68910
|
+
return accepts() ? response ?? false : false;
|
|
68911
|
+
},
|
|
68912
|
+
onAutoApproved: (tool, input2, meta) => {
|
|
68913
|
+
if (accepts())
|
|
68914
|
+
io.onAutoApproved?.(tool, input2, meta);
|
|
68915
|
+
},
|
|
68916
|
+
permissionMode: () => accepts() ? io.permissionMode?.() ?? "ask" : "ask"
|
|
68917
|
+
};
|
|
68918
|
+
}
|
|
68919
|
+
|
|
68334
68920
|
// src/tui/herdr-report.ts
|
|
68335
68921
|
import net from "net";
|
|
68336
68922
|
var SOURCE = "herdr:keryx";
|
|
@@ -72061,14 +72647,14 @@ function shortenCwd(cwd, max) {
|
|
|
72061
72647
|
if (full.length <= max) {
|
|
72062
72648
|
return full;
|
|
72063
72649
|
}
|
|
72064
|
-
const
|
|
72065
|
-
for (let i = 1;i <
|
|
72066
|
-
const candidate = `\u2026/${
|
|
72650
|
+
const segments = full.split("/").filter((s) => s.length > 0);
|
|
72651
|
+
for (let i = 1;i < segments.length; i++) {
|
|
72652
|
+
const candidate = `\u2026/${segments.slice(i).join("/")}`;
|
|
72067
72653
|
if (candidate.length <= max) {
|
|
72068
72654
|
return candidate;
|
|
72069
72655
|
}
|
|
72070
72656
|
}
|
|
72071
|
-
const last =
|
|
72657
|
+
const last = segments[segments.length - 1] ?? full;
|
|
72072
72658
|
return `\u2026${last.slice(last.length - (max - 1))}`;
|
|
72073
72659
|
}
|
|
72074
72660
|
function estimateContextTokens(history) {
|
|
@@ -72776,9 +73362,12 @@ async function launchTuiAgentShell(opts) {
|
|
|
72776
73362
|
let liveDeps;
|
|
72777
73363
|
let liveJobs;
|
|
72778
73364
|
let detachExternal;
|
|
73365
|
+
const foregroundOperation = createForegroundOperationOwner();
|
|
72779
73366
|
try {
|
|
72780
73367
|
const r = renderer = await createShellRenderer(otui, {
|
|
72781
73368
|
onDestroy: () => {
|
|
73369
|
+
foregroundOperation.cancel("renderer destroyed");
|
|
73370
|
+
foregroundOperation.dispose();
|
|
72782
73371
|
mountedChrome?.destroy();
|
|
72783
73372
|
setAskUserHost(undefined);
|
|
72784
73373
|
setSubagentFleetListener(undefined);
|
|
@@ -72842,7 +73431,6 @@ async function launchTuiAgentShell(opts) {
|
|
|
72842
73431
|
const stopBusy = () => {
|
|
72843
73432
|
chrome.stopBusy();
|
|
72844
73433
|
};
|
|
72845
|
-
let mainTurnAbortController;
|
|
72846
73434
|
const sidebar = chrome.sidebarTop;
|
|
72847
73435
|
sidebar.add(new otui.TextRenderable(r, { id: "sb-title", content: otui.t`${otui.bold("keryx")}` }));
|
|
72848
73436
|
sidebar.add(new otui.TextRenderable(r, { id: "sb-model-k", content: otui.t`${otui.dim("Model")}`, marginTop: 1 }));
|
|
@@ -73873,7 +74461,7 @@ Staying in the current session.
|
|
|
73873
74461
|
let mainQueue = [];
|
|
73874
74462
|
let mainQueueSeq = 0;
|
|
73875
74463
|
let pendingQueueEdit;
|
|
73876
|
-
|
|
74464
|
+
const forceHandoff = createForegroundForceHandoff();
|
|
73877
74465
|
let sideWorkerRunning = false;
|
|
73878
74466
|
let sideClearTimeout;
|
|
73879
74467
|
const showSideQueueStatus = () => {
|
|
@@ -74023,15 +74611,19 @@ Staying in the current session.
|
|
|
74023
74611
|
return;
|
|
74024
74612
|
mainQueue = removeMainQueueItem(mainQueue, index);
|
|
74025
74613
|
paintMainQueue();
|
|
74026
|
-
|
|
74027
|
-
|
|
74028
|
-
|
|
74029
|
-
mainTurnAbortController = undefined;
|
|
74614
|
+
(async () => {
|
|
74615
|
+
const waitsForSettlement = forceHandoff.enqueue(item);
|
|
74616
|
+
foregroundOperation.cancel("queue item forced");
|
|
74030
74617
|
io.onSystem?.(`\u25C7 main turn interrupted \u2014 q${index + 1} will run next.
|
|
74031
74618
|
`);
|
|
74032
|
-
|
|
74033
|
-
|
|
74034
|
-
|
|
74619
|
+
if (!waitsForSettlement)
|
|
74620
|
+
return;
|
|
74621
|
+
await runAfterForegroundSettlement(foregroundOperation, () => {
|
|
74622
|
+
const next = forceHandoff.takeAfterSettlement();
|
|
74623
|
+
if (next !== undefined)
|
|
74624
|
+
runLine(next.question);
|
|
74625
|
+
});
|
|
74626
|
+
})();
|
|
74035
74627
|
};
|
|
74036
74628
|
const handleQueueNavKey = (key) => {
|
|
74037
74629
|
if (!queueNavActive) {
|
|
@@ -74238,6 +74830,8 @@ Staying in the current session.
|
|
|
74238
74830
|
});
|
|
74239
74831
|
switch (decision) {
|
|
74240
74832
|
case "exit": {
|
|
74833
|
+
foregroundOperation.cancel("shell exit");
|
|
74834
|
+
foregroundOperation.dispose();
|
|
74241
74835
|
(async () => {
|
|
74242
74836
|
await closeSlateSession(slateSession, mintTimestampAttemptId);
|
|
74243
74837
|
await deps.sweepBackgroundJobs?.();
|
|
@@ -74258,8 +74852,8 @@ Staying in the current session.
|
|
|
74258
74852
|
return;
|
|
74259
74853
|
}
|
|
74260
74854
|
case "interrupt": {
|
|
74261
|
-
if (
|
|
74262
|
-
|
|
74855
|
+
if (foregroundOperation.isActive) {
|
|
74856
|
+
foregroundOperation.cancel("interrupted by user");
|
|
74263
74857
|
io.onSystem?.(`\u25C7 main turn interrupted.
|
|
74264
74858
|
`);
|
|
74265
74859
|
return;
|
|
@@ -74686,10 +75280,13 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74686
75280
|
}));
|
|
74687
75281
|
if (isWikiEnrichIntent(line)) {
|
|
74688
75282
|
const startedAt2 = Date.now();
|
|
75283
|
+
const operation2 = foregroundOperation.begin();
|
|
74689
75284
|
(async () => {
|
|
74690
75285
|
try {
|
|
74691
75286
|
startBusy("planning wiki enrich\u2026");
|
|
74692
75287
|
const plan = await planWikiEnrich(process.cwd());
|
|
75288
|
+
if (foregroundOperation.signal.aborted || foregroundOperation.isDisposed)
|
|
75289
|
+
return;
|
|
74693
75290
|
stopBusy();
|
|
74694
75291
|
const maxList = 40;
|
|
74695
75292
|
const draftLines = plan.drafts.slice(0, maxList).map((p) => ` \xB7 ${p.relativePath}`);
|
|
@@ -74716,6 +75313,8 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74716
75313
|
acceptedCount: plan.accepted.length,
|
|
74717
75314
|
total: plan.forceTargets.length
|
|
74718
75315
|
});
|
|
75316
|
+
if (foregroundOperation.signal.aborted || foregroundOperation.isDisposed)
|
|
75317
|
+
return;
|
|
74719
75318
|
input2.focus();
|
|
74720
75319
|
if (choice2 === "cancel") {
|
|
74721
75320
|
transcript.add(new otui.TextRenderable(r, {
|
|
@@ -74752,8 +75351,11 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74752
75351
|
force,
|
|
74753
75352
|
provider: currentSel.provider,
|
|
74754
75353
|
model: currentSel.model,
|
|
75354
|
+
signal: foregroundOperation.signal,
|
|
74755
75355
|
concurrency: 2,
|
|
74756
75356
|
onPage: (info) => {
|
|
75357
|
+
if (foregroundOperation.signal.aborted || foregroundOperation.isDisposed)
|
|
75358
|
+
return;
|
|
74757
75359
|
setBusyPhase(`enrich ${info.index}/${info.total} [${info.phase}] ${info.path}`);
|
|
74758
75360
|
setMainAgent("running", `${info.index}/${info.total}`);
|
|
74759
75361
|
const status = info.phase === "done" ? "done" : info.phase === "failed" ? "failed" : "running";
|
|
@@ -74766,6 +75368,8 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74766
75368
|
});
|
|
74767
75369
|
}
|
|
74768
75370
|
});
|
|
75371
|
+
if (foregroundOperation.signal.aborted || foregroundOperation.isDisposed)
|
|
75372
|
+
return;
|
|
74769
75373
|
stopBusy();
|
|
74770
75374
|
setMainAgent(result.failed > 0 && result.enriched === 0 ? "failed" : "done", `${result.enriched}ok/${result.failed}fail`);
|
|
74771
75375
|
const lines = [
|
|
@@ -74797,20 +75401,34 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74797
75401
|
saveSession();
|
|
74798
75402
|
} catch {}
|
|
74799
75403
|
} catch (cause) {
|
|
75404
|
+
if (foregroundOperation.signal.aborted || foregroundOperation.isDisposed)
|
|
75405
|
+
return;
|
|
74800
75406
|
stopBusy();
|
|
74801
75407
|
transcript.add(new otui.TextRenderable(r, {
|
|
74802
75408
|
id: `we-err${uid++}`,
|
|
74803
75409
|
content: otui.t`${otui.red(`wiki enrich failed: ${cause instanceof Error ? cause.message : String(cause)}`)}`
|
|
74804
75410
|
}));
|
|
74805
75411
|
} finally {
|
|
74806
|
-
const
|
|
74807
|
-
|
|
74808
|
-
|
|
74809
|
-
|
|
74810
|
-
|
|
74811
|
-
|
|
74812
|
-
|
|
74813
|
-
|
|
75412
|
+
const operationAborted = foregroundOperation.signal.aborted;
|
|
75413
|
+
foregroundOperation.settle(operation2);
|
|
75414
|
+
finalizeWikiForegroundOperation({ aborted: operationAborted, disposed: foregroundOperation.isDisposed }, {
|
|
75415
|
+
stopBusy,
|
|
75416
|
+
complete: () => {
|
|
75417
|
+
const secs = ((Date.now() - startedAt2) / 1000).toFixed(1);
|
|
75418
|
+
transcript.add(new otui.TextRenderable(r, {
|
|
75419
|
+
id: `w${uid++}`,
|
|
75420
|
+
content: otui.t`${otui.dim(`worked for ${secs}s`)}`,
|
|
75421
|
+
marginTop: 1
|
|
75422
|
+
}));
|
|
75423
|
+
focusComposer();
|
|
75424
|
+
if (!forceHandoff.isAwaitingSettlement) {
|
|
75425
|
+
const next = forceHandoff.takeNext() ?? mainQueue.shift();
|
|
75426
|
+
paintMainQueue();
|
|
75427
|
+
if (next !== undefined)
|
|
75428
|
+
runLine(next.question);
|
|
75429
|
+
}
|
|
75430
|
+
}
|
|
75431
|
+
});
|
|
74814
75432
|
}
|
|
74815
75433
|
})();
|
|
74816
75434
|
return;
|
|
@@ -74818,6 +75436,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74818
75436
|
fleet.clearMatching((w) => w.id !== MAIN_AGENT_ID && !isSideWorkerId(w.id));
|
|
74819
75437
|
sessions.clear();
|
|
74820
75438
|
deps.resetSubagentBudget?.();
|
|
75439
|
+
const operation = foregroundOperation.begin();
|
|
74821
75440
|
setMainAgent("running", "waiting");
|
|
74822
75441
|
startBusy("waiting for model");
|
|
74823
75442
|
const startedAt = Date.now();
|
|
@@ -74829,8 +75448,6 @@ ${formatThemeList(getThemeId())}`);
|
|
|
74829
75448
|
}
|
|
74830
75449
|
prevOnSystem?.(text);
|
|
74831
75450
|
};
|
|
74832
|
-
const controller = new AbortController;
|
|
74833
|
-
mainTurnAbortController = controller;
|
|
74834
75451
|
const suggestNextStep = async () => {
|
|
74835
75452
|
try {
|
|
74836
75453
|
const lastUser = [...history].reverse().find((m) => m.role === "user")?.content ?? "";
|
|
@@ -74853,11 +75470,14 @@ ${tail}`,
|
|
|
74853
75470
|
chrome.showSuggestion(result.text.trim().split(/\s+/).slice(0, 20).join(" "));
|
|
74854
75471
|
} catch {}
|
|
74855
75472
|
};
|
|
74856
|
-
|
|
74857
|
-
|
|
75473
|
+
const foregroundIo = createForegroundAgentIoFacade(foregroundOperation, operation, io);
|
|
75474
|
+
runAgentTurn(foregroundIo, deps, history, line, {
|
|
75475
|
+
signal: foregroundOperation.signal,
|
|
74858
75476
|
...slateSession !== undefined ? { slateSession } : {}
|
|
74859
75477
|
}).finally(() => {
|
|
74860
|
-
|
|
75478
|
+
foregroundOperation.settle(operation);
|
|
75479
|
+
if (foregroundOperation.isDisposed)
|
|
75480
|
+
return;
|
|
74861
75481
|
const secs = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
74862
75482
|
stopBusy();
|
|
74863
75483
|
refreshWorkspaceSidebar();
|
|
@@ -74873,15 +75493,11 @@ ${tail}`,
|
|
|
74873
75493
|
sbContext.content = otui.t`${otui.dim(`~${est.toLocaleString()} tokens (est)`)}`;
|
|
74874
75494
|
}
|
|
74875
75495
|
focusComposer();
|
|
74876
|
-
if (
|
|
75496
|
+
if (!forceHandoff.hasPending && mainQueue.length === 0 && !turnFailed) {
|
|
74877
75497
|
suggestNextStep();
|
|
74878
75498
|
}
|
|
74879
|
-
if (
|
|
74880
|
-
const next =
|
|
74881
|
-
priorityMainQuestion = undefined;
|
|
74882
|
-
runLine(next.question);
|
|
74883
|
-
} else if (mainQueue.length > 0) {
|
|
74884
|
-
const next = mainQueue.shift();
|
|
75499
|
+
if (!forceHandoff.isAwaitingSettlement) {
|
|
75500
|
+
const next = forceHandoff.takeNext() ?? mainQueue.shift();
|
|
74885
75501
|
paintMainQueue();
|
|
74886
75502
|
if (next !== undefined)
|
|
74887
75503
|
runLine(next.question);
|
|
@@ -78037,7 +78653,7 @@ async function runRemoteTurn(input2) {
|
|
|
78037
78653
|
let seq = 0;
|
|
78038
78654
|
createTurnRecord({ turnId, sessionId, project: input2.project, origin: REMOTE_ORIGIN, startedAt }, input2.dir);
|
|
78039
78655
|
let bounded2 = false;
|
|
78040
|
-
const
|
|
78656
|
+
const emit3 = (kind2, extra = {}) => {
|
|
78041
78657
|
const event = { schemaVersion: "1.0.0", turnId, seq, kind: kind2, at: clock(), ...extra };
|
|
78042
78658
|
if (appendTurnEvent(event, input2.dir)) {
|
|
78043
78659
|
seq += 1;
|
|
@@ -78076,7 +78692,7 @@ async function runRemoteTurn(input2) {
|
|
|
78076
78692
|
}
|
|
78077
78693
|
return { turnId, sessionId, result };
|
|
78078
78694
|
};
|
|
78079
|
-
|
|
78695
|
+
emit3("turn.started");
|
|
78080
78696
|
if (input2.profile.requiredControls.isolation === "required-fail-closed") {
|
|
78081
78697
|
const available = input2.containmentAvailable ?? (() => false);
|
|
78082
78698
|
if (!available()) {
|
|
@@ -78140,7 +78756,7 @@ async function runRemoteTurn(input2) {
|
|
|
78140
78756
|
if (event.kind === "text_delta" && typeof event.text === "string") {
|
|
78141
78757
|
const redacted = await redactOut(security, event.text);
|
|
78142
78758
|
assistantText += redacted;
|
|
78143
|
-
|
|
78759
|
+
emit3("assistant.delta", { text: redacted });
|
|
78144
78760
|
}
|
|
78145
78761
|
}
|
|
78146
78762
|
if (asked) {
|
|
@@ -78286,12 +78902,12 @@ function matchRoute(pathname) {
|
|
|
78286
78902
|
if (FIXED_ROUTES.has(pathname)) {
|
|
78287
78903
|
return { route: "fixed", pathname };
|
|
78288
78904
|
}
|
|
78289
|
-
const
|
|
78290
|
-
if (
|
|
78291
|
-
return { route: "turn", turnId:
|
|
78905
|
+
const segments = pathname.split("/");
|
|
78906
|
+
if (segments.length === 4 && segments[1] === "v1" && segments[2] === "turns") {
|
|
78907
|
+
return { route: "turn", turnId: segments[3] ?? "" };
|
|
78292
78908
|
}
|
|
78293
|
-
if (
|
|
78294
|
-
return { route: "turn-events", turnId:
|
|
78909
|
+
if (segments.length === 5 && segments[1] === "v1" && segments[2] === "turns" && segments[4] === "events") {
|
|
78910
|
+
return { route: "turn-events", turnId: segments[3] ?? "" };
|
|
78295
78911
|
}
|
|
78296
78912
|
return { route: "none" };
|
|
78297
78913
|
}
|