pi-smart-compact 9.6.2 → 9.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/app/register-context-tools.d.ts.map +1 -1
- package/dist/app/register-smart-compact-command.d.ts.map +1 -1
- package/dist/app/smart-compact-input.d.ts +1 -1
- package/dist/app/smart-compact-input.d.ts.map +1 -1
- package/dist/app/steps/synthesize.d.ts.map +1 -1
- package/dist/constants.d.ts +9 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +442 -330
- package/dist/infra/context-graph.d.ts +7 -0
- package/dist/infra/context-graph.d.ts.map +1 -1
- package/dist/phases/synthesize.d.ts.map +1 -1
- package/dist/provider-eval.js +29 -27
- package/dist/provider-scenario-eval.js +30 -28
- package/dist/telemetry-report.js +31 -29
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/settings-complex.d.ts.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __require = import.meta.require;
|
|
3
|
-
|
|
4
2
|
// src/index.ts
|
|
5
3
|
import {
|
|
6
4
|
convertToLlm as convertToLlm5
|
|
7
5
|
} from "@earendil-works/pi-coding-agent";
|
|
8
6
|
|
|
9
7
|
// src/constants.ts
|
|
10
|
-
var VERSION = "9.
|
|
8
|
+
var VERSION = "9.7.0";
|
|
11
9
|
var CHARS_PER_TOKEN = 3.8;
|
|
12
10
|
var MIN_COMPACTION_SAVING_RATIO = 0.1;
|
|
13
11
|
var ESTIMATOR_ROUNDING_TOLERANCE_TOKENS = 1;
|
|
@@ -63,16 +61,17 @@ var CONFIG_NUMERIC_LIMITS = {
|
|
|
63
61
|
maxLlmInputTokens: { min: 0, max: 1e6, integer: true },
|
|
64
62
|
codexMaxCallMs: {
|
|
65
63
|
min: 5000,
|
|
66
|
-
max:
|
|
64
|
+
max: 3600000,
|
|
67
65
|
integer: true,
|
|
68
66
|
zeroOrRange: true
|
|
69
67
|
},
|
|
70
68
|
maxLatencyMs: {
|
|
71
69
|
min: 5000,
|
|
72
|
-
max:
|
|
70
|
+
max: 7200000,
|
|
73
71
|
integer: true,
|
|
74
72
|
zeroOrRange: true
|
|
75
|
-
}
|
|
73
|
+
},
|
|
74
|
+
pendingTtlMs: { min: 1000, max: 3600000, integer: true }
|
|
76
75
|
};
|
|
77
76
|
var DEFAULT_CONFIG = {
|
|
78
77
|
mode: "auto",
|
|
@@ -98,6 +97,7 @@ var DEFAULT_CONFIG = {
|
|
|
98
97
|
maxLlmInputTokens: 0,
|
|
99
98
|
codexMaxCallMs: 0,
|
|
100
99
|
maxLatencyMs: 0,
|
|
100
|
+
pendingTtlMs: 300000,
|
|
101
101
|
focusWeighting: true,
|
|
102
102
|
zeroCallEnabled: true,
|
|
103
103
|
contextGraphEnabled: true,
|
|
@@ -351,21 +351,24 @@ import crypto from "crypto";
|
|
|
351
351
|
|
|
352
352
|
// src/utils/logger.ts
|
|
353
353
|
var DEBUG = process.env.DEBUG?.includes("smart-compact") ?? false;
|
|
354
|
+
function ts() {
|
|
355
|
+
return new Date().toISOString();
|
|
356
|
+
}
|
|
354
357
|
function warn(msg, err) {
|
|
355
358
|
const detail = err instanceof Error ? err.message : err ?? "";
|
|
356
|
-
console.error(LOG_PREFIX + " " + msg + (detail ? ": " + detail : ""));
|
|
359
|
+
console.error(ts() + " " + LOG_PREFIX + " " + msg + (detail ? ": " + detail : ""));
|
|
357
360
|
}
|
|
358
361
|
function error(msg, err) {
|
|
359
362
|
const detail = err instanceof Error ? err.message + `
|
|
360
363
|
` + err.stack : err ?? "";
|
|
361
|
-
console.error(LOG_PREFIX + " " + msg + (detail ? ": " + detail : ""));
|
|
364
|
+
console.error(ts() + " " + LOG_PREFIX + " " + msg + (detail ? ": " + detail : ""));
|
|
362
365
|
}
|
|
363
366
|
function info(msg, ...args) {
|
|
364
|
-
console.error(LOG_PREFIX + " [info] " + msg, ...args);
|
|
367
|
+
console.error(ts() + " " + LOG_PREFIX + " [info] " + msg, ...args);
|
|
365
368
|
}
|
|
366
369
|
function debug(msg, ...args) {
|
|
367
370
|
if (DEBUG)
|
|
368
|
-
console.error(LOG_PREFIX + " [debug] " + msg, ...args);
|
|
371
|
+
console.error(ts() + " " + LOG_PREFIX + " [debug] " + msg, ...args);
|
|
369
372
|
}
|
|
370
373
|
function debugError(msg, err) {
|
|
371
374
|
if (DEBUG)
|
|
@@ -420,18 +423,18 @@ function tryAcquireLock(target) {
|
|
|
420
423
|
const token = process.pid + ":" + crypto.randomBytes(8).toString("hex");
|
|
421
424
|
try {
|
|
422
425
|
fs.mkdirSync(lockDir, { mode: 448 });
|
|
423
|
-
} catch (
|
|
424
|
-
if (
|
|
426
|
+
} catch (error) {
|
|
427
|
+
if (error?.code === "EEXIST")
|
|
425
428
|
return null;
|
|
426
|
-
throw new Error("Failed to acquire lock for " + target, { cause:
|
|
429
|
+
throw new Error("Failed to acquire lock for " + target, { cause: error });
|
|
427
430
|
}
|
|
428
431
|
try {
|
|
429
432
|
fs.writeFileSync(ownerFile, token, { mode: 384, flag: "wx" });
|
|
430
|
-
} catch (
|
|
433
|
+
} catch (error) {
|
|
431
434
|
try {
|
|
432
435
|
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
433
436
|
} catch {}
|
|
434
|
-
throw new Error("Failed to acquire lock for " + target, { cause:
|
|
437
|
+
throw new Error("Failed to acquire lock for " + target, { cause: error });
|
|
435
438
|
}
|
|
436
439
|
return () => {
|
|
437
440
|
try {
|
|
@@ -474,9 +477,9 @@ async function appendLineLockedAsync(target, line, maxBytes) {
|
|
|
474
477
|
let stat = null;
|
|
475
478
|
try {
|
|
476
479
|
stat = await fsp.stat(target);
|
|
477
|
-
} catch (
|
|
478
|
-
if (!
|
|
479
|
-
throw
|
|
480
|
+
} catch (error) {
|
|
481
|
+
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT")
|
|
482
|
+
throw error;
|
|
480
483
|
}
|
|
481
484
|
if (maxBytes !== undefined && stat && stat.size + payload.length > maxBytes) {
|
|
482
485
|
const retainedLength = Math.min(stat.size, Math.max(0, maxBytes - payload.length));
|
|
@@ -879,12 +882,17 @@ var NUMERIC_RULES = [
|
|
|
879
882
|
{
|
|
880
883
|
key: "codexMaxCallMs",
|
|
881
884
|
valid: (value) => validNumericLimit("codexMaxCallMs", value),
|
|
882
|
-
message: () => "smart-compact config: codexMaxCallMs must be 0 or 5000\
|
|
885
|
+
message: () => "smart-compact config: codexMaxCallMs must be 0 or 5000\u20133600000; 0 derives a cap from maxTokens."
|
|
883
886
|
},
|
|
884
887
|
{
|
|
885
888
|
key: "maxLatencyMs",
|
|
886
889
|
valid: (value) => validNumericLimit("maxLatencyMs", value),
|
|
887
|
-
message: () => "smart-compact config: maxLatencyMs must be 0 or 5000\
|
|
890
|
+
message: () => "smart-compact config: maxLatencyMs must be 0 or 5000\u20137200000; 0 means unlimited."
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
key: "pendingTtlMs",
|
|
894
|
+
valid: (value) => validNumericLimit("pendingTtlMs", value),
|
|
895
|
+
message: () => "smart-compact config: pendingTtlMs must be 1000\u20133600000 (staged summary TTL)."
|
|
888
896
|
},
|
|
889
897
|
{
|
|
890
898
|
key: "minContextPercent",
|
|
@@ -980,8 +988,8 @@ function loadConfig() {
|
|
|
980
988
|
cachedMtime = stat.mtimeMs;
|
|
981
989
|
cachedPath = file;
|
|
982
990
|
return cloneConfig(cachedConfig);
|
|
983
|
-
} catch (
|
|
984
|
-
debug("loadConfig: settings.json not found or unreadable, using defaults",
|
|
991
|
+
} catch (error) {
|
|
992
|
+
debug("loadConfig: settings.json not found or unreadable, using defaults", error);
|
|
985
993
|
cachedConfig = defaultConfig();
|
|
986
994
|
cachedPath = null;
|
|
987
995
|
return cloneConfig(cachedConfig);
|
|
@@ -1497,8 +1505,8 @@ function buildKnownPathReferenceIndex(knownPaths) {
|
|
|
1497
1505
|
const boundarySuffixes = new Set;
|
|
1498
1506
|
const normalizedPaths = [];
|
|
1499
1507
|
let hasUnindexedSuffixes = false;
|
|
1500
|
-
for (const
|
|
1501
|
-
const normalizedPath = normalizePath(
|
|
1508
|
+
for (const path of knownPaths) {
|
|
1509
|
+
const normalizedPath = normalizePath(path).replace(/^\/+/, "");
|
|
1502
1510
|
if (!normalizedPath)
|
|
1503
1511
|
continue;
|
|
1504
1512
|
normalizedPaths.push(normalizedPath);
|
|
@@ -2077,7 +2085,7 @@ function buildExtractionContext(extraction, forRange) {
|
|
|
2077
2085
|
const files = forRange ? extraction.modifiedFiles.filter((f) => inRange(f.lastModifiedIndex)) : extraction.modifiedFiles;
|
|
2078
2086
|
const readFiles = forRange ? [] : extraction.readFiles;
|
|
2079
2087
|
const deletedFiles = forRange ? [] : extraction.deletedFiles;
|
|
2080
|
-
const errors = extraction.errors.filter((
|
|
2088
|
+
const errors = extraction.errors.filter((error) => inRange(error.index));
|
|
2081
2089
|
const decisions = extraction.decisions.filter((decision) => inRange(decision.index));
|
|
2082
2090
|
const constraints = extraction.constraints.filter((constraint) => inRange(constraint.index));
|
|
2083
2091
|
const media = (extraction.mediaAttachments ?? []).filter((attachment) => inRange(attachment.index));
|
|
@@ -2314,7 +2322,7 @@ function buildSummaryPathEvidence(paths, budgetTokens = PROFILES.balanced.summar
|
|
|
2314
2322
|
const unique = Array.from(new Set(paths.filter(Boolean)));
|
|
2315
2323
|
if (!unique.length)
|
|
2316
2324
|
return new Map;
|
|
2317
|
-
const full = unique.map((
|
|
2325
|
+
const full = unique.map((path) => [path, summaryPathLine(path)]);
|
|
2318
2326
|
const minimumPerLine = JSON.stringify("#" + "x".repeat(12)).length + 3;
|
|
2319
2327
|
const budgetChars = Math.max(unique.length * minimumPerLine, Math.min(20000, Math.max(4000, Math.floor(budgetTokens * 2))));
|
|
2320
2328
|
if (full.reduce((total, [, line]) => total + line.length + 3, 0) <= budgetChars) {
|
|
@@ -2322,21 +2330,21 @@ function buildSummaryPathEvidence(paths, budgetTokens = PROFILES.balanced.summar
|
|
|
2322
2330
|
}
|
|
2323
2331
|
const digests = new Map;
|
|
2324
2332
|
const owners = new Map;
|
|
2325
|
-
for (const
|
|
2326
|
-
const fullDigest = createHash("sha256").update(
|
|
2333
|
+
for (const path of unique) {
|
|
2334
|
+
const fullDigest = createHash("sha256").update(path).digest("base64url");
|
|
2327
2335
|
let digest = fullDigest.slice(0, 12);
|
|
2328
2336
|
const owner = owners.get(digest);
|
|
2329
|
-
if (owner && owner !==
|
|
2337
|
+
if (owner && owner !== path) {
|
|
2330
2338
|
digest = fullDigest;
|
|
2331
2339
|
digests.set(owner, createHash("sha256").update(owner).digest("base64url"));
|
|
2332
2340
|
}
|
|
2333
|
-
owners.set(digest,
|
|
2334
|
-
digests.set(
|
|
2341
|
+
owners.set(digest, path);
|
|
2342
|
+
digests.set(path, digest);
|
|
2335
2343
|
}
|
|
2336
2344
|
const perPath = Math.max(JSON.stringify("#" + "x".repeat(12)).length, Math.floor((budgetChars - unique.length * 3) / unique.length));
|
|
2337
|
-
return new Map(unique.map((
|
|
2338
|
-
|
|
2339
|
-
compactPathLine(
|
|
2345
|
+
return new Map(unique.map((path) => [
|
|
2346
|
+
path,
|
|
2347
|
+
compactPathLine(path, perPath, digests.get(path) ?? "")
|
|
2340
2348
|
]));
|
|
2341
2349
|
}
|
|
2342
2350
|
function mergeBodies(first, second) {
|
|
@@ -3008,7 +3016,7 @@ function extractOpenLoops(msgs, extraction) {
|
|
|
3008
3016
|
}));
|
|
3009
3017
|
for (const err of extraction.errors.filter((e) => !e.resolved)) {
|
|
3010
3018
|
const errLower = err.message.toLowerCase();
|
|
3011
|
-
const errFiles = fileNeedles.filter(({ needles }) => needles.some((n) => errLower.includes(n))).map(({ path
|
|
3019
|
+
const errFiles = fileNeedles.filter(({ needles }) => needles.some((n) => errLower.includes(n))).map(({ path }) => path);
|
|
3012
3020
|
loops.push({
|
|
3013
3021
|
id: ID_PREFIX.OPEN_LOOP + ++loopId,
|
|
3014
3022
|
type: "bugfix",
|
|
@@ -3536,27 +3544,27 @@ class SecretScrubber {
|
|
|
3536
3544
|
findings.set("credential", (findings.get("credential") ?? 0) + 1);
|
|
3537
3545
|
this.total++;
|
|
3538
3546
|
};
|
|
3539
|
-
const visit = (
|
|
3540
|
-
if (typeof
|
|
3541
|
-
const result = this.scrubText(
|
|
3547
|
+
const visit = (value) => {
|
|
3548
|
+
if (typeof value === "string") {
|
|
3549
|
+
const result = this.scrubText(value);
|
|
3542
3550
|
mergeFindings(findings, result.findings);
|
|
3543
3551
|
return result.value;
|
|
3544
3552
|
}
|
|
3545
|
-
if (
|
|
3546
|
-
return
|
|
3547
|
-
const cached = seen.get(
|
|
3553
|
+
if (value == null || typeof value !== "object")
|
|
3554
|
+
return value;
|
|
3555
|
+
const cached = seen.get(value);
|
|
3548
3556
|
if (cached !== undefined)
|
|
3549
3557
|
return cached;
|
|
3550
|
-
if (Array.isArray(
|
|
3551
|
-
const
|
|
3552
|
-
seen.set(
|
|
3553
|
-
for (const item of
|
|
3554
|
-
|
|
3555
|
-
return
|
|
3558
|
+
if (Array.isArray(value)) {
|
|
3559
|
+
const output = [];
|
|
3560
|
+
seen.set(value, output);
|
|
3561
|
+
for (const item of value)
|
|
3562
|
+
output.push(visit(item));
|
|
3563
|
+
return output;
|
|
3556
3564
|
}
|
|
3557
3565
|
const output = {};
|
|
3558
|
-
seen.set(
|
|
3559
|
-
for (const [key, item] of Object.entries(
|
|
3566
|
+
seen.set(value, output);
|
|
3567
|
+
for (const [key, item] of Object.entries(value)) {
|
|
3560
3568
|
const carriesSecret = typeof item === "string" && item.length >= 8;
|
|
3561
3569
|
if (this.secretsEnabled && isSecretBearingKey(key) && carriesSecret) {
|
|
3562
3570
|
output[key] = "[REDACTED:credential]";
|
|
@@ -3809,14 +3817,14 @@ function getDefaultServices() {
|
|
|
3809
3817
|
}
|
|
3810
3818
|
|
|
3811
3819
|
// src/domain/telemetry.ts
|
|
3812
|
-
function errorFields(
|
|
3813
|
-
if (!
|
|
3814
|
-
return { name: "", message: String(
|
|
3820
|
+
function errorFields(error, seen = new Set) {
|
|
3821
|
+
if (!error || typeof error !== "object") {
|
|
3822
|
+
return { name: "", message: String(error ?? ""), status: null, code: "" };
|
|
3815
3823
|
}
|
|
3816
|
-
if (seen.has(
|
|
3824
|
+
if (seen.has(error) || seen.size >= 8)
|
|
3817
3825
|
return { name: "", message: "", status: null, code: "" };
|
|
3818
|
-
seen.add(
|
|
3819
|
-
const value =
|
|
3826
|
+
seen.add(error);
|
|
3827
|
+
const value = error;
|
|
3820
3828
|
const cause = value.cause ? errorFields(value.cause, seen) : null;
|
|
3821
3829
|
const numericStatus = Number(value.status ?? value.statusCode);
|
|
3822
3830
|
return {
|
|
@@ -3826,12 +3834,12 @@ function errorFields(error2, seen = new Set) {
|
|
|
3826
3834
|
code: typeof value.code === "string" ? value.code : cause?.code ?? ""
|
|
3827
3835
|
};
|
|
3828
3836
|
}
|
|
3829
|
-
function classifyTelemetryFailure(
|
|
3830
|
-
const fields = errorFields(
|
|
3837
|
+
function classifyTelemetryFailure(error, timedOut = false) {
|
|
3838
|
+
const fields = errorFields(error);
|
|
3831
3839
|
const text = (fields.name + " " + fields.code + " " + fields.message).toLowerCase();
|
|
3832
3840
|
if (timedOut)
|
|
3833
3841
|
return "timeout";
|
|
3834
|
-
if (/max(?:imum)? output|output.?limit|visible[ -]output|length limit/.test(text))
|
|
3842
|
+
if (/max(?:imum)? output|output.?limit|visible[ -]output|length limit|stop reason length/.test(text))
|
|
3835
3843
|
return "output-limit";
|
|
3836
3844
|
if (/timeout|timed out|watchdog|deadline/.test(text))
|
|
3837
3845
|
return "timeout";
|
|
@@ -4187,21 +4195,21 @@ function scheduleExtractionCacheCleanup() {
|
|
|
4187
4195
|
});
|
|
4188
4196
|
}
|
|
4189
4197
|
function reconcileCachedErrors(errors, deltaMessages, deltaToolCalls, baseMsgCount) {
|
|
4190
|
-
return errors.map((
|
|
4191
|
-
if (
|
|
4192
|
-
return { ...
|
|
4193
|
-
if (!
|
|
4194
|
-
return { ...
|
|
4195
|
-
let retryAttempted =
|
|
4198
|
+
return errors.map((error) => {
|
|
4199
|
+
if (error.resolved)
|
|
4200
|
+
return { ...error };
|
|
4201
|
+
if (!error.operationSignature)
|
|
4202
|
+
return { ...error };
|
|
4203
|
+
let retryAttempted = error.retryAttempted;
|
|
4196
4204
|
let resolved = false;
|
|
4197
4205
|
for (let j = 0;j < deltaMessages.length; j++) {
|
|
4198
4206
|
const globalIndex = baseMsgCount + j;
|
|
4199
|
-
if (globalIndex <=
|
|
4207
|
+
if (globalIndex <= error.index || globalIndex > error.index + ERROR_RETRY_WINDOW)
|
|
4200
4208
|
continue;
|
|
4201
4209
|
const message = deltaMessages[j];
|
|
4202
4210
|
if (message.role !== "assistant" || !Array.isArray(message.content))
|
|
4203
4211
|
continue;
|
|
4204
|
-
const retry = message.content.flatMap(flattenToolCallBlock).find((call) => toolOperationSignature(call.name, call.arguments) ===
|
|
4212
|
+
const retry = message.content.flatMap(flattenToolCallBlock).find((call) => toolOperationSignature(call.name, call.arguments) === error.operationSignature);
|
|
4205
4213
|
if (!retry)
|
|
4206
4214
|
continue;
|
|
4207
4215
|
retryAttempted = true;
|
|
@@ -4210,7 +4218,7 @@ function reconcileCachedErrors(errors, deltaMessages, deltaToolCalls, baseMsgCou
|
|
|
4210
4218
|
if (result.role !== "toolResult" || result.isError)
|
|
4211
4219
|
continue;
|
|
4212
4220
|
const resultCall = deltaToolCalls.get(result.toolCallId ?? "");
|
|
4213
|
-
const matches = retry.id == null ? Boolean(resultCall && toolOperationSignature(resultCall.name, resultCall.arguments) ===
|
|
4221
|
+
const matches = retry.id == null ? Boolean(resultCall && toolOperationSignature(resultCall.name, resultCall.arguments) === error.operationSignature) : result.toolCallId === retry.id;
|
|
4214
4222
|
if (matches) {
|
|
4215
4223
|
resolved = true;
|
|
4216
4224
|
break;
|
|
@@ -4218,7 +4226,7 @@ function reconcileCachedErrors(errors, deltaMessages, deltaToolCalls, baseMsgCou
|
|
|
4218
4226
|
}
|
|
4219
4227
|
break;
|
|
4220
4228
|
}
|
|
4221
|
-
return { ...
|
|
4229
|
+
return { ...error, retryAttempted, resolved };
|
|
4222
4230
|
});
|
|
4223
4231
|
}
|
|
4224
4232
|
function boundedTail(items, limit) {
|
|
@@ -4242,9 +4250,9 @@ function recentUnique(items, limit) {
|
|
|
4242
4250
|
};
|
|
4243
4251
|
}
|
|
4244
4252
|
function mergeExtractions(base, delta, baseMsgCount, deltaMessages = [], deltaToolCalls = new Map) {
|
|
4245
|
-
const offsetErrors = delta.errors.map((
|
|
4246
|
-
...
|
|
4247
|
-
index:
|
|
4253
|
+
const offsetErrors = delta.errors.map((error) => ({
|
|
4254
|
+
...error,
|
|
4255
|
+
index: error.index + baseMsgCount
|
|
4248
4256
|
}));
|
|
4249
4257
|
const offsetDecisions = delta.decisions.map((decision) => ({
|
|
4250
4258
|
...decision,
|
|
@@ -4293,7 +4301,7 @@ function mergeExtractions(base, delta, baseMsgCount, deltaMessages = [], deltaTo
|
|
|
4293
4301
|
const referencedFiles = recentUnique([...base.referencedFiles ?? [], ...delta.referencedFiles ?? []], EXTRACTION_LIMITS.REFERENCED_FILES);
|
|
4294
4302
|
const mediaAttachments = boundedTail([...base.mediaAttachments ?? [], ...offsetMedia], EXTRACTION_LIMITS.MEDIA_ATTACHMENTS);
|
|
4295
4303
|
const reconciledBaseErrors = reconcileCachedErrors(base.errors, deltaMessages, deltaToolCalls, baseMsgCount);
|
|
4296
|
-
const errors = boundedTail([...reconciledBaseErrors, ...offsetErrors].filter((
|
|
4304
|
+
const errors = boundedTail([...reconciledBaseErrors, ...offsetErrors].filter((error) => !isTransientToolDiagnostic(error.message)), EXTRACTION_LIMITS.ERRORS);
|
|
4297
4305
|
const decisions = boundedTail([...base.decisions, ...offsetDecisions], EXTRACTION_LIMITS.DECISIONS);
|
|
4298
4306
|
const constraints = boundedTail([...base.constraints, ...offsetConstraints], EXTRACTION_LIMITS.CONSTRAINTS);
|
|
4299
4307
|
const topics = boundedTail([...base.topics, ...offsetTopics], EXTRACTION_LIMITS.TOPICS);
|
|
@@ -4332,7 +4340,7 @@ function mergeExtractions(base, delta, baseMsgCount, deltaMessages = [], deltaTo
|
|
|
4332
4340
|
...base.lastUserMessages,
|
|
4333
4341
|
...delta.lastUserMessages
|
|
4334
4342
|
].slice(-5),
|
|
4335
|
-
lastErrors: errors.values.filter((
|
|
4343
|
+
lastErrors: errors.values.filter((error) => !error.resolved).map((error) => error.message).slice(-3),
|
|
4336
4344
|
messageCount: baseMsgCount + delta.messageCount,
|
|
4337
4345
|
...Object.keys(evidenceOverflow).length ? { evidenceOverflow } : {}
|
|
4338
4346
|
};
|
|
@@ -4349,8 +4357,8 @@ async function appendMetricsSnapshot(sessionId, snapshot) {
|
|
|
4349
4357
|
...snapshot
|
|
4350
4358
|
});
|
|
4351
4359
|
return true;
|
|
4352
|
-
} catch (
|
|
4353
|
-
warn("appendMetricsSnapshot failed",
|
|
4360
|
+
} catch (error) {
|
|
4361
|
+
warn("appendMetricsSnapshot failed", error);
|
|
4354
4362
|
return false;
|
|
4355
4363
|
}
|
|
4356
4364
|
}
|
|
@@ -4363,8 +4371,8 @@ async function appendMetricsLog(sessionId, extra, services) {
|
|
|
4363
4371
|
...extra
|
|
4364
4372
|
});
|
|
4365
4373
|
return true;
|
|
4366
|
-
} catch (
|
|
4367
|
-
warn("appendMetricsLog failed",
|
|
4374
|
+
} catch (error) {
|
|
4375
|
+
warn("appendMetricsLog failed", error);
|
|
4368
4376
|
return false;
|
|
4369
4377
|
}
|
|
4370
4378
|
}
|
|
@@ -4421,8 +4429,8 @@ function processAlive(pid) {
|
|
|
4421
4429
|
try {
|
|
4422
4430
|
process.kill(pid, 0);
|
|
4423
4431
|
return true;
|
|
4424
|
-
} catch (
|
|
4425
|
-
return
|
|
4432
|
+
} catch (error) {
|
|
4433
|
+
return error.code === "EPERM";
|
|
4426
4434
|
}
|
|
4427
4435
|
}
|
|
4428
4436
|
function readLease(file) {
|
|
@@ -4441,11 +4449,11 @@ function acquireFileLease(file, staleMs) {
|
|
|
4441
4449
|
try {
|
|
4442
4450
|
fs4.writeFileSync(fd, JSON.stringify({ pid: process.pid, createdAt: Date.now(), token }) + `
|
|
4443
4451
|
`);
|
|
4444
|
-
} catch (
|
|
4452
|
+
} catch (error) {
|
|
4445
4453
|
try {
|
|
4446
4454
|
fs4.unlinkSync(file);
|
|
4447
4455
|
} catch {}
|
|
4448
|
-
throw
|
|
4456
|
+
throw error;
|
|
4449
4457
|
} finally {
|
|
4450
4458
|
fs4.closeSync(fd);
|
|
4451
4459
|
}
|
|
@@ -4463,9 +4471,9 @@ function acquireFileLease(file, staleMs) {
|
|
|
4463
4471
|
}, Math.max(1e4, Math.floor(staleMs / 3)));
|
|
4464
4472
|
lease.heartbeat.unref();
|
|
4465
4473
|
return lease;
|
|
4466
|
-
} catch (
|
|
4467
|
-
if (
|
|
4468
|
-
throw
|
|
4474
|
+
} catch (error) {
|
|
4475
|
+
if (error.code !== "EEXIST")
|
|
4476
|
+
throw error;
|
|
4469
4477
|
return null;
|
|
4470
4478
|
}
|
|
4471
4479
|
};
|
|
@@ -4671,7 +4679,7 @@ function resolveMode(requested, contextPercent, extraction, additionalRisk = 0)
|
|
|
4671
4679
|
return "fast";
|
|
4672
4680
|
if (!extraction)
|
|
4673
4681
|
return contextPercent < 70 ? "fast" : "balanced";
|
|
4674
|
-
const unresolved = extraction.errors.filter((
|
|
4682
|
+
const unresolved = extraction.errors.filter((error) => !error.resolved).length;
|
|
4675
4683
|
const risk = unresolved * 2 + extraction.decisions.length + extraction.constraints.length + Math.ceil(extraction.modifiedFiles.length / 5) + Math.ceil(extraction.topics.length / 4) + additionalRisk;
|
|
4676
4684
|
if (risk >= 12)
|
|
4677
4685
|
return "thorough";
|
|
@@ -4699,7 +4707,7 @@ function deterministicExtractionConfidence(extraction, context = {}) {
|
|
|
4699
4707
|
score -= 0.25;
|
|
4700
4708
|
if (extraction.topics.length > 6)
|
|
4701
4709
|
score -= 0.15;
|
|
4702
|
-
if (extraction.errors.filter((
|
|
4710
|
+
if (extraction.errors.filter((error) => !error.resolved).length > 2)
|
|
4703
4711
|
score -= 0.2;
|
|
4704
4712
|
if ((extraction.mediaAttachments?.length ?? 0) > 0)
|
|
4705
4713
|
score -= 0.15;
|
|
@@ -5002,8 +5010,8 @@ async function saveProjectFingerprint(projectId, sessionId, extraction) {
|
|
|
5002
5010
|
release();
|
|
5003
5011
|
}
|
|
5004
5012
|
return true;
|
|
5005
|
-
} catch (
|
|
5006
|
-
warn("saveProjectFingerprint failed",
|
|
5013
|
+
} catch (error) {
|
|
5014
|
+
warn("saveProjectFingerprint failed", error);
|
|
5007
5015
|
return false;
|
|
5008
5016
|
}
|
|
5009
5017
|
}
|
|
@@ -5811,8 +5819,8 @@ function freshState(fp, data) {
|
|
|
5811
5819
|
if (Date.now() - updatedAt > SEVEN_DAYS_MS) {
|
|
5812
5820
|
try {
|
|
5813
5821
|
fs5.unlinkSync(fp);
|
|
5814
|
-
} catch (
|
|
5815
|
-
debug("stale state cleanup failed",
|
|
5822
|
+
} catch (error) {
|
|
5823
|
+
debug("stale state cleanup failed", error);
|
|
5816
5824
|
}
|
|
5817
5825
|
return null;
|
|
5818
5826
|
}
|
|
@@ -5828,12 +5836,12 @@ function pruneScopedStateSnapshots(target) {
|
|
|
5828
5836
|
for (const snapshot of snapshots.slice(Math.max(0, STATE_SNAPSHOT_MAX_FILES - 1))) {
|
|
5829
5837
|
try {
|
|
5830
5838
|
fs5.unlinkSync(snapshot.file);
|
|
5831
|
-
} catch (
|
|
5832
|
-
debug("state snapshot cleanup failed",
|
|
5839
|
+
} catch (error) {
|
|
5840
|
+
debug("state snapshot cleanup failed", error);
|
|
5833
5841
|
}
|
|
5834
5842
|
}
|
|
5835
|
-
} catch (
|
|
5836
|
-
debug("state snapshot retention failed",
|
|
5843
|
+
} catch (error) {
|
|
5844
|
+
debug("state snapshot retention failed", error);
|
|
5837
5845
|
}
|
|
5838
5846
|
}
|
|
5839
5847
|
function saveCompactionState(projectId, state) {
|
|
@@ -5843,19 +5851,19 @@ function saveCompactionState(projectId, state) {
|
|
|
5843
5851
|
if (state.scope)
|
|
5844
5852
|
pruneScopedStateSnapshots(target);
|
|
5845
5853
|
return true;
|
|
5846
|
-
} catch (
|
|
5847
|
-
warn("saveCompactionState failed",
|
|
5854
|
+
} catch (error) {
|
|
5855
|
+
warn("saveCompactionState failed", error);
|
|
5848
5856
|
return false;
|
|
5849
5857
|
}
|
|
5850
5858
|
}
|
|
5851
|
-
function loadScopedCompactionState(scope,
|
|
5859
|
+
function loadScopedCompactionState(scope, branchEntryIds = []) {
|
|
5852
5860
|
const snapshotProbe = scopedCompactionStateFile(scope.projectId, scope.sessionId, "__snapshot__");
|
|
5853
5861
|
let availableSnapshots = new Set;
|
|
5854
5862
|
try {
|
|
5855
5863
|
availableSnapshots = new Set(fs5.readdirSync(path8.dirname(snapshotProbe), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name));
|
|
5856
5864
|
} catch {}
|
|
5857
5865
|
const ancestry = Array.from(new Set([
|
|
5858
|
-
...
|
|
5866
|
+
...branchEntryIds,
|
|
5859
5867
|
...scope.branchHeadId ? [scope.branchHeadId] : []
|
|
5860
5868
|
])).reverse();
|
|
5861
5869
|
const valid = (state, branchHeadId) => Boolean(state?.scope?.schemaVersion === 2 && state.scope.projectId === scope.projectId && state.scope.sessionId === scope.sessionId && typeof state.scope.branchHeadId === "string" && (!branchHeadId || state.scope.branchHeadId === branchHeadId));
|
|
@@ -5874,8 +5882,8 @@ function loadScopedCompactionState(scope, branchEntryIds2 = []) {
|
|
|
5874
5882
|
try {
|
|
5875
5883
|
fs5.unlinkSync(legacyPath);
|
|
5876
5884
|
writeJsonSync(getStatePath(scope.projectId, legacy), legacy, true);
|
|
5877
|
-
} catch (
|
|
5878
|
-
warn("branch state migration failed",
|
|
5885
|
+
} catch (error) {
|
|
5886
|
+
warn("branch state migration failed", error);
|
|
5879
5887
|
}
|
|
5880
5888
|
return legacy;
|
|
5881
5889
|
}
|
|
@@ -6010,7 +6018,7 @@ function mergeCompactionStates(previous, current) {
|
|
|
6010
6018
|
const activePrevious = applyContinuityOverrides(previous, factOverrides);
|
|
6011
6019
|
const currentPresent = new Set([...activeCurrent.modifiedFiles, ...activeCurrent.readFiles].map(normalizeFactKey));
|
|
6012
6020
|
const currentDeleted = new Set(activeCurrent.deletedFiles.map(normalizeFactKey));
|
|
6013
|
-
const resolvedKeys = new Set(activeCurrent.resolvedErrors.map((
|
|
6021
|
+
const resolvedKeys = new Set(activeCurrent.resolvedErrors.map((error) => normalizeFactKey(error.message)));
|
|
6014
6022
|
const isResolvedLoop = (loop) => {
|
|
6015
6023
|
if (loop.type !== "bugfix" || loop.status !== "open")
|
|
6016
6024
|
return false;
|
|
@@ -6025,7 +6033,7 @@ function mergeCompactionStates(previous, current) {
|
|
|
6025
6033
|
};
|
|
6026
6034
|
const decisions = mergeBy(activeCurrent.decisions, activePrevious.decisions, (item) => normalizeFactKey(item.summary), 30).map((item, index) => ({ ...item, id: ID_PREFIX.DECISION + (index + 1) }));
|
|
6027
6035
|
const constraints = mergeBy(activeCurrent.constraints, activePrevious.constraints, (item) => normalizeFactKey(item.text), 30).map((item, index) => ({ ...item, id: "constraint-" + (index + 1) }));
|
|
6028
|
-
const unresolvedErrors = mergeBy(activeCurrent.unresolvedErrors, activePrevious.unresolvedErrors.filter((
|
|
6036
|
+
const unresolvedErrors = mergeBy(activeCurrent.unresolvedErrors, activePrevious.unresolvedErrors.filter((error) => !resolvedKeys.has(normalizeFactKey(error.message))), (item) => normalizeFactKey(item.message), 15).map((item, index) => ({ ...item, id: ID_PREFIX.ERROR + (index + 1) }));
|
|
6029
6037
|
const openLoops = mergeOpenLoops(activeCurrent.openLoops, activePrevious.openLoops.map((loop) => isResolvedLoop(loop) ? { ...loop, status: "resolved" } : loop));
|
|
6030
6038
|
const currentGoalKey = activeCurrent.goalKey ?? (activeCurrent.goal ? normalizeFactKey(activeCurrent.goal) : "");
|
|
6031
6039
|
const previousGoalKey = activePrevious.goalKey ?? (activePrevious.goal ? normalizeFactKey(activePrevious.goal) : "");
|
|
@@ -6122,7 +6130,7 @@ function computeDelta(prev, current) {
|
|
|
6122
6130
|
const newModifiedFiles = current.modifiedFiles.filter((f) => !prevFiles.has(f));
|
|
6123
6131
|
const prevErrorMsgs = new Set(prev.unresolvedErrors.map((e) => normalizeFactKey(e.message)));
|
|
6124
6132
|
const resolvedErrorKeys = new Set([
|
|
6125
|
-
...current.resolvedErrors.map((
|
|
6133
|
+
...current.resolvedErrors.map((error) => normalizeFactKey(error.message)),
|
|
6126
6134
|
...retired("error")
|
|
6127
6135
|
]);
|
|
6128
6136
|
const resolvedErrors = prev.unresolvedErrors.filter((e) => resolvedErrorKeys.has(normalizeFactKey(e.message))).map((e) => e.message);
|
|
@@ -6196,7 +6204,7 @@ function ensurePinnedPaths(summary, pinned) {
|
|
|
6196
6204
|
if (!pinned.length)
|
|
6197
6205
|
return summary;
|
|
6198
6206
|
const lower = summary.toLowerCase();
|
|
6199
|
-
const missing = pinned.map((
|
|
6207
|
+
const missing = pinned.map((path) => summaryEvidenceLine(path, TRUNC.MESSAGE)).filter((path) => path && !lower.includes(path.toLowerCase()));
|
|
6200
6208
|
if (!missing.length)
|
|
6201
6209
|
return summary;
|
|
6202
6210
|
const parsed = parseSummary(summary);
|
|
@@ -6225,7 +6233,7 @@ async function showOpenLoopsUI(ctx, sourceLoops, initialOverrides = []) {
|
|
|
6225
6233
|
let changed = false;
|
|
6226
6234
|
while (true) {
|
|
6227
6235
|
const loops = applyLoopOverrides(sourceLoops, overrides);
|
|
6228
|
-
const labels = loops.map((
|
|
6236
|
+
const labels = loops.map((loop, index) => index + 1 + ". [" + loop.status + "/" + loop.priority + "] " + loop.summary.slice(0, TRUNC.TOPIC_LABEL));
|
|
6229
6237
|
const choice = await ctx.ui.select("Open loops", [...labels, "Done"]);
|
|
6230
6238
|
if (!choice || choice === "Done")
|
|
6231
6239
|
return changed ? overrides : null;
|
|
@@ -6418,13 +6426,13 @@ var SOFT_BOUNDARY_COPY = {
|
|
|
6418
6426
|
function formatPreflightSummary(preflight, modelLabel, details = false) {
|
|
6419
6427
|
const plan = preflight.plan;
|
|
6420
6428
|
if (!plan) {
|
|
6421
|
-
const
|
|
6429
|
+
const lines = [
|
|
6422
6430
|
"Plan unavailable \xB7 " + explainPreflightReason(preflight.reason),
|
|
6423
6431
|
"\u2713 Complete tool pairs \xB7 \u2713 zero-gap verification before apply"
|
|
6424
6432
|
];
|
|
6425
6433
|
if (details)
|
|
6426
|
-
|
|
6427
|
-
return
|
|
6434
|
+
lines.push("Estimator messages ~" + tokenCount(preflight.rawEstimatedMessageTokens) + " \xB7 normalization unavailable", "Route " + modelLabel + " \xB7 viability " + preflight.reason);
|
|
6435
|
+
return lines;
|
|
6428
6436
|
}
|
|
6429
6437
|
const stateReserve = Math.max(0, (plan.finalSummaryAllowanceTokens ?? plan.summaryBudgetTokens + Math.ceil(plan.summaryBudgetTokens * POST_SUMMARY_RESERVE_RATIO)) - plan.summaryBudgetTokens);
|
|
6430
6438
|
const lines = [
|
|
@@ -6716,8 +6724,8 @@ async function showCompactUI(ctx, opts) {
|
|
|
6716
6724
|
const marker = index === selected ? "\u203A " : " ";
|
|
6717
6725
|
const recommendedMark = mode === recommended.mode ? " recommended" : " ";
|
|
6718
6726
|
const trait = mode === "fast" ? "quickest" : mode === "balanced" ? "default" : "deepest";
|
|
6719
|
-
const
|
|
6720
|
-
const line = " " + marker + MODE_LABELS[mode].padEnd(9) + recommendedMark + " " +
|
|
6727
|
+
const stats = (viable && plan ? "~" + compactTokenCount(plan.projectedAfterTokens) + " after \xB7 " + percent(plan.projectedYield * 100) + " saved" : "unavailable \xB7 " + explainPreflightReason(preview.reason)) + " \xB7 " + trait;
|
|
6728
|
+
const line = " " + marker + MODE_LABELS[mode].padEnd(9) + recommendedMark + " " + stats;
|
|
6721
6729
|
lines.push(cell(index === selected ? theme.fg("accent", theme.bold(line)) : theme.fg(viable ? mode === recommended.mode ? "success" : "text" : "muted", line)));
|
|
6722
6730
|
}
|
|
6723
6731
|
lines.push(divider);
|
|
@@ -6864,7 +6872,7 @@ async function* streamJsonlLines(file, chunkSize = 64 * 1024) {
|
|
|
6864
6872
|
if (leftover.length > 0)
|
|
6865
6873
|
yield leftover;
|
|
6866
6874
|
} finally {
|
|
6867
|
-
await handle.close().catch((
|
|
6875
|
+
await handle.close().catch((error) => debug("streamJsonlLines close failed", error));
|
|
6868
6876
|
}
|
|
6869
6877
|
}
|
|
6870
6878
|
var LOG_PATH_CACHE_TTL_MS = 30000;
|
|
@@ -6906,26 +6914,26 @@ function findSessionLogFile(sessionId, cwd) {
|
|
|
6906
6914
|
const cached = lruGet(logPathCache, cacheKey);
|
|
6907
6915
|
if (cached && cached.home === home2 && cached.expiresAt > now)
|
|
6908
6916
|
return cached.path;
|
|
6909
|
-
const
|
|
6910
|
-
if (!fs6.existsSync(
|
|
6917
|
+
const sessionsDir = getSessionsDir();
|
|
6918
|
+
if (!fs6.existsSync(sessionsDir))
|
|
6911
6919
|
return remember(null);
|
|
6912
6920
|
if (directDirectory) {
|
|
6913
6921
|
const direct = findLogInDirectory(directDirectory, sessionId);
|
|
6914
6922
|
if (direct)
|
|
6915
6923
|
return remember(direct);
|
|
6916
6924
|
}
|
|
6917
|
-
for (const subdir of fs6.readdirSync(
|
|
6925
|
+
for (const subdir of fs6.readdirSync(sessionsDir, { withFileTypes: true })) {
|
|
6918
6926
|
if (!subdir.isDirectory())
|
|
6919
6927
|
continue;
|
|
6920
|
-
const subdirPath = path10.join(
|
|
6928
|
+
const subdirPath = path10.join(sessionsDir, subdir.name);
|
|
6921
6929
|
if (subdirPath === directDirectory)
|
|
6922
6930
|
continue;
|
|
6923
6931
|
const found = findLogInDirectory(subdirPath, sessionId);
|
|
6924
6932
|
if (found)
|
|
6925
6933
|
return remember(found);
|
|
6926
6934
|
}
|
|
6927
|
-
} catch (
|
|
6928
|
-
debug("findSessionLogFile failed",
|
|
6935
|
+
} catch (error) {
|
|
6936
|
+
debug("findSessionLogFile failed", error);
|
|
6929
6937
|
}
|
|
6930
6938
|
return remember(null);
|
|
6931
6939
|
}
|
|
@@ -6990,8 +6998,8 @@ async function readOriginalMessageMap(sessionId, wantedIds, cwd) {
|
|
|
6990
6998
|
map
|
|
6991
6999
|
}, getMaxEntries());
|
|
6992
7000
|
return map;
|
|
6993
|
-
} catch (
|
|
6994
|
-
debug("readOriginalMessageMap failed",
|
|
7001
|
+
} catch (error) {
|
|
7002
|
+
debug("readOriginalMessageMap failed", error);
|
|
6995
7003
|
return null;
|
|
6996
7004
|
}
|
|
6997
7005
|
}
|
|
@@ -7284,12 +7292,12 @@ function prunePass(dir) {
|
|
|
7284
7292
|
try {
|
|
7285
7293
|
if (isOwnedBackupFile(full))
|
|
7286
7294
|
fs7.unlinkSync(full);
|
|
7287
|
-
} catch (
|
|
7288
|
-
debug("prunePass unlink failed",
|
|
7295
|
+
} catch (error) {
|
|
7296
|
+
debug("prunePass unlink failed", error);
|
|
7289
7297
|
}
|
|
7290
7298
|
}
|
|
7291
|
-
} catch (
|
|
7292
|
-
debug("prunePass scan failed",
|
|
7299
|
+
} catch (error) {
|
|
7300
|
+
debug("prunePass scan failed", error);
|
|
7293
7301
|
}
|
|
7294
7302
|
}
|
|
7295
7303
|
function schedulePruneBackups(dir) {
|
|
@@ -7323,8 +7331,8 @@ function prepareConversationBackup(source, sessionId, metadata = {}) {
|
|
|
7323
7331
|
branchLeafId: metadata.branchLeafId,
|
|
7324
7332
|
contextTokens: metadata.contextTokens
|
|
7325
7333
|
};
|
|
7326
|
-
} catch (
|
|
7327
|
-
warn("prepareConversationBackup failed",
|
|
7334
|
+
} catch (error) {
|
|
7335
|
+
warn("prepareConversationBackup failed", error);
|
|
7328
7336
|
return null;
|
|
7329
7337
|
}
|
|
7330
7338
|
}
|
|
@@ -7342,8 +7350,8 @@ async function commitPreparedConversationBackup(prepared) {
|
|
|
7342
7350
|
await atomicWriteFile(prepared.path, metadata + body);
|
|
7343
7351
|
schedulePruneBackups(path11.dirname(prepared.path));
|
|
7344
7352
|
return prepared.path;
|
|
7345
|
-
} catch (
|
|
7346
|
-
warn("commitPreparedConversationBackup failed",
|
|
7353
|
+
} catch (error) {
|
|
7354
|
+
warn("commitPreparedConversationBackup failed", error);
|
|
7347
7355
|
return null;
|
|
7348
7356
|
}
|
|
7349
7357
|
}
|
|
@@ -7376,8 +7384,8 @@ function listBackups(limit = 20) {
|
|
|
7376
7384
|
}
|
|
7377
7385
|
out.sort((left, right) => left.date < right.date ? 1 : left.date > right.date ? -1 : 0);
|
|
7378
7386
|
return out.slice(0, limit);
|
|
7379
|
-
} catch (
|
|
7380
|
-
warn("listBackups failed",
|
|
7387
|
+
} catch (error) {
|
|
7388
|
+
warn("listBackups failed", error);
|
|
7381
7389
|
return [];
|
|
7382
7390
|
}
|
|
7383
7391
|
}
|
|
@@ -7405,8 +7413,8 @@ function readConversationBackup(file) {
|
|
|
7405
7413
|
...branchLeafId ? { branchLeafId } : {},
|
|
7406
7414
|
...contextTokens !== undefined && Number.isSafeInteger(contextTokens) ? { contextTokens } : {}
|
|
7407
7415
|
};
|
|
7408
|
-
} catch (
|
|
7409
|
-
warn("readConversationBackup failed",
|
|
7416
|
+
} catch (error) {
|
|
7417
|
+
warn("readConversationBackup failed", error);
|
|
7410
7418
|
return null;
|
|
7411
7419
|
}
|
|
7412
7420
|
}
|
|
@@ -7554,10 +7562,10 @@ function extractWithCache(rc) {
|
|
|
7554
7562
|
|
|
7555
7563
|
// src/phases/explore.ts
|
|
7556
7564
|
import { Type } from "typebox";
|
|
7557
|
-
function explicitlyRejectsTools(
|
|
7558
|
-
if (!
|
|
7565
|
+
function explicitlyRejectsTools(error) {
|
|
7566
|
+
if (!error || typeof error !== "object")
|
|
7559
7567
|
return false;
|
|
7560
|
-
const record =
|
|
7568
|
+
const record = error;
|
|
7561
7569
|
const status = Number(record.status ?? record.statusCode ?? record.response?.status);
|
|
7562
7570
|
const message = String(record.message ?? "");
|
|
7563
7571
|
return (status === 400 || status === 404 || status === 422) && /(?:(?:tools?|function(?:[ -]calling)?).{0,60}(?:unsupported|not supported|unknown|unavailable|invalid)|(?:unsupported|does not support|doesn't support).{0,60}(?:tools?|function))/i.test(message);
|
|
@@ -7925,13 +7933,13 @@ async function exploreConversation(llmMessages, extraction, model, auth, prevSum
|
|
|
7925
7933
|
if (cachedSupport === false) {
|
|
7926
7934
|
if (notify)
|
|
7927
7935
|
notify("Tool support cached: unsupported (" + cacheLabel + ")", "info");
|
|
7928
|
-
const
|
|
7929
|
-
if (!
|
|
7936
|
+
const report = await directExploration(llmMessages, extraction, model, auth, prevSummary, userNote, signal, svc);
|
|
7937
|
+
if (!report.boundaries.length) {
|
|
7930
7938
|
const retried = await explorationRetry(model, auth, llmMessages, extraction, userNote, signal, svc);
|
|
7931
7939
|
if (retried.boundaries.length)
|
|
7932
7940
|
return { report: retried, rounds: 1, toolSupported: false };
|
|
7933
7941
|
}
|
|
7934
|
-
return { report
|
|
7942
|
+
return { report, rounds: 1, toolSupported: false };
|
|
7935
7943
|
}
|
|
7936
7944
|
const probeResp = await trackedComplete("explore", model, {
|
|
7937
7945
|
systemPrompt: COMPACT_SYSTEM_PREFIX + `
|
|
@@ -7999,13 +8007,13 @@ async function exploreConversation(llmMessages, extraction, model, auth, prevSum
|
|
|
7999
8007
|
if (nextToolCalls.length === 0) {
|
|
8000
8008
|
const text = response.content.filter((c) => c.type === "text").map((c) => c.text).join(`
|
|
8001
8009
|
`).trim();
|
|
8002
|
-
let
|
|
8003
|
-
if (!
|
|
8004
|
-
|
|
8005
|
-
if (
|
|
8010
|
+
let report = parseExplorationReport(text, llmMessages);
|
|
8011
|
+
if (!report.boundaries.length) {
|
|
8012
|
+
report = await directExploration(llmMessages, extraction, model, auth, prevSummary, userNote, signal, svc);
|
|
8013
|
+
if (report.boundaries.length)
|
|
8006
8014
|
rounds++;
|
|
8007
8015
|
}
|
|
8008
|
-
return { report
|
|
8016
|
+
return { report, rounds, toolSupported: true };
|
|
8009
8017
|
}
|
|
8010
8018
|
messages.push(response);
|
|
8011
8019
|
for (const tc of nextToolCalls) {
|
|
@@ -8024,21 +8032,21 @@ async function exploreConversation(llmMessages, extraction, model, auth, prevSum
|
|
|
8024
8032
|
if (lastAssistant?.content) {
|
|
8025
8033
|
const text = lastAssistant.content.filter((c) => c.type === "text").map((c) => c.text).join(`
|
|
8026
8034
|
`).trim();
|
|
8027
|
-
const
|
|
8028
|
-
if (
|
|
8029
|
-
return { report
|
|
8035
|
+
const report = parseExplorationReport(text, llmMessages);
|
|
8036
|
+
if (report.boundaries.length)
|
|
8037
|
+
return { report, rounds, toolSupported: true };
|
|
8030
8038
|
}
|
|
8031
8039
|
} else {
|
|
8032
8040
|
const text = probeResp.content.filter((c) => c.type === "text").map((c) => c.text).join(`
|
|
8033
8041
|
`).trim();
|
|
8034
|
-
let
|
|
8035
|
-
const parsedOk =
|
|
8042
|
+
let report = parseExplorationReport(text, llmMessages);
|
|
8043
|
+
const parsedOk = report.boundaries.length > 0;
|
|
8036
8044
|
if (!parsedOk) {
|
|
8037
|
-
|
|
8045
|
+
report = await directExploration(llmMessages, extraction, model, auth, prevSummary, userNote, signal, svc);
|
|
8038
8046
|
}
|
|
8039
8047
|
if (parsedOk)
|
|
8040
8048
|
toolSupport.set(cacheKey, true, svc.clock.now());
|
|
8041
|
-
return { report
|
|
8049
|
+
return { report, rounds: 1, toolSupported: parsedOk };
|
|
8042
8050
|
}
|
|
8043
8051
|
} catch (e) {
|
|
8044
8052
|
const rejected = explicitlyRejectsTools(e);
|
|
@@ -8639,42 +8647,58 @@ async function summarizeBatch(batch, extraction, model, auth, signal, services,
|
|
|
8639
8647
|
const cached = cacheKey ? getCachedBatch(cacheKey) : null;
|
|
8640
8648
|
if (cached)
|
|
8641
8649
|
return cached;
|
|
8642
|
-
const
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
{
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
{
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
]
|
|
8656
|
-
}, {
|
|
8650
|
+
const messages = [
|
|
8651
|
+
{
|
|
8652
|
+
role: "user",
|
|
8653
|
+
content: [{ type: "text", text: promptPrefix }],
|
|
8654
|
+
timestamp: Date.now()
|
|
8655
|
+
},
|
|
8656
|
+
{
|
|
8657
|
+
role: "user",
|
|
8658
|
+
content: [{ type: "text", text: dynamicSuffix }],
|
|
8659
|
+
timestamp: Date.now()
|
|
8660
|
+
}
|
|
8661
|
+
];
|
|
8662
|
+
const baseOpts = {
|
|
8657
8663
|
apiKey: auth.apiKey,
|
|
8658
8664
|
headers: auth.headers,
|
|
8659
8665
|
maxTokens: maxOutputTokens ?? Math.min(Math.max(1000, batch.length * 250), 4096, getProviderCaps(model.provider).maxOutputTokens),
|
|
8660
8666
|
signal
|
|
8661
|
-
}
|
|
8662
|
-
const
|
|
8667
|
+
};
|
|
8668
|
+
const attempt = async (reasoning) => {
|
|
8669
|
+
const resp = await trackedComplete("batch", model, {
|
|
8670
|
+
systemPrompt: COMPACT_SYSTEM_PREFIX,
|
|
8671
|
+
messages
|
|
8672
|
+
}, reasoning === undefined ? baseOpts : { ...baseOpts, reasoning }, services);
|
|
8673
|
+
const output = resp.content.filter((c) => c.type === "text").map((c) => c.text).join(`
|
|
8663
8674
|
`);
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
+
const sectionMap = new Map;
|
|
8676
|
+
const duplicateIds = new Set;
|
|
8677
|
+
const sections = output.split(/^### /m).filter((s) => s.trim());
|
|
8678
|
+
for (const sec of sections) {
|
|
8679
|
+
const m = sec.match(/^CHUNK\s+(\d+):\s*(.*?)\n/i);
|
|
8680
|
+
if (m) {
|
|
8681
|
+
const id = parseInt(m[1], 10);
|
|
8682
|
+
if (sectionMap.has(id))
|
|
8683
|
+
duplicateIds.add(id);
|
|
8684
|
+
else
|
|
8685
|
+
sectionMap.set(id, sec);
|
|
8686
|
+
}
|
|
8687
|
+
}
|
|
8688
|
+
assertCompleteBatchResponse(resp.stopReason, sectionMap, duplicateIds, batch.length);
|
|
8689
|
+
return sectionMap;
|
|
8690
|
+
};
|
|
8691
|
+
let sectionMap;
|
|
8692
|
+
try {
|
|
8693
|
+
sectionMap = await attempt();
|
|
8694
|
+
} catch (err) {
|
|
8695
|
+
if (err instanceof BatchSummaryFormatError && /non-terminal stop reason length/.test(err.message) && services?.thinkingLevels.summaryThinkingLevel !== "minimal") {
|
|
8696
|
+
info("Batch synthesis exhausted the output budget (reasoning shared it); retrying once with minimal reasoning");
|
|
8697
|
+
sectionMap = await attempt("minimal");
|
|
8698
|
+
} else {
|
|
8699
|
+
throw err;
|
|
8675
8700
|
}
|
|
8676
8701
|
}
|
|
8677
|
-
assertCompleteBatchResponse(resp.stopReason, sectionMap, duplicateIds, batch.length);
|
|
8678
8702
|
const result = batch.map((ch, i) => {
|
|
8679
8703
|
const id = i + 1;
|
|
8680
8704
|
const sec = sectionMap.get(id) ?? "";
|
|
@@ -8714,9 +8738,9 @@ function deterministicFileEvidence(extraction, budgetTokens, continuity = null)
|
|
|
8714
8738
|
const deletedPaths = Array.from(new Set([...extraction.deletedFiles, ...continuity?.deletedFiles ?? []]));
|
|
8715
8739
|
const evidence = buildSummaryPathEvidence([...modifiedPaths, ...readPaths, ...deletedPaths], budgetTokens);
|
|
8716
8740
|
return {
|
|
8717
|
-
modified: modifiedPaths.map((
|
|
8718
|
-
read: readPaths.map((
|
|
8719
|
-
deleted: deletedPaths.map((
|
|
8741
|
+
modified: modifiedPaths.map((path) => evidence.get(path)).filter((path) => Boolean(path)),
|
|
8742
|
+
read: readPaths.map((path) => evidence.get(path)).filter((path) => Boolean(path)),
|
|
8743
|
+
deleted: deletedPaths.map((path) => evidence.get(path)).filter((path) => Boolean(path))
|
|
8720
8744
|
};
|
|
8721
8745
|
}
|
|
8722
8746
|
async function assembleLLM(summaries, extraction, report, model, auth, budget, prevContext, signal, services, focus, continuity = null) {
|
|
@@ -8756,8 +8780,8 @@ function assembleFallback(summaries, extraction, steering = {}, budgetTokens = 6
|
|
|
8756
8780
|
const detModified = files.modified;
|
|
8757
8781
|
const detRead = files.read;
|
|
8758
8782
|
const detDeleted = files.deleted;
|
|
8759
|
-
const unresolved = extraction.errors.filter((
|
|
8760
|
-
const resolved = extraction.errors.filter((
|
|
8783
|
+
const unresolved = extraction.errors.filter((error) => !error.resolved).map((error) => safe(error.message, TRUNC.PREVIEW)).filter(Boolean);
|
|
8784
|
+
const resolved = extraction.errors.filter((error) => error.resolved).slice(-5).map((error) => safe(error.message, TRUNC.PREVIEW)).filter(Boolean);
|
|
8761
8785
|
const constraints = extraction.constraints.map((item) => "- [" + item.category + "] " + safe(item.text)).filter((line) => !line.endsWith("] "));
|
|
8762
8786
|
if (steering.focus?.trim())
|
|
8763
8787
|
constraints.push("- [focus] Preserve detail about: " + safe(steering.focus, TRUNC.CONSTRAINT_TEXT));
|
|
@@ -8777,8 +8801,8 @@ function assembleFallback(summaries, extraction, steering = {}, budgetTokens = 6
|
|
|
8777
8801
|
const goal = safe(extraction.mainGoal ?? "", TRUNC.DETAIL) || "Continue the current task.";
|
|
8778
8802
|
const overflow = Object.entries(extraction.evidenceOverflow ?? {}).filter(([, count]) => typeof count === "number" && count > 0).map(([kind, count]) => "- Safety bound omitted " + count + " older " + kind + " item(s) from the human summary.");
|
|
8779
8803
|
const critical = [
|
|
8780
|
-
...unresolved.map((
|
|
8781
|
-
...resolved.map((
|
|
8804
|
+
...unresolved.map((error) => "- Unresolved error: " + safe(error, TRUNC.TOPIC_LABEL)),
|
|
8805
|
+
...resolved.map((error) => "- Resolved error: " + safe(error, TRUNC.TOPIC_LABEL)),
|
|
8782
8806
|
...overflow
|
|
8783
8807
|
];
|
|
8784
8808
|
return [
|
|
@@ -8794,7 +8818,7 @@ function assembleFallback(summaries, extraction, steering = {}, budgetTokens = 6
|
|
|
8794
8818
|
"### In Progress",
|
|
8795
8819
|
...inProgress.length ? inProgress : ["- Continue current work."],
|
|
8796
8820
|
"### Blocked",
|
|
8797
|
-
...unresolved.length ? unresolved.map((
|
|
8821
|
+
...unresolved.length ? unresolved.map((error) => "- " + error) : ["- None recorded."],
|
|
8798
8822
|
"",
|
|
8799
8823
|
"## Key Decisions",
|
|
8800
8824
|
...decisions.length ? decisions : ["- None recorded."],
|
|
@@ -8946,7 +8970,7 @@ function hasListedPath(listed, file, display, normalizedOwners) {
|
|
|
8946
8970
|
return false;
|
|
8947
8971
|
}
|
|
8948
8972
|
function outcomeClaims(summary, pathEvidence) {
|
|
8949
|
-
const pathLines = new Set(Array.from(pathEvidence, ([
|
|
8973
|
+
const pathLines = new Set(Array.from(pathEvidence, ([path, display]) => [path, display, "`" + path + "`"]).flat());
|
|
8950
8974
|
return Array.from(new Set(summary.split(/\r?\n/).map((line) => line.replace(/^\s*(?:[-*+]|\d+[.)])\s+/, "").replace(/^\[[ x]\]\s+/i, "").trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !pathLines.has(line)).filter((line) => HIGH_RISK_OUTCOME_RE.test(line)).filter((line) => /\bno\s+(?:errors?|failures?)\b/i.test(line) || !NEGATED_OUTCOME_RE.test(line)))).slice(0, 12);
|
|
8951
8975
|
}
|
|
8952
8976
|
function classifyOutcomeClaim(claim) {
|
|
@@ -9016,7 +9040,7 @@ function successfulToolEvidence(messages) {
|
|
|
9016
9040
|
function successfulToolSupportsClaim(claim, tools, extraction) {
|
|
9017
9041
|
const shape = semanticShape(claim);
|
|
9018
9042
|
const category = classifyOutcomeClaim(claim);
|
|
9019
|
-
if (category === "error" && extraction.errors.some((
|
|
9043
|
+
if (category === "error" && extraction.errors.some((error) => error.resolved && hasSemanticEvidence(claim, error.message)))
|
|
9020
9044
|
return true;
|
|
9021
9045
|
if (category === "file" && extraction.modifiedFiles.some((file) => claim.toLowerCase().includes(file.path.toLowerCase())))
|
|
9022
9046
|
return true;
|
|
@@ -9381,15 +9405,15 @@ function uniqueByText(items, text) {
|
|
|
9381
9405
|
}
|
|
9382
9406
|
function collectVerificationEvidence(extraction, continuity, evidence) {
|
|
9383
9407
|
const unresolved = uniqueByText([
|
|
9384
|
-
...extraction.errors.flatMap((
|
|
9385
|
-
...(continuity?.unresolvedErrors ?? []).map((
|
|
9386
|
-
message:
|
|
9408
|
+
...extraction.errors.flatMap((error) => !error.resolved ? [{ message: error.message }] : []),
|
|
9409
|
+
...(continuity?.unresolvedErrors ?? []).map((error) => ({
|
|
9410
|
+
message: error.message
|
|
9387
9411
|
}))
|
|
9388
9412
|
], (item) => item.message);
|
|
9389
9413
|
const resolved = uniqueByText([
|
|
9390
|
-
...extraction.errors.flatMap((
|
|
9391
|
-
...(continuity?.resolvedErrors ?? []).map((
|
|
9392
|
-
message:
|
|
9414
|
+
...extraction.errors.flatMap((error) => error.resolved ? [{ message: error.message }] : []),
|
|
9415
|
+
...(continuity?.resolvedErrors ?? []).map((error) => ({
|
|
9416
|
+
message: error.message
|
|
9393
9417
|
}))
|
|
9394
9418
|
], (item) => item.message).slice(-5);
|
|
9395
9419
|
const steeringConstraints = [];
|
|
@@ -9476,16 +9500,16 @@ function verifyPathCoverage(parsed, extraction, continuity, evidence, accumulato
|
|
|
9476
9500
|
return { modified, read, deleted, rendered };
|
|
9477
9501
|
}
|
|
9478
9502
|
function verifyErrorEvidence(normalizedSummary, collected, accumulator) {
|
|
9479
|
-
for (const
|
|
9480
|
-
const snippet = summaryEvidenceLine(
|
|
9503
|
+
for (const error of collected.unresolved) {
|
|
9504
|
+
const snippet = summaryEvidenceLine(error.message, TRUNC.ERROR_SNIPPET).toLowerCase().replace(/\\/g, "/");
|
|
9481
9505
|
if (snippet.length > 5 && !normalizedSummary.includes(snippet)) {
|
|
9482
|
-
addGap(accumulator, { kind: "missing-error", message:
|
|
9506
|
+
addGap(accumulator, { kind: "missing-error", message: error.message }, 5);
|
|
9483
9507
|
}
|
|
9484
9508
|
}
|
|
9485
|
-
for (const
|
|
9486
|
-
const snippet = summaryEvidenceLine(
|
|
9509
|
+
for (const error of collected.resolved) {
|
|
9510
|
+
const snippet = summaryEvidenceLine(error.message, TRUNC.ERROR_SNIPPET).toLowerCase().replace(/\\/g, "/");
|
|
9487
9511
|
if (snippet.length > 5 && !normalizedSummary.includes(snippet)) {
|
|
9488
|
-
addGap(accumulator, { kind: "missing-error", message:
|
|
9512
|
+
addGap(accumulator, { kind: "missing-error", message: error.message, resolved: true }, 2);
|
|
9489
9513
|
}
|
|
9490
9514
|
}
|
|
9491
9515
|
}
|
|
@@ -9565,7 +9589,7 @@ function verifyFileReferences(summary, extraction, continuity, evidence, collect
|
|
|
9565
9589
|
...renderedPaths.flatMap(extractFileRefs),
|
|
9566
9590
|
...continuity?.modifiedFiles ?? [],
|
|
9567
9591
|
...continuity?.readFiles ?? [],
|
|
9568
|
-
...(continuity?.unresolvedErrors ?? []).flatMap((
|
|
9592
|
+
...(continuity?.unresolvedErrors ?? []).flatMap((error) => error.files),
|
|
9569
9593
|
...(continuity?.openLoops ?? []).flatMap((loop) => loop.files)
|
|
9570
9594
|
]));
|
|
9571
9595
|
const knownFileIndex = buildKnownPathReferenceIndex(knownFiles);
|
|
@@ -9593,8 +9617,8 @@ function verifyProgressConsistency(parsed, extraction, collected, paths, accumul
|
|
|
9593
9617
|
const needles = buildUniquePathNeedlesFromIndex(file.path, modifiedPathOwners);
|
|
9594
9618
|
if (!needles.some((needle) => doneRefs.has(needle)))
|
|
9595
9619
|
continue;
|
|
9596
|
-
const unresolved = collected.unresolved.find((
|
|
9597
|
-
const firstLine =
|
|
9620
|
+
const unresolved = collected.unresolved.find((error) => {
|
|
9621
|
+
const firstLine = error.message.split(/\r?\n/, 1)[0] ?? "";
|
|
9598
9622
|
const refs = extractFileRefs(firstLine).map(normalizePath);
|
|
9599
9623
|
return needles.some((needle) => refs.includes(normalizePath(needle)));
|
|
9600
9624
|
});
|
|
@@ -9645,19 +9669,19 @@ function patchDeterministic(summary, gaps, extraction, continuity = null, eviden
|
|
|
9645
9669
|
const deletedPaths = Array.from(new Set([...extraction.deletedFiles, ...continuity?.deletedFiles ?? []]));
|
|
9646
9670
|
const pathEvidence = buildSummaryPathEvidence([...modifiedPaths, ...readPaths, ...deletedPaths], evidence.summaryBudgetTokens);
|
|
9647
9671
|
const replaceFileSection = (kind, paths) => {
|
|
9648
|
-
const body = paths.map((
|
|
9672
|
+
const body = paths.map((path) => "- " + (pathEvidence.get(path) ?? JSON.stringify(path))).join(`
|
|
9649
9673
|
`);
|
|
9650
9674
|
canonical = upsertSection(canonical, kind, body || "- None recorded.");
|
|
9651
9675
|
};
|
|
9652
9676
|
const safe = (value, max = TRUNC.MESSAGE) => summaryEvidenceLine(value, max);
|
|
9653
9677
|
const unresolvedMessages = Array.from(new Set([
|
|
9654
|
-
...extraction.errors.filter((
|
|
9655
|
-
...(continuity?.unresolvedErrors ?? []).map((
|
|
9678
|
+
...extraction.errors.filter((error) => !error.resolved).map((error) => error.message),
|
|
9679
|
+
...(continuity?.unresolvedErrors ?? []).map((error) => error.message)
|
|
9656
9680
|
]));
|
|
9657
9681
|
const unresolvedLoops = (continuity?.openLoops ?? []).filter((loop) => loop.status !== "resolved");
|
|
9658
9682
|
const blockedItems = [
|
|
9659
9683
|
...unresolvedMessages.map((message) => safe(message)).filter(Boolean).map((message) => "- " + message),
|
|
9660
|
-
...unresolvedLoops.map((loop) => safe(loop.summary)).filter(Boolean).map((
|
|
9684
|
+
...unresolvedLoops.map((loop) => safe(loop.summary)).filter(Boolean).map((summary) => "- " + summary)
|
|
9661
9685
|
];
|
|
9662
9686
|
const patchBlockedNone = () => {
|
|
9663
9687
|
const progress = findSection(canonical, "progress");
|
|
@@ -9732,8 +9756,8 @@ function patchDeterministic(summary, gaps, extraction, continuity = null, eviden
|
|
|
9732
9756
|
canonical = upsertSection(canonical, "goal", safe(gap.goal, TRUNC.DETAIL) || "Continue the current task.");
|
|
9733
9757
|
break;
|
|
9734
9758
|
case "missing-open-loops": {
|
|
9735
|
-
const current = extraction.errors.filter((
|
|
9736
|
-
const carriedErrors = (continuity?.unresolvedErrors ?? []).map((
|
|
9759
|
+
const current = extraction.errors.filter((error) => !error.resolved).map((error) => safe(error.message, TRUNC.SNIPPET)).filter(Boolean).map((message) => "- [high] Resolve " + message);
|
|
9760
|
+
const carriedErrors = (continuity?.unresolvedErrors ?? []).map((error) => safe(error.message, TRUNC.SNIPPET)).filter(Boolean).map((message) => "- [high] Resolve " + message);
|
|
9737
9761
|
const carriedLoops = (continuity?.openLoops ?? []).filter((loop) => loop.status !== "resolved").map((loop) => ({
|
|
9738
9762
|
priority: loop.priority,
|
|
9739
9763
|
summary: safe(loop.summary, TRUNC.SNIPPET)
|
|
@@ -9828,8 +9852,8 @@ Return the COMPLETE corrected summary in the same format.`;
|
|
|
9828
9852
|
]));
|
|
9829
9853
|
const preserved = originalSections.every((section) => !section.body.trim() || Boolean(patchedBodies.get(sectionIdentity(section))));
|
|
9830
9854
|
return preserved ? patched : summary;
|
|
9831
|
-
} catch (
|
|
9832
|
-
debug("patchSummary LLM failed",
|
|
9855
|
+
} catch (error) {
|
|
9856
|
+
debug("patchSummary LLM failed", error);
|
|
9833
9857
|
return summary;
|
|
9834
9858
|
}
|
|
9835
9859
|
}
|
|
@@ -9905,20 +9929,20 @@ function failureAction(kind) {
|
|
|
9905
9929
|
return "For local stack diagnostics, restart Pi with DEBUG=smart-compact and reproduce.";
|
|
9906
9930
|
}
|
|
9907
9931
|
}
|
|
9908
|
-
function formatGenerationFailureForUi(
|
|
9909
|
-
const kind = classifyTelemetryFailure(
|
|
9932
|
+
function formatGenerationFailureForUi(error) {
|
|
9933
|
+
const kind = classifyTelemetryFailure(error);
|
|
9910
9934
|
return kind + ". " + failureAction(kind);
|
|
9911
9935
|
}
|
|
9912
|
-
function formatCompactErrorForUi(
|
|
9913
|
-
if (
|
|
9914
|
-
const kinds =
|
|
9915
|
-
return "Verification stopped apply at the " +
|
|
9936
|
+
function formatCompactErrorForUi(error) {
|
|
9937
|
+
if (error instanceof VerificationGateError) {
|
|
9938
|
+
const kinds = error.gapKinds.slice(0, 4).join(", ") || "unknown";
|
|
9939
|
+
return "Verification stopped apply at the " + error.stage + " gate: " + error.score + "/100, " + error.gapCount + (error.gapCount === 1 ? " unresolved gap [" : " unresolved gaps [") + kinds + "]. Conversation unchanged. " + "Review /smart-compact metrics; do not bypass verification. For local evidence, restart Pi with DEBUG=smart-compact.";
|
|
9916
9940
|
}
|
|
9917
|
-
if (
|
|
9918
|
-
const reason =
|
|
9919
|
-
return "Yield check stopped apply: estimated " +
|
|
9941
|
+
if (error instanceof YieldGateError) {
|
|
9942
|
+
const reason = error.reason === "target-miss" ? "target missed" : "saving below 10%";
|
|
9943
|
+
return "Yield check stopped apply: estimated " + error.estimatedAfterTokens.toLocaleString() + "t after vs " + error.targetAfterTokens.toLocaleString() + "t target (" + reason + "). Conversation unchanged. Try /smart-compact balanced for a larger target; safety checks still apply.";
|
|
9920
9944
|
}
|
|
9921
|
-
return "Smart compact failed [" + classifyTelemetryFailure(
|
|
9945
|
+
return "Smart compact failed [" + classifyTelemetryFailure(error) + "]. Conversation unchanged. " + failureAction(classifyTelemetryFailure(error));
|
|
9922
9946
|
}
|
|
9923
9947
|
|
|
9924
9948
|
// src/app/steps/synthesize.ts
|
|
@@ -9931,8 +9955,8 @@ async function summarizeConversation(rc) {
|
|
|
9931
9955
|
const refined = resolveMode("auto", rc.contextPercent, extraction, continuityRisk(rc.previousState) + (rc.adapted ? 12 : 0));
|
|
9932
9956
|
if (refined !== rc.mode) {
|
|
9933
9957
|
rc.mode = refined;
|
|
9934
|
-
const
|
|
9935
|
-
rc.services.budget.setLimits(resolveCallBudget(rc.config.maxLlmCalls, refined, rc.maxLlmCalls, rc.flags.autoTriggered && !rc.flags.skipCompact), effectiveBudget(rc.config.maxLlmInputTokens,
|
|
9958
|
+
const policy = MODE_POLICIES[refined];
|
|
9959
|
+
rc.services.budget.setLimits(resolveCallBudget(rc.config.maxLlmCalls, refined, rc.maxLlmCalls, rc.flags.autoTriggered && !rc.flags.skipCompact), effectiveBudget(rc.config.maxLlmInputTokens, policy.maxInputTokens, rc.maxLlmInputTokens), policy.maxOutputTokens);
|
|
9936
9960
|
rc.notify("Auto strategy refined to " + refined + " within the planned " + rc.profile + " window", "info");
|
|
9937
9961
|
}
|
|
9938
9962
|
}
|
|
@@ -9972,9 +9996,9 @@ async function summarizeConversation(rc) {
|
|
|
9972
9996
|
phaseName: "Synthesize",
|
|
9973
9997
|
detail: "Building a deterministic continuation summary \xB7 no LLM call"
|
|
9974
9998
|
});
|
|
9975
|
-
const
|
|
9999
|
+
const finalSummary = assembleFallback([], extraction, { focus: rc.focus, note: rc.userNote }, pc.summaryBudgetTokens, rc.previousState);
|
|
9976
10000
|
setCachedSynthesis(cacheKey, {
|
|
9977
|
-
finalSummary
|
|
10001
|
+
finalSummary,
|
|
9978
10002
|
method: "heuristic",
|
|
9979
10003
|
summaries: [],
|
|
9980
10004
|
explorationReport: null,
|
|
@@ -9983,7 +10007,7 @@ async function summarizeConversation(rc) {
|
|
|
9983
10007
|
});
|
|
9984
10008
|
rc.notify("Zero-call deterministic compaction (high-confidence extraction)", "info");
|
|
9985
10009
|
Object.assign(rc, {
|
|
9986
|
-
finalSummary
|
|
10010
|
+
finalSummary,
|
|
9987
10011
|
method: "heuristic",
|
|
9988
10012
|
methodForMetrics: "zero-call",
|
|
9989
10013
|
generationFallbacks: [],
|
|
@@ -10012,11 +10036,11 @@ async function summarizeConversation(rc) {
|
|
|
10012
10036
|
let summaryAuth;
|
|
10013
10037
|
try {
|
|
10014
10038
|
summaryAuth = await resolveStageAuth(rc, "summary");
|
|
10015
|
-
} catch (
|
|
10039
|
+
} catch (error) {
|
|
10016
10040
|
cacheable = false;
|
|
10017
10041
|
generationFallbacks.push("summary route unavailable");
|
|
10018
|
-
debugError("Summary route unavailable",
|
|
10019
|
-
rc.notify("Summary route unavailable \xB7 using deterministic fallback [" + formatGenerationFailureForUi(
|
|
10042
|
+
debugError("Summary route unavailable", error);
|
|
10043
|
+
rc.notify("Summary route unavailable \xB7 using deterministic fallback [" + formatGenerationFailureForUi(error) + "]", "warning");
|
|
10020
10044
|
}
|
|
10021
10045
|
if (!summaryAuth) {
|
|
10022
10046
|
showProgressOverlay(rc.ctx, {
|
|
@@ -10128,6 +10152,9 @@ async function summarizeConversation(rc) {
|
|
|
10128
10152
|
totalBatches
|
|
10129
10153
|
});
|
|
10130
10154
|
const concurrency = rc.providerCaps.concurrencyLimit;
|
|
10155
|
+
if (rc.services.thinkingLevels.summaryThinkingLevel == null) {
|
|
10156
|
+
rc.notify("Summary thinking level unset \u2014 provider-default reasoning may share the batch output budget (length-truncated batches retry once at minimal reasoning)", "info");
|
|
10157
|
+
}
|
|
10131
10158
|
if (totalBatches <= 1) {
|
|
10132
10159
|
const single = batches[0];
|
|
10133
10160
|
if (single) {
|
|
@@ -10213,8 +10240,8 @@ async function summarizeConversation(rc) {
|
|
|
10213
10240
|
if (r)
|
|
10214
10241
|
summaries.push(...r);
|
|
10215
10242
|
const failedBatches = errors.filter(Boolean);
|
|
10216
|
-
for (const
|
|
10217
|
-
debugError("Synthesis batch used deterministic fallback",
|
|
10243
|
+
for (const error of failedBatches)
|
|
10244
|
+
debugError("Synthesis batch used deterministic fallback", error);
|
|
10218
10245
|
if (failedBatches.length) {
|
|
10219
10246
|
cacheable = false;
|
|
10220
10247
|
generationFallbacks.push(failedBatches.length + " synthesis batch fallback(s)");
|
|
@@ -10331,8 +10358,8 @@ async function verifyAndPatch(rc) {
|
|
|
10331
10358
|
try {
|
|
10332
10359
|
const verifyAuth = await resolveStageAuth(rc, "verify");
|
|
10333
10360
|
summary = await patchSummary(summary, verification.gaps, rc.verifyModel ?? rc.summaryModel, verifyAuth, rc.cancellation.signal, rc.services);
|
|
10334
|
-
} catch (
|
|
10335
|
-
debugError("LLM verification patch used deterministic fallback",
|
|
10361
|
+
} catch (error) {
|
|
10362
|
+
debugError("LLM verification patch used deterministic fallback", error);
|
|
10336
10363
|
}
|
|
10337
10364
|
if (summary !== beforePatch) {
|
|
10338
10365
|
llmPatched = true;
|
|
@@ -10543,11 +10570,11 @@ function bunSqliteAdapter(db) {
|
|
|
10543
10570
|
const result = fn(...args);
|
|
10544
10571
|
db.exec("COMMIT");
|
|
10545
10572
|
return result;
|
|
10546
|
-
} catch (
|
|
10573
|
+
} catch (error) {
|
|
10547
10574
|
try {
|
|
10548
10575
|
db.exec("ROLLBACK");
|
|
10549
10576
|
} catch {}
|
|
10550
|
-
throw
|
|
10577
|
+
throw error;
|
|
10551
10578
|
}
|
|
10552
10579
|
},
|
|
10553
10580
|
close: () => db.close()
|
|
@@ -10563,11 +10590,11 @@ function nodeSqliteAdapter(db) {
|
|
|
10563
10590
|
const result = fn(...args);
|
|
10564
10591
|
db.exec("COMMIT");
|
|
10565
10592
|
return result;
|
|
10566
|
-
} catch (
|
|
10593
|
+
} catch (error) {
|
|
10567
10594
|
try {
|
|
10568
10595
|
db.exec("ROLLBACK");
|
|
10569
10596
|
} catch {}
|
|
10570
|
-
throw
|
|
10597
|
+
throw error;
|
|
10571
10598
|
}
|
|
10572
10599
|
},
|
|
10573
10600
|
close: () => db.close()
|
|
@@ -10810,7 +10837,10 @@ function addFact(db, scope, sessionNodeId, kind, title, content, relatedPaths =
|
|
|
10810
10837
|
});
|
|
10811
10838
|
node.factKey = factKey(keyText);
|
|
10812
10839
|
node.id = stableId(scope.projectId, scope.sessionId, kind, node.factKey, scope.branchHeadId ?? "");
|
|
10813
|
-
|
|
10840
|
+
const latest = latestLineageFact(db, scope, kind, node.factKey);
|
|
10841
|
+
if (sameActiveFact(latest, node))
|
|
10842
|
+
return;
|
|
10843
|
+
if (latest && latest.status !== "active" && node.status === "active")
|
|
10814
10844
|
return;
|
|
10815
10845
|
upsertNode(db, node);
|
|
10816
10846
|
linkNodes(db, scope.projectId, sessionNodeId, node.id, "contains", 1, node.updatedAt);
|
|
@@ -10895,7 +10925,10 @@ function indexCompactionState(projectId, state) {
|
|
|
10895
10925
|
relatedPaths: item.files,
|
|
10896
10926
|
confidence: item.priority === "critical" || item.priority === "high" ? 0.98 : 0.88
|
|
10897
10927
|
});
|
|
10898
|
-
|
|
10928
|
+
const latestLoop = latestLineageFact(db, scope, "loop", node.factKey);
|
|
10929
|
+
if (sameActiveFact(latestLoop, node))
|
|
10930
|
+
continue;
|
|
10931
|
+
if (latestLoop && latestLoop.status !== "active" && node.status === "active")
|
|
10899
10932
|
continue;
|
|
10900
10933
|
upsertNode(db, node);
|
|
10901
10934
|
linkNodes(db, projectId, sessionNode.id, node.id, "contains", 1, now);
|
|
@@ -10935,8 +10968,8 @@ function indexCompactionState(projectId, state) {
|
|
|
10935
10968
|
});
|
|
10936
10969
|
transaction();
|
|
10937
10970
|
return true;
|
|
10938
|
-
} catch (
|
|
10939
|
-
warn("indexCompactionState failed",
|
|
10971
|
+
} catch (error) {
|
|
10972
|
+
warn("indexCompactionState failed", error);
|
|
10940
10973
|
return false;
|
|
10941
10974
|
}
|
|
10942
10975
|
}
|
|
@@ -10944,8 +10977,8 @@ var pendingCompactionIndexes = new Map;
|
|
|
10944
10977
|
var compactionIndexDrainScheduled = false;
|
|
10945
10978
|
var MAX_PENDING_COMPACTION_INDEXES = 64;
|
|
10946
10979
|
function settleIndexJob(job, indexed) {
|
|
10947
|
-
for (const
|
|
10948
|
-
|
|
10980
|
+
for (const resolve of job.resolve)
|
|
10981
|
+
resolve(indexed);
|
|
10949
10982
|
}
|
|
10950
10983
|
function drainCompactionIndexes() {
|
|
10951
10984
|
compactionIndexDrainScheduled = false;
|
|
@@ -10959,8 +10992,8 @@ function drainCompactionIndexes() {
|
|
|
10959
10992
|
for (const job of jobs) {
|
|
10960
10993
|
settleIndexJob(job, indexCompactionState(job.projectId, job.state));
|
|
10961
10994
|
}
|
|
10962
|
-
} catch (
|
|
10963
|
-
warn("context graph index drain failed",
|
|
10995
|
+
} catch (error) {
|
|
10996
|
+
warn("context graph index drain failed", error);
|
|
10964
10997
|
for (const job of jobs)
|
|
10965
10998
|
settleIndexJob(job, false);
|
|
10966
10999
|
} finally {
|
|
@@ -10981,22 +11014,25 @@ function scheduleCompactionStateIndex(projectId, state) {
|
|
|
10981
11014
|
if (!sessionId || !branchHeadId || state.scope?.projectId !== projectId)
|
|
10982
11015
|
return Promise.resolve(false);
|
|
10983
11016
|
const key = projectId + "\x00" + sessionId + "\x00" + branchHeadId;
|
|
10984
|
-
return new Promise((
|
|
11017
|
+
return new Promise((resolve) => {
|
|
10985
11018
|
const existing = pendingCompactionIndexes.get(key);
|
|
10986
11019
|
if (existing) {
|
|
10987
11020
|
existing.state = state;
|
|
10988
|
-
existing.resolve.push(
|
|
11021
|
+
existing.resolve.push(resolve);
|
|
10989
11022
|
return;
|
|
10990
11023
|
}
|
|
10991
11024
|
if (pendingCompactionIndexes.size >= MAX_PENDING_COMPACTION_INDEXES) {
|
|
10992
11025
|
warn("context graph index queue full; new derived update was rejected");
|
|
10993
|
-
|
|
11026
|
+
resolve(false);
|
|
10994
11027
|
return;
|
|
10995
11028
|
}
|
|
10996
|
-
pendingCompactionIndexes.set(key, { projectId, state, resolve: [
|
|
11029
|
+
pendingCompactionIndexes.set(key, { projectId, state, resolve: [resolve] });
|
|
10997
11030
|
armCompactionIndexDrain();
|
|
10998
11031
|
});
|
|
10999
11032
|
}
|
|
11033
|
+
function flushCompactionStateIndexes() {
|
|
11034
|
+
drainCompactionIndexes();
|
|
11035
|
+
}
|
|
11000
11036
|
function closeContextMemory(projectId, kind, content, status) {
|
|
11001
11037
|
const db = openDatabase();
|
|
11002
11038
|
const rows = db.query(`
|
|
@@ -11225,8 +11261,8 @@ function recallContext(scope, query, options = {}) {
|
|
|
11225
11261
|
break;
|
|
11226
11262
|
}
|
|
11227
11263
|
return [...deduped.values()];
|
|
11228
|
-
} catch (
|
|
11229
|
-
warn("recallContext failed",
|
|
11264
|
+
} catch (error) {
|
|
11265
|
+
warn("recallContext failed", error);
|
|
11230
11266
|
return [];
|
|
11231
11267
|
}
|
|
11232
11268
|
}
|
|
@@ -11260,6 +11296,42 @@ function formatRecallResults(results, maxChars = 6000) {
|
|
|
11260
11296
|
|
|
11261
11297
|
`);
|
|
11262
11298
|
}
|
|
11299
|
+
function getContextGraphStats(projectId) {
|
|
11300
|
+
try {
|
|
11301
|
+
const db = openDatabase();
|
|
11302
|
+
const row = db.query(`
|
|
11303
|
+
SELECT count(*) AS totalNodes,
|
|
11304
|
+
sum(CASE WHEN status = 'active' THEN 1 ELSE 0 END) AS activeNodes,
|
|
11305
|
+
count(DISTINCT CASE WHEN kind = 'session' THEN session_id END) AS sessions,
|
|
11306
|
+
max(updated_at) AS lastUpdatedAt
|
|
11307
|
+
FROM context_nodes WHERE project_id = ? AND kind NOT IN ('project')
|
|
11308
|
+
`).get(projectId);
|
|
11309
|
+
return {
|
|
11310
|
+
totalNodes: Number(row?.totalNodes ?? 0),
|
|
11311
|
+
activeNodes: Number(row?.activeNodes ?? 0),
|
|
11312
|
+
sessions: Number(row?.sessions ?? 0),
|
|
11313
|
+
lastUpdatedAt: row?.lastUpdatedAt == null ? null : Number(row.lastUpdatedAt)
|
|
11314
|
+
};
|
|
11315
|
+
} catch {
|
|
11316
|
+
return { totalNodes: 0, activeNodes: 0, sessions: 0, lastUpdatedAt: null };
|
|
11317
|
+
}
|
|
11318
|
+
}
|
|
11319
|
+
function forgetProjectGraph(projectId) {
|
|
11320
|
+
try {
|
|
11321
|
+
flushCompactionStateIndexes();
|
|
11322
|
+
const db = openDatabase();
|
|
11323
|
+
const transaction = db.transaction(() => {
|
|
11324
|
+
db.query("DELETE FROM context_nodes_fts WHERE node_id IN (SELECT id FROM context_nodes WHERE project_id = ?)").run(projectId);
|
|
11325
|
+
db.query("DELETE FROM context_edges WHERE project_id = ?").run(projectId);
|
|
11326
|
+
db.query("DELETE FROM context_nodes WHERE project_id = ?").run(projectId);
|
|
11327
|
+
});
|
|
11328
|
+
transaction();
|
|
11329
|
+
return true;
|
|
11330
|
+
} catch (error) {
|
|
11331
|
+
warn("forgetProjectGraph failed", error);
|
|
11332
|
+
return false;
|
|
11333
|
+
}
|
|
11334
|
+
}
|
|
11263
11335
|
|
|
11264
11336
|
// src/domain/provider-evaluation.ts
|
|
11265
11337
|
function providerStage(phase) {
|
|
@@ -11804,10 +11876,10 @@ var POLICY_PATHS = new Set([
|
|
|
11804
11876
|
"autoTrigger",
|
|
11805
11877
|
"showStatus"
|
|
11806
11878
|
]);
|
|
11807
|
-
function applyGlobalSettingRuntime(
|
|
11808
|
-
if (POLICY_PATHS.has(
|
|
11879
|
+
function applyGlobalSettingRuntime(path, ctx, policy, contextTools) {
|
|
11880
|
+
if (POLICY_PATHS.has(path))
|
|
11809
11881
|
policy.restore(ctx);
|
|
11810
|
-
if (
|
|
11882
|
+
if (path === "contextGraphEnabled")
|
|
11811
11883
|
contextTools.apply();
|
|
11812
11884
|
}
|
|
11813
11885
|
|
|
@@ -12073,8 +12145,8 @@ function createNativeContinuityBridge(opts = {}) {
|
|
|
12073
12145
|
fs10.chmodSync(target, 384);
|
|
12074
12146
|
} catch {}
|
|
12075
12147
|
});
|
|
12076
|
-
} catch (
|
|
12077
|
-
debug("native continuity stage failed",
|
|
12148
|
+
} catch (error) {
|
|
12149
|
+
debug("native continuity stage failed", error);
|
|
12078
12150
|
}
|
|
12079
12151
|
},
|
|
12080
12152
|
take(scope) {
|
|
@@ -12094,8 +12166,8 @@ function createNativeContinuityBridge(opts = {}) {
|
|
|
12094
12166
|
}
|
|
12095
12167
|
return sameScope(entry.scope, scope) && now() - entry.createdAt <= ttlMs ? entry.text : null;
|
|
12096
12168
|
});
|
|
12097
|
-
} catch (
|
|
12098
|
-
debug("native continuity take failed",
|
|
12169
|
+
} catch (error) {
|
|
12170
|
+
debug("native continuity take failed", error);
|
|
12099
12171
|
return null;
|
|
12100
12172
|
}
|
|
12101
12173
|
},
|
|
@@ -12114,15 +12186,15 @@ function createNativeContinuityBridge(opts = {}) {
|
|
|
12114
12186
|
} catch {}
|
|
12115
12187
|
}
|
|
12116
12188
|
});
|
|
12117
|
-
} catch (
|
|
12118
|
-
debug("native continuity clear failed",
|
|
12189
|
+
} catch (error) {
|
|
12190
|
+
debug("native continuity clear failed", error);
|
|
12119
12191
|
}
|
|
12120
12192
|
},
|
|
12121
12193
|
size() {
|
|
12122
12194
|
try {
|
|
12123
12195
|
return locked(() => prune(0).length);
|
|
12124
|
-
} catch (
|
|
12125
|
-
debug("native continuity size failed",
|
|
12196
|
+
} catch (error) {
|
|
12197
|
+
debug("native continuity size failed", error);
|
|
12126
12198
|
return 0;
|
|
12127
12199
|
}
|
|
12128
12200
|
}
|
|
@@ -12163,7 +12235,7 @@ function createSettledAutoTrigger(options = {}) {
|
|
|
12163
12235
|
return;
|
|
12164
12236
|
const requestToken = Symbol(sessionId);
|
|
12165
12237
|
active.set(sessionId, requestToken);
|
|
12166
|
-
await new Promise((
|
|
12238
|
+
await new Promise((resolve) => {
|
|
12167
12239
|
let finished = false;
|
|
12168
12240
|
const finish = () => {
|
|
12169
12241
|
if (finished)
|
|
@@ -12171,7 +12243,7 @@ function createSettledAutoTrigger(options = {}) {
|
|
|
12171
12243
|
finished = true;
|
|
12172
12244
|
if (active.get(sessionId) === requestToken)
|
|
12173
12245
|
active.delete(sessionId);
|
|
12174
|
-
|
|
12246
|
+
resolve();
|
|
12175
12247
|
};
|
|
12176
12248
|
try {
|
|
12177
12249
|
ctx.compact({
|
|
@@ -12180,13 +12252,13 @@ function createSettledAutoTrigger(options = {}) {
|
|
|
12180
12252
|
noteCompaction(sessionId);
|
|
12181
12253
|
finish();
|
|
12182
12254
|
},
|
|
12183
|
-
onError: (
|
|
12184
|
-
debugError("Settled smart compact request failed",
|
|
12255
|
+
onError: (error) => {
|
|
12256
|
+
debugError("Settled smart compact request failed", error);
|
|
12185
12257
|
finish();
|
|
12186
12258
|
}
|
|
12187
12259
|
});
|
|
12188
|
-
} catch (
|
|
12189
|
-
debugError("Settled smart compact request failed",
|
|
12260
|
+
} catch (error) {
|
|
12261
|
+
debugError("Settled smart compact request failed", error);
|
|
12190
12262
|
finish();
|
|
12191
12263
|
}
|
|
12192
12264
|
});
|
|
@@ -12430,14 +12502,13 @@ Paths: ` + relatedPaths.join(", ") : ""));
|
|
|
12430
12502
|
],
|
|
12431
12503
|
details: { memory, redactions: scrubber.count() }
|
|
12432
12504
|
};
|
|
12433
|
-
} catch (
|
|
12434
|
-
debugError("Project memory persistence failed",
|
|
12435
|
-
const message = scrubber.scrubText(
|
|
12505
|
+
} catch (error) {
|
|
12506
|
+
debugError("Project memory persistence failed", error);
|
|
12507
|
+
const message = scrubber.scrubText(error instanceof Error ? error.message : String(error)).value;
|
|
12436
12508
|
throw new Error("Project memory could not be saved: " + message);
|
|
12437
12509
|
}
|
|
12438
12510
|
}
|
|
12439
12511
|
});
|
|
12440
|
-
availability.apply();
|
|
12441
12512
|
pi.on("session_start", availability.apply);
|
|
12442
12513
|
return availability;
|
|
12443
12514
|
}
|
|
@@ -12468,8 +12539,8 @@ function createContextToolAvailability(pi) {
|
|
|
12468
12539
|
hiddenByConfig.clear();
|
|
12469
12540
|
disabledByConfig = false;
|
|
12470
12541
|
}
|
|
12471
|
-
} catch (
|
|
12472
|
-
debugError("Context tool availability update failed",
|
|
12542
|
+
} catch (error) {
|
|
12543
|
+
debugError("Context tool availability update failed", error);
|
|
12473
12544
|
}
|
|
12474
12545
|
}
|
|
12475
12546
|
};
|
|
@@ -12856,14 +12927,14 @@ function summarizeDashboard(entries) {
|
|
|
12856
12927
|
const durations = entries.map(metricDuration).filter(Boolean);
|
|
12857
12928
|
const success = entries.filter((e) => statusLabel(e.status) === "success").length;
|
|
12858
12929
|
const timeout = entries.filter((e) => e.status === "timeout").length;
|
|
12859
|
-
const
|
|
12930
|
+
const error = entries.filter((e) => e.status === "error").length;
|
|
12860
12931
|
const dryRun = entries.filter((e) => e.status === "dry-run").length;
|
|
12861
12932
|
const scored = entries.map((e) => e.verificationScore).filter((v) => typeof v === "number");
|
|
12862
12933
|
return {
|
|
12863
12934
|
runs: entries.length,
|
|
12864
12935
|
success,
|
|
12865
12936
|
timeout,
|
|
12866
|
-
error
|
|
12937
|
+
error,
|
|
12867
12938
|
dryRun,
|
|
12868
12939
|
successRate: entries.length ? success / entries.length : 0,
|
|
12869
12940
|
avgDuration: Math.round(average(durations)),
|
|
@@ -13202,7 +13273,8 @@ var ACTIONS = {
|
|
|
13202
13273
|
dashboard: "dashboard",
|
|
13203
13274
|
restore: "restore",
|
|
13204
13275
|
loops: "loops",
|
|
13205
|
-
settings: "settings"
|
|
13276
|
+
settings: "settings",
|
|
13277
|
+
forget: "forget"
|
|
13206
13278
|
};
|
|
13207
13279
|
var MODES = {
|
|
13208
13280
|
auto: "auto",
|
|
@@ -13415,12 +13487,12 @@ Dashboard: ` + dashboard : ""));
|
|
|
13415
13487
|
const usage = ctx.getContextUsage?.();
|
|
13416
13488
|
const totalTokens = usage?.tokens ?? 0;
|
|
13417
13489
|
const contextPercent = safeContextPercent(totalTokens, ctx.model?.contextWindow);
|
|
13418
|
-
const
|
|
13490
|
+
const percent = Math.round(contextPercent);
|
|
13419
13491
|
if (!totalTokens || totalTokens < MIN_TOKEN_THRESHOLD) {
|
|
13420
|
-
return textResult("Context is not large enough for compaction (" + totalTokens.toLocaleString() + " tokens, " +
|
|
13492
|
+
return textResult("Context is not large enough for compaction (" + totalTokens.toLocaleString() + " tokens, " + percent + "%). No action needed.");
|
|
13421
13493
|
}
|
|
13422
13494
|
if (contextPercent < config.minContextPercent) {
|
|
13423
|
-
return textResult("Compaction skipped: context " +
|
|
13495
|
+
return textResult("Compaction skipped: context " + percent + "% (" + totalTokens.toLocaleString() + " / " + (ctx.model?.contextWindow ?? 0).toLocaleString() + " tokens), below the " + config.minContextPercent + "% agent-tool threshold. tool=XX% measures tool-output ratio, not context usage. " + "For deliberate early compaction, the user can run /smart-compact; preview and safety checks still apply.");
|
|
13424
13496
|
}
|
|
13425
13497
|
const current = ctx.model;
|
|
13426
13498
|
const { segModel, sumModel, verifyModel } = resolveModels(ctx, current, config);
|
|
@@ -13475,9 +13547,9 @@ Dashboard: ` + dashboard : ""));
|
|
|
13475
13547
|
return textResult("Smart compact cancelled by " + outcome.source + "; no summary was staged.");
|
|
13476
13548
|
}
|
|
13477
13549
|
return textResult("Smart compact skipped: " + outcome.reason.replace(/-/g, " ") + ". No summary was staged.");
|
|
13478
|
-
} catch (
|
|
13479
|
-
debugError("Smart compact tool failed",
|
|
13480
|
-
throw new Error(formatCompactErrorForUi(
|
|
13550
|
+
} catch (error) {
|
|
13551
|
+
debugError("Smart compact tool failed", error);
|
|
13552
|
+
throw new Error(formatCompactErrorForUi(error));
|
|
13481
13553
|
}
|
|
13482
13554
|
}
|
|
13483
13555
|
});
|
|
@@ -13751,7 +13823,7 @@ var LIMIT_SETTINGS = [
|
|
|
13751
13823
|
id: "codexMaxCallMs",
|
|
13752
13824
|
label: "Codex call watchdog",
|
|
13753
13825
|
description: "Zero derives the per-call watchdog automatically.",
|
|
13754
|
-
placeholder: "0 or 5000\
|
|
13826
|
+
placeholder: "0 or 5000\u20133600000; blank uses default",
|
|
13755
13827
|
parse: numberParser(CONFIG_NUMERIC_LIMITS.codexMaxCallMs),
|
|
13756
13828
|
format: scalarFormat
|
|
13757
13829
|
},
|
|
@@ -13759,9 +13831,17 @@ var LIMIT_SETTINGS = [
|
|
|
13759
13831
|
id: "maxLatencyMs",
|
|
13760
13832
|
label: "Pipeline latency limit",
|
|
13761
13833
|
description: "Zero disables the overall pipeline deadline.",
|
|
13762
|
-
placeholder: "0 or 5000\
|
|
13834
|
+
placeholder: "0 or 5000\u20137200000; blank uses default",
|
|
13763
13835
|
parse: numberParser(CONFIG_NUMERIC_LIMITS.maxLatencyMs),
|
|
13764
13836
|
format: scalarFormat
|
|
13837
|
+
},
|
|
13838
|
+
{
|
|
13839
|
+
id: "pendingTtlMs",
|
|
13840
|
+
label: "Staged summary TTL",
|
|
13841
|
+
description: "How long a staged summary waits for commit before expiry.",
|
|
13842
|
+
placeholder: "1000\u20133600000; blank uses default",
|
|
13843
|
+
parse: numberParser(CONFIG_NUMERIC_LIMITS.pendingTtlMs),
|
|
13844
|
+
format: scalarFormat
|
|
13765
13845
|
}
|
|
13766
13846
|
];
|
|
13767
13847
|
var PATH_SETTINGS = [
|
|
@@ -13865,17 +13945,17 @@ class InputSettingEditor extends Container3 {
|
|
|
13865
13945
|
let parsed;
|
|
13866
13946
|
try {
|
|
13867
13947
|
parsed = setting.parse(value);
|
|
13868
|
-
} catch (
|
|
13869
|
-
this.status.setText(
|
|
13948
|
+
} catch (error) {
|
|
13949
|
+
this.status.setText(error instanceof Error ? error.message : String(error));
|
|
13870
13950
|
this.requestRender();
|
|
13871
13951
|
return;
|
|
13872
13952
|
}
|
|
13873
13953
|
this.saving = true;
|
|
13874
13954
|
this.status.setText("Saving\u2026");
|
|
13875
13955
|
this.requestRender();
|
|
13876
|
-
this.pending = this.save(parsed).then(() => this.done()).catch((
|
|
13956
|
+
this.pending = this.save(parsed).then(() => this.done()).catch((error) => {
|
|
13877
13957
|
this.saving = false;
|
|
13878
|
-
this.status.setText(
|
|
13958
|
+
this.status.setText(error instanceof Error ? error.message : String(error));
|
|
13879
13959
|
this.requestRender();
|
|
13880
13960
|
});
|
|
13881
13961
|
};
|
|
@@ -13934,9 +14014,9 @@ class ModelSettingEditor extends Container3 {
|
|
|
13934
14014
|
const selected = models.find((candidate) => candidate.value === item.value);
|
|
13935
14015
|
if (!selected)
|
|
13936
14016
|
return;
|
|
13937
|
-
this.pending = save(selected.settingValue).then(done).catch((
|
|
14017
|
+
this.pending = save(selected.settingValue).then(done).catch((error) => {
|
|
13938
14018
|
this.saving = false;
|
|
13939
|
-
this.status.setText(
|
|
14019
|
+
this.status.setText(error instanceof Error ? error.message : String(error));
|
|
13940
14020
|
this.requestRender();
|
|
13941
14021
|
});
|
|
13942
14022
|
};
|
|
@@ -14024,9 +14104,9 @@ function modelSettingsItems(ctx, requestRender, writeConfig = writeGlobalConfigV
|
|
|
14024
14104
|
});
|
|
14025
14105
|
}
|
|
14026
14106
|
return new ModelSettingEditor(choices, selectedValue, requestRender, async (selected) => {
|
|
14027
|
-
const
|
|
14107
|
+
const effective = await writeConfig(setting.id, selected === "default" ? undefined : selected);
|
|
14028
14108
|
item.currentValue = selected;
|
|
14029
|
-
item.description = `${setting.description} Effective global value: ${String(
|
|
14109
|
+
item.description = `${setting.description} Effective global value: ${String(effective[setting.id])}.`;
|
|
14030
14110
|
}, close);
|
|
14031
14111
|
};
|
|
14032
14112
|
return item;
|
|
@@ -14128,8 +14208,8 @@ class GlobalSettingsCoordinator {
|
|
|
14128
14208
|
this.pending.delete(id);
|
|
14129
14209
|
this.emit(id, { display, config });
|
|
14130
14210
|
}
|
|
14131
|
-
} catch (
|
|
14132
|
-
onError(
|
|
14211
|
+
} catch (error) {
|
|
14212
|
+
onError(error instanceof Error ? error.message : String(error));
|
|
14133
14213
|
if (this.pending.get(id)?.revision === revision) {
|
|
14134
14214
|
this.pending.delete(id);
|
|
14135
14215
|
this.emit(id, {
|
|
@@ -14413,8 +14493,8 @@ function sessionSettingsList(policy, ctx, done) {
|
|
|
14413
14493
|
ctx.ui.notify(result.error, "error");
|
|
14414
14494
|
list.updateValue(id, displayValue(field, policy));
|
|
14415
14495
|
}
|
|
14416
|
-
} catch (
|
|
14417
|
-
ctx.ui.notify(
|
|
14496
|
+
} catch (error) {
|
|
14497
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
14418
14498
|
list.updateValue(id, displayValue(policyField(id), policy));
|
|
14419
14499
|
}
|
|
14420
14500
|
}, done);
|
|
@@ -14511,9 +14591,9 @@ function createSettingsController(root, display, requestRender) {
|
|
|
14511
14591
|
};
|
|
14512
14592
|
}
|
|
14513
14593
|
async function showSmartCompactSettings(ctx, policy, coordinator = new GlobalSettingsCoordinator, onApplied = () => {}) {
|
|
14514
|
-
const writeConfig = async (
|
|
14515
|
-
const config = await writeGlobalConfigValue(
|
|
14516
|
-
await onApplied(
|
|
14594
|
+
const writeConfig = async (path, value) => {
|
|
14595
|
+
const config = await writeGlobalConfigValue(path, value);
|
|
14596
|
+
await onApplied(path, config);
|
|
14517
14597
|
return config;
|
|
14518
14598
|
};
|
|
14519
14599
|
await ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
@@ -14592,10 +14672,10 @@ async function restoreBackup(ctx) {
|
|
|
14592
14672
|
if (result.cancelled)
|
|
14593
14673
|
ctx.ui.notify("Restore cancelled", "info");
|
|
14594
14674
|
return;
|
|
14595
|
-
} catch (
|
|
14596
|
-
debugError("Exact backup restore fork unavailable",
|
|
14597
|
-
if (!/Invalid entry ID for forking/i.test(
|
|
14598
|
-
ctx.ui.notify("Exact restore failed: " + (
|
|
14675
|
+
} catch (error) {
|
|
14676
|
+
debugError("Exact backup restore fork unavailable", error);
|
|
14677
|
+
if (!/Invalid entry ID for forking/i.test(error instanceof Error ? error.message : String(error))) {
|
|
14678
|
+
ctx.ui.notify("Exact restore failed: " + (error instanceof Error ? error.message : String(error)), "error");
|
|
14599
14679
|
return;
|
|
14600
14680
|
}
|
|
14601
14681
|
}
|
|
@@ -14611,10 +14691,36 @@ async function restoreBackup(ctx) {
|
|
|
14611
14691
|
});
|
|
14612
14692
|
if (result.cancelled)
|
|
14613
14693
|
ctx.ui.notify("Restore cancelled", "info");
|
|
14614
|
-
} catch (
|
|
14615
|
-
debugError("Backup restore into new session failed",
|
|
14616
|
-
ctx.ui.notify("Restore failed: " + (
|
|
14694
|
+
} catch (error) {
|
|
14695
|
+
debugError("Backup restore into new session failed", error);
|
|
14696
|
+
ctx.ui.notify("Restore failed: " + (error instanceof Error ? error.message : String(error)), "error");
|
|
14697
|
+
}
|
|
14698
|
+
}
|
|
14699
|
+
async function forgetProjectMemory(ctx) {
|
|
14700
|
+
if (ctx.mode !== "tui") {
|
|
14701
|
+
ctx.ui.notify("Forgetting project memory requires TUI mode for its confirmation dialog", "warning");
|
|
14702
|
+
return;
|
|
14703
|
+
}
|
|
14704
|
+
const projectId = deriveProjectIdFromCwd(ctx.cwd);
|
|
14705
|
+
if (!projectId) {
|
|
14706
|
+
ctx.ui.notify("Project memory must be forgotten from a project directory", "warning");
|
|
14707
|
+
return;
|
|
14708
|
+
}
|
|
14709
|
+
const stats = getContextGraphStats(projectId);
|
|
14710
|
+
if (!stats.totalNodes) {
|
|
14711
|
+
ctx.ui.notify("No persisted memory for this project", "info");
|
|
14712
|
+
return;
|
|
14713
|
+
}
|
|
14714
|
+
const confirmed = await ctx.ui.confirm("Forget project memory?", "Permanently delete " + stats.totalNodes + " memory nodes (" + stats.sessions + " session" + (stats.sessions === 1 ? "" : "s") + ") for this project, including FTS copies and edges. This cannot be undone. Compaction state (restore data) and backups are kept.");
|
|
14715
|
+
if (!confirmed) {
|
|
14716
|
+
ctx.ui.notify("Forget cancelled \u2014 project memory untouched", "info");
|
|
14717
|
+
return;
|
|
14617
14718
|
}
|
|
14719
|
+
if (!forgetProjectGraph(projectId)) {
|
|
14720
|
+
ctx.ui.notify("Forget failed \u2014 the graph database reported an error (see DEBUG=smart-compact log)", "error");
|
|
14721
|
+
return;
|
|
14722
|
+
}
|
|
14723
|
+
ctx.ui.notify("Forgotten: deleted " + stats.totalNodes + " memory nodes for this project", "info");
|
|
14618
14724
|
}
|
|
14619
14725
|
async function manageOpenLoops(ctx) {
|
|
14620
14726
|
const projectId = deriveProjectIdFromCwd(ctx.cwd);
|
|
@@ -14712,6 +14818,7 @@ function registerSmartCompactCommand(pi, dependencies) {
|
|
|
14712
14818
|
"restore",
|
|
14713
14819
|
"loops",
|
|
14714
14820
|
"settings",
|
|
14821
|
+
"forget",
|
|
14715
14822
|
"fast",
|
|
14716
14823
|
"balanced",
|
|
14717
14824
|
"thorough",
|
|
@@ -14747,12 +14854,16 @@ function registerSmartCompactCommand(pi, dependencies) {
|
|
|
14747
14854
|
await manageOpenLoops(ctx);
|
|
14748
14855
|
return;
|
|
14749
14856
|
}
|
|
14857
|
+
if (input.action === "forget") {
|
|
14858
|
+
await forgetProjectMemory(ctx);
|
|
14859
|
+
return;
|
|
14860
|
+
}
|
|
14750
14861
|
if (input.action === "settings") {
|
|
14751
14862
|
if (ctx.mode !== "tui") {
|
|
14752
14863
|
ctx.ui.notify("Smart Compact settings require TUI mode. Use settings.json for permanent defaults.", "warning");
|
|
14753
14864
|
return;
|
|
14754
14865
|
}
|
|
14755
|
-
await showSmartCompactSettings(ctx, dependencies.policy, settingsCoordinator, (
|
|
14866
|
+
await showSmartCompactSettings(ctx, dependencies.policy, settingsCoordinator, (path) => dependencies.onGlobalSettingApplied?.(path, ctx));
|
|
14756
14867
|
return;
|
|
14757
14868
|
}
|
|
14758
14869
|
const config = loadConfig();
|
|
@@ -14788,9 +14899,9 @@ function registerSmartCompactCommand(pi, dependencies) {
|
|
|
14788
14899
|
timeoutMs: input.timeoutMs,
|
|
14789
14900
|
force: true
|
|
14790
14901
|
});
|
|
14791
|
-
} catch (
|
|
14792
|
-
debugError("Manual smart compact failed",
|
|
14793
|
-
ctx.ui.notify(formatCompactErrorForUi(
|
|
14902
|
+
} catch (error) {
|
|
14903
|
+
debugError("Manual smart compact failed", error);
|
|
14904
|
+
ctx.ui.notify(formatCompactErrorForUi(error), "error");
|
|
14794
14905
|
}
|
|
14795
14906
|
}
|
|
14796
14907
|
});
|
|
@@ -14888,12 +14999,12 @@ function createSmartCompactPolicy(pi) {
|
|
|
14888
14999
|
ctx.ui.setStatus(STATUS_KEY, current.showStatus ? statusText(effective) : undefined);
|
|
14889
15000
|
return effective;
|
|
14890
15001
|
};
|
|
14891
|
-
const restoreToolMembership = (
|
|
15002
|
+
const restoreToolMembership = (enabled) => {
|
|
14892
15003
|
const active = pi.getActiveTools();
|
|
14893
15004
|
const hasTool = active.includes(SMART_COMPACT_TOOL_NAME);
|
|
14894
|
-
if (
|
|
15005
|
+
if (enabled && !hasTool) {
|
|
14895
15006
|
pi.setActiveTools([...new Set([...active, SMART_COMPACT_TOOL_NAME])]);
|
|
14896
|
-
} else if (!
|
|
15007
|
+
} else if (!enabled && hasTool) {
|
|
14897
15008
|
pi.setActiveTools(active.filter((name) => name !== SMART_COMPACT_TOOL_NAME));
|
|
14898
15009
|
}
|
|
14899
15010
|
};
|
|
@@ -14905,8 +15016,8 @@ function createSmartCompactPolicy(pi) {
|
|
|
14905
15016
|
const effective = apply(ctx);
|
|
14906
15017
|
pi.appendEntry(SMART_COMPACT_POLICY_ENTRY, { version: POLICY_VERSION, overrides: { ...overrides } });
|
|
14907
15018
|
return { ok: true, policy: effective };
|
|
14908
|
-
} catch (
|
|
14909
|
-
debugError("Smart Compact policy update failed",
|
|
15019
|
+
} catch (error) {
|
|
15020
|
+
debugError("Smart Compact policy update failed", error);
|
|
14910
15021
|
overrides = previous;
|
|
14911
15022
|
try {
|
|
14912
15023
|
restoreToolMembership(previousToolEnabled);
|
|
@@ -15001,6 +15112,7 @@ function smartCompactExtension(pi) {
|
|
|
15001
15112
|
return write;
|
|
15002
15113
|
};
|
|
15003
15114
|
const commitCandidates = createCompactionCommitStore({
|
|
15115
|
+
ttlMs: loadConfig().pendingTtlMs,
|
|
15004
15116
|
onDiscard: (pending, reason) => {
|
|
15005
15117
|
recordApplyFailure(pending, reason);
|
|
15006
15118
|
}
|
|
@@ -15022,8 +15134,8 @@ function smartCompactExtension(pi) {
|
|
|
15022
15134
|
else
|
|
15023
15135
|
signal.addEventListener("abort", discardOnAbort, { once: true });
|
|
15024
15136
|
return !signal.aborted;
|
|
15025
|
-
} catch (
|
|
15026
|
-
warn("Failed to stage smart compaction commit candidate",
|
|
15137
|
+
} catch (error) {
|
|
15138
|
+
warn("Failed to stage smart compaction commit candidate", error);
|
|
15027
15139
|
recordApplyFailure(pending, "apply-error");
|
|
15028
15140
|
return false;
|
|
15029
15141
|
}
|
|
@@ -15034,11 +15146,11 @@ function smartCompactExtension(pi) {
|
|
|
15034
15146
|
runLock: isRunning,
|
|
15035
15147
|
onNativeApplyError,
|
|
15036
15148
|
policy,
|
|
15037
|
-
onGlobalSettingApplied(
|
|
15149
|
+
onGlobalSettingApplied(path, ctx) {
|
|
15038
15150
|
try {
|
|
15039
|
-
applyGlobalSettingRuntime(
|
|
15040
|
-
} catch (
|
|
15041
|
-
debugError("Smart Compact runtime settings refresh failed",
|
|
15151
|
+
applyGlobalSettingRuntime(path, ctx, policy, contextToolAvailability);
|
|
15152
|
+
} catch (error) {
|
|
15153
|
+
debugError("Smart Compact runtime settings refresh failed", error);
|
|
15042
15154
|
}
|
|
15043
15155
|
}
|
|
15044
15156
|
});
|
|
@@ -15056,8 +15168,8 @@ function smartCompactExtension(pi) {
|
|
|
15056
15168
|
...loadConfig(),
|
|
15057
15169
|
autoTrigger: true
|
|
15058
15170
|
});
|
|
15059
|
-
} catch (
|
|
15060
|
-
debugError("Settled smart compact trigger stopped",
|
|
15171
|
+
} catch (error) {
|
|
15172
|
+
debugError("Settled smart compact trigger stopped", error);
|
|
15061
15173
|
}
|
|
15062
15174
|
});
|
|
15063
15175
|
pi.on("session_before_compact", async (event, ctx) => {
|
|
@@ -15158,9 +15270,9 @@ function smartCompactExtension(pi) {
|
|
|
15158
15270
|
ctx.ui.notify("Compaction applied, but durable persistence was incomplete: " + persistenceFailures.join(", "), "warning");
|
|
15159
15271
|
}
|
|
15160
15272
|
activateOnlineDamage(candidate);
|
|
15161
|
-
} catch (
|
|
15273
|
+
} catch (error) {
|
|
15162
15274
|
clearCompactProgress(ctx);
|
|
15163
|
-
warn("Failed to commit applied smart compaction",
|
|
15275
|
+
warn("Failed to commit applied smart compaction", error);
|
|
15164
15276
|
}
|
|
15165
15277
|
return;
|
|
15166
15278
|
}
|
|
@@ -15236,8 +15348,8 @@ function smartCompactExtension(pi) {
|
|
|
15236
15348
|
if (observation.report.damageScore > 0) {
|
|
15237
15349
|
ctx.ui.notify("Post-compaction damage detected: " + observation.report.summary, "warning");
|
|
15238
15350
|
}
|
|
15239
|
-
} catch (
|
|
15240
|
-
warn("online damage monitor message_end failed",
|
|
15351
|
+
} catch (error) {
|
|
15352
|
+
warn("online damage monitor message_end failed", error);
|
|
15241
15353
|
}
|
|
15242
15354
|
});
|
|
15243
15355
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
@@ -15255,7 +15367,7 @@ function smartCompactExtension(pi) {
|
|
|
15255
15367
|
});
|
|
15256
15368
|
}
|
|
15257
15369
|
export {
|
|
15258
|
-
|
|
15370
|
+
smartCompactExtension as default,
|
|
15259
15371
|
findModelById,
|
|
15260
|
-
|
|
15372
|
+
resolveModels
|
|
15261
15373
|
};
|