pi-smart-compact 7.16.0 → 7.18.1
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/CHANGELOG.md +51 -0
- package/dist/constants.d.ts +59 -4
- package/dist/constants.d.ts.map +1 -1
- package/dist/domain/keywords.d.ts +20 -0
- package/dist/domain/keywords.d.ts.map +1 -0
- package/dist/domain/tool-semantics.d.ts +45 -0
- package/dist/domain/tool-semantics.d.ts.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1264 -1189
- package/dist/infra/llm-client.d.ts +18 -0
- package/dist/infra/llm-client.d.ts.map +1 -1
- package/dist/infra/paths.d.ts.map +1 -1
- package/dist/infra/services.d.ts +1 -0
- package/dist/infra/services.d.ts.map +1 -1
- package/dist/phases/explore.d.ts.map +1 -1
- package/dist/phases/verify.d.ts.map +1 -1
- package/dist/ui/overlays.d.ts.map +1 -1
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/damage.d.ts.map +1 -1
- package/dist/utils/extraction.d.ts.map +1 -1
- package/dist/utils/fingerprint.d.ts.map +1 -1
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/pruning.d.ts.map +1 -1
- package/dist/utils/state.d.ts.map +1 -1
- package/dist/utils/tokens.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ var __export = (target, all) => {
|
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
|
|
18
18
|
// src/constants.ts
|
|
19
|
-
var VERSION = "7.
|
|
19
|
+
var VERSION = "7.18.1", CHARS_PER_TOKEN = 3.8, COMPACT_SYSTEM_PREFIX, PROFILES, DEFAULT_CONFIG, NO_OP_RE, SHIFT_RE, CHOICE_RE, SINGLE_PASS_PREFIX, SINGLE_PASS_SUFFIX = `
|
|
20
20
|
{PREV_CONTEXT}
|
|
21
21
|
|
|
22
22
|
{EXTRACTION_CONTEXT}
|
|
@@ -39,7 +39,7 @@ IMMUTABLE CONTEXT (verified deterministic data):
|
|
|
39
39
|
{EXPLORATION_CONTEXT}
|
|
40
40
|
{PREV_CONTEXT}
|
|
41
41
|
|
|
42
|
-
<summaries>{SUMMARIES}</summaries>`, SESSION_TYPE_INSTRUCTIONS, SECTION_GOAL = "## Goal", SECTION_CONSTRAINTS = "## Constraints & Preferences", SECTION_PROGRESS = "## Progress", SECTION_DECISIONS = "## Key Decisions", SECTION_FILES_MODIFIED = "## Files Modified", SECTION_FILES_READ = "## Files Read", SECTION_NEXT_STEPS = "## Next Steps", SECTION_CRITICAL_CONTEXT = "## Critical Context", SECTION_TOPICS = "## Topics Covered", SECTION_OPEN_LOOPS = "## Open Loops", SECTION_CHANGES = "## Changes Since Last Compaction", LOG_PREFIX = "[smart-compact]", MIN_TOKEN_THRESHOLD = 5000, MAX_EXPLORATION_ROUNDS = 8, BACKUP_MAX_FILES = 20, BACKUP_MAX_AGE_MS, MAX_TOOL_OUTPUT_CHARS = 800, METRICS_BUFFER_MAX = 200, CONFIG_KEY = "smartCompact", CONFIG_KEY_ALT = "semanticCompact", EXPLORER_SYSTEM_PROMPT;
|
|
42
|
+
<summaries>{SUMMARIES}</summaries>`, SESSION_TYPE_INSTRUCTIONS, SECTION_GOAL = "## Goal", SECTION_CONSTRAINTS = "## Constraints & Preferences", SECTION_PROGRESS = "## Progress", SECTION_DECISIONS = "## Key Decisions", SECTION_FILES_MODIFIED = "## Files Modified", SECTION_FILES_READ = "## Files Read", SECTION_NEXT_STEPS = "## Next Steps", SECTION_CRITICAL_CONTEXT = "## Critical Context", SECTION_TOPICS = "## Topics Covered", SECTION_OPEN_LOOPS = "## Open Loops", SECTION_CHANGES = "## Changes Since Last Compaction", LOG_PREFIX = "[smart-compact]", MIN_TOKEN_THRESHOLD = 5000, MAX_EXPLORATION_ROUNDS = 8, BACKUP_MAX_FILES = 20, BACKUP_MAX_AGE_MS, FIVE_MINUTES_MS, ONE_HOUR_MS, SEVEN_DAYS_MS, THIRTY_DAYS_MS, EXTRACTION_CACHE_PREFIX = "compact-extraction-", ID_PREFIX, TUNING, TRUNC, MAX_TOOL_OUTPUT_CHARS = 800, LIKELY_ERROR_RE, ERROR_SCAN_MAX_LEN = 2000, ERROR_RETRY_WINDOW = 6, ERROR_RESOLVE_WINDOW = 10, METRICS_BUFFER_MAX = 200, CONFIG_KEY = "smartCompact", CONFIG_KEY_ALT = "semanticCompact", EXPLORER_SYSTEM_PROMPT;
|
|
43
43
|
var init_constants = __esm(() => {
|
|
44
44
|
COMPACT_SYSTEM_PREFIX = "You are an expert conversation summarizer for a coding agent. " + "Produce structured markdown summaries. " + "Follow output format exactly. " + "Use EXACT names \u2014 never paraphrase code identifiers. " + "Trust deterministic extraction data over intuition.";
|
|
45
45
|
PROFILES = {
|
|
@@ -81,7 +81,7 @@ var init_constants = __esm(() => {
|
|
|
81
81
|
pinPaths: []
|
|
82
82
|
};
|
|
83
83
|
NO_OP_RE = /applied:\s*0|no changes applied|nothing to (?:do|change)|0 edits? applied/i;
|
|
84
|
-
SHIFT_RE =
|
|
84
|
+
SHIFT_RE = /\u015Fimdi|simdi|peki|bi de|bide|ge\u00E7elim|gecelim|bakal\u0131m|bakalim|yapal\u0131m|yapalim|ba\u015Fka|baska|sonra|tamam \u015Fimdi|tamam simdi|now let|also|next|let's|moving on|switch to/iu;
|
|
85
85
|
CHOICE_RE = /use\s+\S+\s+(?:instead|not|rather)|don't\s+use|avoid\s+|switch\s+to\s+|go\s+with\s+|prefer\s+/i;
|
|
86
86
|
SINGLE_PASS_PREFIX = `Summarize this coding agent conversation. Produce ONE structured summary.
|
|
87
87
|
` + `
|
|
@@ -178,6 +178,56 @@ Rules for Accuracy:
|
|
|
178
178
|
discussion: "Focus on: decisions made, trade-offs discussed, consensus reached. Prioritize rationale over implementation details."
|
|
179
179
|
};
|
|
180
180
|
BACKUP_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000;
|
|
181
|
+
FIVE_MINUTES_MS = 5 * 60 * 1000;
|
|
182
|
+
ONE_HOUR_MS = 60 * 60 * 1000;
|
|
183
|
+
SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
184
|
+
THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
|
|
185
|
+
ID_PREFIX = {
|
|
186
|
+
PROJECT: "proj-",
|
|
187
|
+
COMPACT_SESSION: "sc-",
|
|
188
|
+
MULTI_TOOL_USE_SYNTHETIC: "mtu_",
|
|
189
|
+
OPEN_LOOP: "loop-",
|
|
190
|
+
DECISION: "decision-",
|
|
191
|
+
ERROR: "error-"
|
|
192
|
+
};
|
|
193
|
+
TUNING = {
|
|
194
|
+
EMA_PREV: 0.7,
|
|
195
|
+
EMA_SAMPLE: 0.3,
|
|
196
|
+
CALIBRATION_CLAMP_MIN: 0.3,
|
|
197
|
+
CALIBRATION_CLAMP_MAX: 3,
|
|
198
|
+
CONFIDENCE_HIGH: 0.85,
|
|
199
|
+
CONFIDENCE_MEDIUM: 0.8,
|
|
200
|
+
CONFIDENCE_LOW: 0.6
|
|
201
|
+
};
|
|
202
|
+
TRUNC = {
|
|
203
|
+
MESSAGE: 300,
|
|
204
|
+
ERROR_DETAIL: 500,
|
|
205
|
+
DECISION_SUMMARY: 200,
|
|
206
|
+
USER_RESPONSE: 300,
|
|
207
|
+
CONSTRAINT_TEXT: 300,
|
|
208
|
+
OPEN_LOOP_SUMMARY: 120,
|
|
209
|
+
TIMELINE_EVENT: 150,
|
|
210
|
+
TIMELINE_ERROR: 100,
|
|
211
|
+
SNIPPET: 80,
|
|
212
|
+
PREVIEW: 150,
|
|
213
|
+
PREVIEW_MID: 200,
|
|
214
|
+
DETAIL: 300,
|
|
215
|
+
PREVIEW_LONG: 400,
|
|
216
|
+
PREVIEW_XL: 500,
|
|
217
|
+
CHUNK_FALLBACK: 180,
|
|
218
|
+
DECISION_DETAIL: 60,
|
|
219
|
+
TOPIC_LABEL: 100,
|
|
220
|
+
PROJ_ID_HASH: 12,
|
|
221
|
+
CONV_HASH: 8,
|
|
222
|
+
RESULT_GAPS: 5,
|
|
223
|
+
SESSION_ID_DISPLAY: 20,
|
|
224
|
+
ERROR_SNIPPET: 30,
|
|
225
|
+
TIMELINE_DISPLAY: 10,
|
|
226
|
+
EXPLORE_RESULTS: 15,
|
|
227
|
+
BACKUP_PREVIEW_LINES: 5,
|
|
228
|
+
FINGERPRINT_SEG: 2
|
|
229
|
+
};
|
|
230
|
+
LIKELY_ERROR_RE = /(?:command not found|no such file|permission denied|syntax error|cannot find|module not found|compilation error|build failed|test failed|^FAIL\b|ERROR:)/i;
|
|
181
231
|
EXPLORER_SYSTEM_PROMPT = `You are a conversation analyst. You have deterministic extraction data and can query the raw conversation using tools.
|
|
182
232
|
|
|
183
233
|
` + `Your job:
|
|
@@ -247,7 +297,7 @@ function damageReportsFile() {
|
|
|
247
297
|
return path.join(smartCompactCacheDir(), "damage-reports.jsonl");
|
|
248
298
|
}
|
|
249
299
|
function extractionCacheFile(sessionId) {
|
|
250
|
-
return path.join(cacheDir(),
|
|
300
|
+
return path.join(cacheDir(), EXTRACTION_CACHE_PREFIX + sessionId.replace(/[^a-zA-Z0-9-]/g, "_") + ".json");
|
|
251
301
|
}
|
|
252
302
|
function projectFingerprintFile(projectId) {
|
|
253
303
|
return path.join(projectFingerprintDir(), projectId + ".json");
|
|
@@ -261,7 +311,9 @@ function remediationHintsFile(projectId) {
|
|
|
261
311
|
function metricsDashboardFile() {
|
|
262
312
|
return path.join(cacheDir(), "smart-compact-report.html");
|
|
263
313
|
}
|
|
264
|
-
var init_paths = () => {
|
|
314
|
+
var init_paths = __esm(() => {
|
|
315
|
+
init_constants();
|
|
316
|
+
});
|
|
265
317
|
|
|
266
318
|
// src/infra/fs.ts
|
|
267
319
|
import fs from "fs";
|
|
@@ -389,8 +441,8 @@ class TokenCalibrationStore {
|
|
|
389
441
|
const key = calibrationKey(provider, model);
|
|
390
442
|
const prev = this.factors.get(key) ?? 1;
|
|
391
443
|
const sample = actual / estimated;
|
|
392
|
-
const clamped = Math.max(
|
|
393
|
-
this.factors.set(key, prev *
|
|
444
|
+
const clamped = Math.max(TUNING.CALIBRATION_CLAMP_MIN, Math.min(TUNING.CALIBRATION_CLAMP_MAX, sample));
|
|
445
|
+
this.factors.set(key, prev * TUNING.EMA_PREV + clamped * TUNING.EMA_SAMPLE);
|
|
394
446
|
}
|
|
395
447
|
}
|
|
396
448
|
function calibrationKey(provider, model) {
|
|
@@ -398,7 +450,21 @@ function calibrationKey(provider, model) {
|
|
|
398
450
|
}
|
|
399
451
|
function estimateTokens(text, provider, model, calibration = _fallbackCalibration) {
|
|
400
452
|
const baseRatio = provider ? getProviderCaps(provider).tokenRatioEstimate : CHARS_PER_TOKEN;
|
|
401
|
-
const
|
|
453
|
+
const startsJson = text.startsWith("[") || text.startsWith("{");
|
|
454
|
+
let jsonPenalty = 1;
|
|
455
|
+
if (startsJson) {
|
|
456
|
+
jsonPenalty = JSON_PENALTY;
|
|
457
|
+
} else if (text.length > 0) {
|
|
458
|
+
const sample = text.length > JSON_DENSITY_SCAN_CAP ? text.slice(0, JSON_DENSITY_SCAN_CAP) : text;
|
|
459
|
+
let structural = 0;
|
|
460
|
+
for (let i = 0;i < sample.length; i++) {
|
|
461
|
+
const c = sample.charCodeAt(i);
|
|
462
|
+
if (c === 34 || c === 91 || c === 93 || c === 123 || c === 125)
|
|
463
|
+
structural++;
|
|
464
|
+
}
|
|
465
|
+
if (structural / sample.length > JSON_DENSITY_THRESHOLD)
|
|
466
|
+
jsonPenalty = JSON_PENALTY;
|
|
467
|
+
}
|
|
402
468
|
const langPenalty = /[\u00E7\u011F\u0131\u00F6\u015F\u00FC\u00C7\u011E\u0130\u00D6\u015E\u00DC]/.test(text) ? 0.9 : 1;
|
|
403
469
|
const factor = calibration.get(provider, model);
|
|
404
470
|
return Math.ceil(text.length / baseRatio * jsonPenalty * langPenalty * factor);
|
|
@@ -406,7 +472,7 @@ function estimateTokens(text, provider, model, calibration = _fallbackCalibratio
|
|
|
406
472
|
function calibrateFromResponse(estimated, actual, provider, model, calibration = _fallbackCalibration) {
|
|
407
473
|
calibration.calibrate(estimated, actual, provider, model);
|
|
408
474
|
}
|
|
409
|
-
var PROVIDER_MAP, PROVIDER_ALIASES, DEFAULT_CAPS, _fallbackCalibration;
|
|
475
|
+
var PROVIDER_MAP, PROVIDER_ALIASES, DEFAULT_CAPS, _fallbackCalibration, JSON_PENALTY = 0.85, JSON_DENSITY_THRESHOLD = 0.05, JSON_DENSITY_SCAN_CAP = 8192;
|
|
410
476
|
var init_tokens = __esm(() => {
|
|
411
477
|
init_constants();
|
|
412
478
|
PROVIDER_MAP = {
|
|
@@ -671,7 +737,7 @@ var init_llm_retry = __esm(() => {
|
|
|
671
737
|
});
|
|
672
738
|
|
|
673
739
|
// src/infra/llm-client.ts
|
|
674
|
-
import { complete } from "@earendil-works/pi-ai";
|
|
740
|
+
import { complete } from "@earendil-works/pi-ai/compat";
|
|
675
741
|
function getLlmClient() {
|
|
676
742
|
return _client;
|
|
677
743
|
}
|
|
@@ -744,7 +810,7 @@ import crypto4 from "crypto";
|
|
|
744
810
|
class ToolSupportCache {
|
|
745
811
|
entries = new Map;
|
|
746
812
|
ttlMs;
|
|
747
|
-
constructor(ttlMs =
|
|
813
|
+
constructor(ttlMs = ONE_HOUR_MS) {
|
|
748
814
|
this.ttlMs = ttlMs;
|
|
749
815
|
}
|
|
750
816
|
get(key, now) {
|
|
@@ -850,11 +916,7 @@ var init_services = __esm(() => {
|
|
|
850
916
|
|
|
851
917
|
// src/utils/cache.ts
|
|
852
918
|
import fs3 from "fs";
|
|
853
|
-
import
|
|
854
|
-
import crypto5 from "crypto";
|
|
855
|
-
function fallbackSessionId() {
|
|
856
|
-
return "sc-" + Date.now().toString(36) + "-" + crypto5.randomBytes(4).toString("hex");
|
|
857
|
-
}
|
|
919
|
+
import path5 from "path";
|
|
858
920
|
function cacheOpts(opts, provider, phase, services) {
|
|
859
921
|
if (phase && INTERNAL_PHASES.has(phase)) {
|
|
860
922
|
return { ...opts, cacheRetention: "none" };
|
|
@@ -864,7 +926,7 @@ function cacheOpts(opts, provider, phase, services) {
|
|
|
864
926
|
if (retention === "none") {
|
|
865
927
|
return { ...opts, cacheRetention: "none" };
|
|
866
928
|
}
|
|
867
|
-
return { ...opts, sessionId: services?.compactSessionId ??
|
|
929
|
+
return { ...opts, sessionId: services?.compactSessionId ?? makeCompactSessionId(), cacheRetention: retention };
|
|
868
930
|
}
|
|
869
931
|
function recordMetric(m, services = getDefaultServices()) {
|
|
870
932
|
services.metrics.record(m);
|
|
@@ -970,14 +1032,14 @@ function scheduleExtractionCacheCleanup() {
|
|
|
970
1032
|
_extractionPruneInFlight = true;
|
|
971
1033
|
queueMicrotask(() => {
|
|
972
1034
|
try {
|
|
973
|
-
const dir =
|
|
1035
|
+
const dir = path5.dirname(getCachePath("_"));
|
|
974
1036
|
if (!fs3.existsSync(dir))
|
|
975
1037
|
return;
|
|
976
1038
|
const now = Date.now();
|
|
977
1039
|
for (const name of fs3.readdirSync(dir)) {
|
|
978
|
-
if (!name.startsWith(
|
|
1040
|
+
if (!name.startsWith(EXTRACTION_CACHE_PREFIX) || !name.endsWith(".json"))
|
|
979
1041
|
continue;
|
|
980
|
-
const fp =
|
|
1042
|
+
const fp = path5.join(dir, name);
|
|
981
1043
|
try {
|
|
982
1044
|
const stat = fs3.statSync(fp);
|
|
983
1045
|
if (now - stat.mtimeMs > EXTRACTION_CACHE_PRUNE_MAX_AGE_MS) {
|
|
@@ -1023,9 +1085,9 @@ function mergeExtractions(base, delta, baseMsgCount) {
|
|
|
1023
1085
|
constraints: [...base.constraints, ...offsetConstraints],
|
|
1024
1086
|
topics: [...base.topics, ...offsetTopics],
|
|
1025
1087
|
timeline: [...base.timeline, ...offsetTimeline],
|
|
1026
|
-
mainGoal:
|
|
1027
|
-
lastUserMessages:
|
|
1028
|
-
lastErrors:
|
|
1088
|
+
mainGoal: base.mainGoal ?? delta.mainGoal,
|
|
1089
|
+
lastUserMessages: [...base.lastUserMessages, ...delta.lastUserMessages].slice(-5),
|
|
1090
|
+
lastErrors: [...base.lastErrors, ...delta.lastErrors].slice(-3),
|
|
1029
1091
|
messageCount: baseMsgCount + delta.messageCount
|
|
1030
1092
|
};
|
|
1031
1093
|
}
|
|
@@ -1321,6 +1383,7 @@ var init_cache = __esm(() => {
|
|
|
1321
1383
|
init_llm_client();
|
|
1322
1384
|
init_paths();
|
|
1323
1385
|
init_fs();
|
|
1386
|
+
init_constants();
|
|
1324
1387
|
init_id_fingerprint();
|
|
1325
1388
|
init_services();
|
|
1326
1389
|
INTERNAL_PHASES = new Set([
|
|
@@ -1333,8 +1396,8 @@ var init_cache = __esm(() => {
|
|
|
1333
1396
|
"assemble",
|
|
1334
1397
|
"patch"
|
|
1335
1398
|
]);
|
|
1336
|
-
EXTRACTION_CACHE_TTL_MS =
|
|
1337
|
-
EXTRACTION_CACHE_PRUNE_MAX_AGE_MS =
|
|
1399
|
+
EXTRACTION_CACHE_TTL_MS = ONE_HOUR_MS;
|
|
1400
|
+
EXTRACTION_CACHE_PRUNE_MAX_AGE_MS = SEVEN_DAYS_MS;
|
|
1338
1401
|
});
|
|
1339
1402
|
|
|
1340
1403
|
// src/ui/dashboard-format.ts
|
|
@@ -1451,7 +1514,7 @@ __export(exports_overlays, {
|
|
|
1451
1514
|
});
|
|
1452
1515
|
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
|
1453
1516
|
import { Container, Key, matchesKey, SelectList, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
1454
|
-
import
|
|
1517
|
+
import path6 from "path";
|
|
1455
1518
|
function renderContextBar(theme, pct, tokens, barLen = 24) {
|
|
1456
1519
|
const clamped = Math.min(Math.max(pct, 0), 100);
|
|
1457
1520
|
const filled = Math.min(barLen, Math.round(clamped / 100 * barLen));
|
|
@@ -1634,7 +1697,7 @@ async function showResultScreen(ctx, details, extraction, services) {
|
|
|
1634
1697
|
const f = modFiles[i];
|
|
1635
1698
|
const fc = extraction.modifiedFiles.find((e) => e.path === f);
|
|
1636
1699
|
const count = fc ? " (" + fc.toolCalls + "x)" : "";
|
|
1637
|
-
c.addChild(new Text(theme.fg("success", " \u270E ") + theme.fg("text",
|
|
1700
|
+
c.addChild(new Text(theme.fg("success", " \u270E ") + theme.fg("text", path6.basename(f)) + theme.fg("dim", count + " \u2192 " + f), 0, 0));
|
|
1638
1701
|
}
|
|
1639
1702
|
if (modFiles.length > maxShow) {
|
|
1640
1703
|
c.addChild(new Text(theme.fg("dim", " + " + (modFiles.length - maxShow) + " more"), 0, 0));
|
|
@@ -1657,7 +1720,7 @@ async function showResultScreen(ctx, details, extraction, services) {
|
|
|
1657
1720
|
c.addChild(new Text(theme.fg("success", " \u2705 All facts verified \u2014 no gaps detected"), 0, 0));
|
|
1658
1721
|
} else if (details.gaps.length > 0) {
|
|
1659
1722
|
c.addChild(new Text(theme.fg("warning", " \u26A0\uFE0F " + details.gaps.length + " gap(s) patched:"), 0, 0));
|
|
1660
|
-
for (const g of details.gaps.slice(0,
|
|
1723
|
+
for (const g of details.gaps.slice(0, TRUNC.RESULT_GAPS)) {
|
|
1661
1724
|
c.addChild(new Text(theme.fg("dim", " \u2022 " + g), 0, 0));
|
|
1662
1725
|
}
|
|
1663
1726
|
}
|
|
@@ -1808,7 +1871,7 @@ async function showRestorePicker(ctx, backups) {
|
|
|
1808
1871
|
const items = backups.map((b) => ({
|
|
1809
1872
|
value: b.path,
|
|
1810
1873
|
label: new Date(b.date).toLocaleString() + " \xB7 " + Math.max(1, Math.round(b.sizeBytes / 1024)) + "KB",
|
|
1811
|
-
description: b.sessionId.slice(0,
|
|
1874
|
+
description: b.sessionId.slice(0, TRUNC.SESSION_ID_DISPLAY)
|
|
1812
1875
|
}));
|
|
1813
1876
|
return await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
1814
1877
|
const c = new Container;
|
|
@@ -1920,6 +1983,7 @@ async function showRestoreAction(ctx, backupPath) {
|
|
|
1920
1983
|
});
|
|
1921
1984
|
}
|
|
1922
1985
|
var init_overlays = __esm(() => {
|
|
1986
|
+
init_constants();
|
|
1923
1987
|
init_cache();
|
|
1924
1988
|
init_tokens();
|
|
1925
1989
|
});
|
|
@@ -1932,1151 +1996,1161 @@ init_constants();
|
|
|
1932
1996
|
init_logger();
|
|
1933
1997
|
init_paths();
|
|
1934
1998
|
init_fs();
|
|
1935
|
-
init_constants();
|
|
1936
1999
|
import fs2 from "fs";
|
|
1937
|
-
import
|
|
2000
|
+
import path4 from "path";
|
|
1938
2001
|
import crypto2 from "crypto";
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
delete sc.autoTrigger;
|
|
1949
|
-
}
|
|
1950
|
-
if ("backupEnabled" in sc && typeof sc.backupEnabled !== "boolean") {
|
|
1951
|
-
warn("smart-compact config: backupEnabled must be boolean, got " + typeof sc.backupEnabled);
|
|
1952
|
-
delete sc.backupEnabled;
|
|
1953
|
-
}
|
|
1954
|
-
if ("summaryModel" in sc && sc.summaryModel !== null && typeof sc.summaryModel !== "string") {
|
|
1955
|
-
warn("smart-compact config: summaryModel must be string|null, got " + typeof sc.summaryModel);
|
|
1956
|
-
delete sc.summaryModel;
|
|
1957
|
-
}
|
|
1958
|
-
if ("segmentationModel" in sc && sc.segmentationModel !== null && typeof sc.segmentationModel !== "string") {
|
|
1959
|
-
warn("smart-compact config: segmentationModel must be string|null, got " + typeof sc.segmentationModel);
|
|
1960
|
-
delete sc.segmentationModel;
|
|
1961
|
-
}
|
|
1962
|
-
if ("profiles" in sc) {
|
|
1963
|
-
if (typeof sc.profiles !== "object" || sc.profiles === null || Array.isArray(sc.profiles)) {
|
|
1964
|
-
warn("smart-compact config: profiles must be an object, got " + typeof sc.profiles);
|
|
1965
|
-
delete sc.profiles;
|
|
1966
|
-
} else {
|
|
1967
|
-
const profiles = sc.profiles;
|
|
1968
|
-
for (const [profileName, value] of Object.entries(profiles)) {
|
|
1969
|
-
if (!VALID_PROFILES.includes(profileName)) {
|
|
1970
|
-
warn("smart-compact config: ignoring unknown profile override '" + profileName + "'.");
|
|
1971
|
-
delete profiles[profileName];
|
|
1972
|
-
continue;
|
|
1973
|
-
}
|
|
1974
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1975
|
-
warn("smart-compact config: profile '" + profileName + "' must be an object.");
|
|
1976
|
-
delete profiles[profileName];
|
|
1977
|
-
continue;
|
|
1978
|
-
}
|
|
1979
|
-
const profileCfg = value;
|
|
1980
|
-
for (const [key, raw] of Object.entries(profileCfg)) {
|
|
1981
|
-
if (!PROFILE_NUMERIC_KEYS.includes(key)) {
|
|
1982
|
-
warn("smart-compact config: ignoring unknown profile key '" + profileName + "." + key + "'.");
|
|
1983
|
-
delete profileCfg[key];
|
|
1984
|
-
continue;
|
|
1985
|
-
}
|
|
1986
|
-
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0 || raw > 1e6) {
|
|
1987
|
-
warn("smart-compact config: profile '" + profileName + "." + key + "' must be a positive finite number.");
|
|
1988
|
-
delete profileCfg[key];
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
}
|
|
1994
|
-
if ("autoTriggerTimeoutMs" in sc) {
|
|
1995
|
-
const v = sc.autoTriggerTimeoutMs;
|
|
1996
|
-
if (typeof v !== "number" || !Number.isFinite(v) || v < 1000 || v > 300000) {
|
|
1997
|
-
warn("smart-compact config: autoTriggerTimeoutMs must be 1000\u2013300000, got " + v + ". Using default " + DEFAULT_CONFIG.autoTriggerTimeoutMs + "ms.");
|
|
1998
|
-
delete sc.autoTriggerTimeoutMs;
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
if ("minContextPercent" in sc) {
|
|
2002
|
-
const v = sc.minContextPercent;
|
|
2003
|
-
if (typeof v !== "number" || !Number.isFinite(v) || v < 0 || v > 100) {
|
|
2004
|
-
warn("smart-compact config: minContextPercent must be 0\u2013100, got " + v + ". Using default " + DEFAULT_CONFIG.minContextPercent + ".");
|
|
2005
|
-
delete sc.minContextPercent;
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
if ("backupDir" in sc && sc.backupDir !== undefined && typeof sc.backupDir !== "string") {
|
|
2009
|
-
warn("smart-compact config: backupDir must be a string, got " + typeof sc.backupDir + ". Using default.");
|
|
2010
|
-
delete sc.backupDir;
|
|
2011
|
-
}
|
|
2012
|
-
if ("pinPaths" in sc && sc.pinPaths !== undefined) {
|
|
2013
|
-
if (!Array.isArray(sc.pinPaths) || !sc.pinPaths.every((x) => typeof x === "string")) {
|
|
2014
|
-
warn("smart-compact config: pinPaths must be a string[], ignoring.");
|
|
2015
|
-
delete sc.pinPaths;
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
var _cfg = null;
|
|
2020
|
-
var _cfgMtime = 0;
|
|
2021
|
-
var _cfgPath = null;
|
|
2022
|
-
function loadConfig() {
|
|
2023
|
-
try {
|
|
2024
|
-
const p = settingsFile();
|
|
2025
|
-
const stat = fs2.statSync(p);
|
|
2026
|
-
if (_cfg && _cfgPath === p && stat.mtimeMs === _cfgMtime)
|
|
2027
|
-
return _cfg;
|
|
2028
|
-
const raw = JSON.parse(fs2.readFileSync(p, "utf-8"));
|
|
2029
|
-
const sc = raw[CONFIG_KEY] ?? raw[CONFIG_KEY_ALT] ?? {};
|
|
2030
|
-
validateSmartCompactConfig(sc);
|
|
2031
|
-
const merged = { ...DEFAULT_CONFIG, ...sc };
|
|
2032
|
-
if (sc.profiles)
|
|
2033
|
-
merged.profiles = { ...PROFILES, ...sc.profiles };
|
|
2034
|
-
if (!merged.backupDir)
|
|
2035
|
-
merged.backupDir = defaultBackupDir();
|
|
2036
|
-
_cfg = merged;
|
|
2037
|
-
_cfgMtime = stat.mtimeMs;
|
|
2038
|
-
_cfgPath = p;
|
|
2039
|
-
return _cfg;
|
|
2040
|
-
} catch (e) {
|
|
2041
|
-
debug("loadConfig: settings.json not found or unreadable, using defaults", e);
|
|
2042
|
-
const fallback = { ...DEFAULT_CONFIG, backupDir: defaultBackupDir() };
|
|
2043
|
-
_cfg = fallback;
|
|
2044
|
-
_cfgPath = null;
|
|
2045
|
-
return fallback;
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
var _pruneInFlight = new Set;
|
|
2049
|
-
function prunePass(dir) {
|
|
2050
|
-
try {
|
|
2051
|
-
const entries = fs2.readdirSync(dir).filter((name) => name.endsWith(".md")).map((name) => {
|
|
2052
|
-
const full = path3.join(dir, name);
|
|
2053
|
-
try {
|
|
2054
|
-
return { full, mtimeMs: fs2.statSync(full).mtimeMs };
|
|
2055
|
-
} catch {
|
|
2056
|
-
return null;
|
|
2057
|
-
}
|
|
2058
|
-
}).filter((v) => v !== null);
|
|
2059
|
-
const now = Date.now();
|
|
2060
|
-
const overAge = entries.filter((e) => now - e.mtimeMs > BACKUP_MAX_AGE_MS);
|
|
2061
|
-
const sorted = entries.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2062
|
-
const overCount = sorted.slice(BACKUP_MAX_FILES);
|
|
2063
|
-
const toRemove = new Set([...overAge, ...overCount].map((e) => e.full));
|
|
2064
|
-
for (const full of toRemove) {
|
|
2065
|
-
try {
|
|
2066
|
-
fs2.unlinkSync(full);
|
|
2067
|
-
} catch (e) {
|
|
2068
|
-
debug("prunePass unlink failed", e);
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
|
-
} catch (e) {
|
|
2072
|
-
debug("prunePass scan failed", e);
|
|
2073
|
-
}
|
|
2002
|
+
|
|
2003
|
+
// src/utils/extraction.ts
|
|
2004
|
+
init_constants();
|
|
2005
|
+
init_tokens();
|
|
2006
|
+
import path3 from "path";
|
|
2007
|
+
|
|
2008
|
+
// src/utils/type-guards.ts
|
|
2009
|
+
function isTextBlock(c) {
|
|
2010
|
+
return typeof c === "object" && c !== null && c.type === "text" && typeof c.text === "string";
|
|
2074
2011
|
}
|
|
2075
|
-
function
|
|
2076
|
-
|
|
2077
|
-
return;
|
|
2078
|
-
_pruneInFlight.add(dir);
|
|
2079
|
-
queueMicrotask(() => {
|
|
2080
|
-
try {
|
|
2081
|
-
prunePass(dir);
|
|
2082
|
-
} finally {
|
|
2083
|
-
_pruneInFlight.delete(dir);
|
|
2084
|
-
}
|
|
2085
|
-
});
|
|
2012
|
+
function isToolCallBlock(c) {
|
|
2013
|
+
return typeof c === "object" && c !== null && c.type === "toolCall" && typeof c.name === "string";
|
|
2086
2014
|
}
|
|
2087
|
-
function
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
return null;
|
|
2092
|
-
const dir = cfg.backupDir;
|
|
2093
|
-
ensureDir(dir);
|
|
2094
|
-
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
2095
|
-
const hash = crypto2.createHash("sha256").update(convText).digest("hex").slice(0, 8);
|
|
2096
|
-
const fp = path3.join(dir, sessionId + "-" + ts + "-" + hash + ".md");
|
|
2097
|
-
atomicWriteFileSync(fp, `# Smart Compact Backup
|
|
2098
|
-
# Date: ` + new Date().toISOString() + `
|
|
2099
|
-
# Session: ` + sessionId + `
|
|
2100
|
-
|
|
2101
|
-
` + convText);
|
|
2102
|
-
schedulePruneBackups(dir);
|
|
2103
|
-
return fp;
|
|
2104
|
-
} catch (e) {
|
|
2105
|
-
warn("backupConversation failed", e);
|
|
2106
|
-
return null;
|
|
2107
|
-
}
|
|
2015
|
+
function getToolCallNames(content) {
|
|
2016
|
+
if (!Array.isArray(content))
|
|
2017
|
+
return [];
|
|
2018
|
+
return content.filter(isToolCallBlock).map((b) => b.name);
|
|
2108
2019
|
}
|
|
2109
|
-
function
|
|
2110
|
-
|
|
2111
|
-
const dir = loadConfig().backupDir;
|
|
2112
|
-
if (!fs2.existsSync(dir))
|
|
2113
|
-
return [];
|
|
2114
|
-
const out = [];
|
|
2115
|
-
for (const name of fs2.readdirSync(dir)) {
|
|
2116
|
-
if (!name.endsWith(".md"))
|
|
2117
|
-
continue;
|
|
2118
|
-
const full = path3.join(dir, name);
|
|
2119
|
-
try {
|
|
2120
|
-
const stat = fs2.statSync(full);
|
|
2121
|
-
if (!stat.isFile())
|
|
2122
|
-
continue;
|
|
2123
|
-
const head = fs2.readFileSync(full, "utf-8").split(`
|
|
2124
|
-
`).slice(0, 5).join(`
|
|
2125
|
-
`);
|
|
2126
|
-
const date = head.match(/^# Date:\s*(.+)$/m)?.[1]?.trim();
|
|
2127
|
-
const session = head.match(/^# Session:\s*(.+)$/m)?.[1]?.trim();
|
|
2128
|
-
out.push({
|
|
2129
|
-
path: full,
|
|
2130
|
-
sessionId: session ?? name,
|
|
2131
|
-
date: date ?? stat.mtime.toISOString(),
|
|
2132
|
-
sizeBytes: stat.size
|
|
2133
|
-
});
|
|
2134
|
-
} catch {}
|
|
2135
|
-
}
|
|
2136
|
-
out.sort((a, b) => a.date < b.date ? 1 : a.date > b.date ? -1 : 0);
|
|
2137
|
-
return out.slice(0, limit);
|
|
2138
|
-
} catch (e) {
|
|
2139
|
-
warn("listBackups failed", e);
|
|
2020
|
+
function filterToolCalls(content) {
|
|
2021
|
+
if (!Array.isArray(content))
|
|
2140
2022
|
return [];
|
|
2141
|
-
|
|
2023
|
+
return content.filter(isToolCallBlock);
|
|
2142
2024
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
`);
|
|
2148
|
-
let i = 0;
|
|
2149
|
-
while (i < lines.length && lines[i].startsWith("#"))
|
|
2150
|
-
i++;
|
|
2151
|
-
if (i < lines.length && lines[i].trim() === "")
|
|
2152
|
-
i++;
|
|
2153
|
-
return lines.slice(i).join(`
|
|
2154
|
-
`).trim() || null;
|
|
2155
|
-
} catch (e) {
|
|
2156
|
-
warn("readBackupContent failed", e);
|
|
2157
|
-
return null;
|
|
2158
|
-
}
|
|
2025
|
+
var KNOWN_METHODS = new Set(["eesv", "single-pass", "heuristic"]);
|
|
2026
|
+
var KNOWN_PROFILES = new Set(["light", "balanced", "aggressive"]);
|
|
2027
|
+
function isStringArray(v) {
|
|
2028
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
2159
2029
|
}
|
|
2160
|
-
function
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2030
|
+
function isValidSmartCompactDetails(d) {
|
|
2031
|
+
if (!d || typeof d !== "object")
|
|
2032
|
+
return false;
|
|
2033
|
+
const r = d;
|
|
2034
|
+
if (!isStringArray(r.modifiedFiles))
|
|
2035
|
+
return false;
|
|
2036
|
+
if (!isStringArray(r.readFiles))
|
|
2037
|
+
return false;
|
|
2038
|
+
if (!isStringArray(r.topics))
|
|
2039
|
+
return false;
|
|
2040
|
+
if (typeof r.method !== "string" || !KNOWN_METHODS.has(r.method))
|
|
2041
|
+
return false;
|
|
2042
|
+
if (typeof r.profile !== "string" || !KNOWN_PROFILES.has(r.profile))
|
|
2043
|
+
return false;
|
|
2044
|
+
if (typeof r.qualityScore !== "number" || !Number.isFinite(r.qualityScore))
|
|
2045
|
+
return false;
|
|
2046
|
+
if (typeof r.totalMessages !== "number" || !Number.isFinite(r.totalMessages))
|
|
2047
|
+
return false;
|
|
2048
|
+
if (r.gaps !== undefined && !isStringArray(r.gaps))
|
|
2049
|
+
return false;
|
|
2050
|
+
if (r.verified !== undefined && typeof r.verified !== "boolean")
|
|
2051
|
+
return false;
|
|
2052
|
+
if (r.backupPath !== undefined && r.backupPath !== null && typeof r.backupPath !== "string")
|
|
2053
|
+
return false;
|
|
2054
|
+
return true;
|
|
2170
2055
|
}
|
|
2171
|
-
function
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
const
|
|
2177
|
-
if (!
|
|
2178
|
-
return
|
|
2179
|
-
|
|
2180
|
-
|
|
2056
|
+
function sanitizeSmartCompactDetails(d) {
|
|
2057
|
+
if (isValidSmartCompactDetails(d))
|
|
2058
|
+
return d;
|
|
2059
|
+
if (!d || typeof d !== "object")
|
|
2060
|
+
return null;
|
|
2061
|
+
const r = d;
|
|
2062
|
+
if (!isStringArray(r.modifiedFiles) || !isStringArray(r.readFiles) || !isStringArray(r.topics))
|
|
2063
|
+
return null;
|
|
2064
|
+
const repaired = {
|
|
2065
|
+
method: KNOWN_METHODS.has(r.method) ? r.method : "heuristic",
|
|
2066
|
+
chunkCount: typeof r.chunkCount === "number" ? r.chunkCount : 0,
|
|
2067
|
+
topics: r.topics,
|
|
2068
|
+
readFiles: r.readFiles,
|
|
2069
|
+
modifiedFiles: r.modifiedFiles,
|
|
2070
|
+
totalMessages: typeof r.totalMessages === "number" ? r.totalMessages : 0,
|
|
2071
|
+
totalTokensSummarized: typeof r.totalTokensSummarized === "number" ? r.totalTokensSummarized : 0,
|
|
2072
|
+
llmCalls: typeof r.llmCalls === "number" ? r.llmCalls : 0,
|
|
2073
|
+
profile: KNOWN_PROFILES.has(r.profile) ? r.profile : "balanced",
|
|
2074
|
+
backupPath: typeof r.backupPath === "string" ? r.backupPath : null,
|
|
2075
|
+
tokensSaved: typeof r.tokensSaved === "number" ? r.tokensSaved : 0,
|
|
2076
|
+
verified: typeof r.verified === "boolean" ? r.verified : false,
|
|
2077
|
+
gaps: isStringArray(r.gaps) ? r.gaps : [],
|
|
2078
|
+
explorationRounds: typeof r.explorationRounds === "number" ? r.explorationRounds : 0,
|
|
2079
|
+
explorationBoundaries: typeof r.explorationBoundaries === "number" ? r.explorationBoundaries : 0,
|
|
2080
|
+
model: typeof r.model === "string" ? r.model : "unknown",
|
|
2081
|
+
qualityScore: typeof r.qualityScore === "number" ? r.qualityScore : 0,
|
|
2082
|
+
tokensBefore: typeof r.tokensBefore === "number" ? r.tokensBefore : 0
|
|
2083
|
+
};
|
|
2084
|
+
return isValidSmartCompactDetails(repaired) ? repaired : null;
|
|
2181
2085
|
}
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2086
|
+
|
|
2087
|
+
// src/utils/file-needles.ts
|
|
2088
|
+
var GENERIC_BASENAMES = new Set([
|
|
2089
|
+
"index.ts",
|
|
2090
|
+
"index.js",
|
|
2091
|
+
"index.tsx",
|
|
2092
|
+
"index.jsx",
|
|
2093
|
+
"types.ts",
|
|
2094
|
+
"helpers.ts",
|
|
2095
|
+
"utils.ts",
|
|
2096
|
+
"main.ts",
|
|
2097
|
+
"main.js",
|
|
2098
|
+
"mod.rs",
|
|
2099
|
+
"lib.rs",
|
|
2100
|
+
"__init__.py"
|
|
2101
|
+
]);
|
|
2102
|
+
var MIN_BARE_BASENAME_LEN = 5;
|
|
2103
|
+
function buildPathNeedles(filePath) {
|
|
2104
|
+
const parts = filePath.toLowerCase().split("/").filter(Boolean);
|
|
2105
|
+
if (parts.length === 0)
|
|
2106
|
+
return [];
|
|
2107
|
+
const needles = [];
|
|
2108
|
+
const basename = parts[parts.length - 1];
|
|
2109
|
+
if (!GENERIC_BASENAMES.has(basename) && basename.length >= MIN_BARE_BASENAME_LEN) {
|
|
2110
|
+
needles.push(basename);
|
|
2193
2111
|
}
|
|
2194
|
-
|
|
2112
|
+
for (let j = parts.length - 2;j >= 0; j--) {
|
|
2113
|
+
needles.push(parts.slice(j).join("/"));
|
|
2114
|
+
}
|
|
2115
|
+
return needles;
|
|
2195
2116
|
}
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2117
|
+
|
|
2118
|
+
// src/domain/tool-semantics.ts
|
|
2119
|
+
var PATH_KEYS = ["path", "file_path", "filePath", "filename", "file"];
|
|
2120
|
+
var PAYLOAD_KEYS = ["content", "newText", "oldText", "edits", "patch", "replacement"];
|
|
2121
|
+
var COMMAND_KEYS = ["command", "cmd", "script"];
|
|
2122
|
+
function hasPresent(args, keys) {
|
|
2123
|
+
return keys.some((k) => args[k] != null);
|
|
2124
|
+
}
|
|
2125
|
+
function extractToolPath(args) {
|
|
2126
|
+
if (!args || typeof args !== "object")
|
|
2127
|
+
return;
|
|
2128
|
+
const a = args;
|
|
2129
|
+
for (const k of PATH_KEYS) {
|
|
2130
|
+
const v = a[k];
|
|
2131
|
+
if (typeof v === "string" && v.length > 0)
|
|
2132
|
+
return v;
|
|
2133
|
+
}
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
function classifyTool(args) {
|
|
2137
|
+
if (!args || typeof args !== "object")
|
|
2138
|
+
return "other";
|
|
2139
|
+
const a = args;
|
|
2140
|
+
const hasPath = extractToolPath(a) !== undefined;
|
|
2141
|
+
const hasPayload = hasPresent(a, PAYLOAD_KEYS);
|
|
2142
|
+
const hasCommand = hasPresent(a, COMMAND_KEYS);
|
|
2143
|
+
if (hasPath && hasPayload)
|
|
2144
|
+
return "mutates";
|
|
2145
|
+
if (hasCommand)
|
|
2146
|
+
return "executes";
|
|
2147
|
+
if (hasPath)
|
|
2148
|
+
return "accesses";
|
|
2149
|
+
return "other";
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
// src/utils/extraction.ts
|
|
2153
|
+
var TRUNCATE_RE = /\u2026\u2702\d+$/;
|
|
2154
|
+
function isTruncated(content) {
|
|
2155
|
+
return TRUNCATE_RE.test(extractText(content));
|
|
2156
|
+
}
|
|
2157
|
+
var DELETE_RESULT_RE = /\b(?:deleted|removed|unlinked)\b/i;
|
|
2158
|
+
function flattenToolCallBlock(b) {
|
|
2159
|
+
if (!isToolCallBlock(b))
|
|
2160
|
+
return [];
|
|
2161
|
+
if (b.name === "multi_tool_use.parallel" && Array.isArray(b.arguments?.tool_uses)) {
|
|
2162
|
+
return b.arguments.tool_uses.map((u) => {
|
|
2163
|
+
const recipient = u?.recipient_name ?? "";
|
|
2164
|
+
return {
|
|
2165
|
+
name: recipient.replace(/^functions\./, ""),
|
|
2166
|
+
id: u?.id ?? undefined,
|
|
2167
|
+
arguments: u?.parameters ?? {}
|
|
2168
|
+
};
|
|
2169
|
+
});
|
|
2205
2170
|
}
|
|
2206
|
-
return
|
|
2171
|
+
return [{ name: b.name, id: b.id, arguments: b.arguments }];
|
|
2207
2172
|
}
|
|
2208
|
-
function
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2173
|
+
function extractText(content) {
|
|
2174
|
+
if (typeof content === "string")
|
|
2175
|
+
return content;
|
|
2176
|
+
if (!Array.isArray(content))
|
|
2177
|
+
return "";
|
|
2178
|
+
return content.map((b) => {
|
|
2179
|
+
if (typeof b === "string")
|
|
2180
|
+
return b;
|
|
2181
|
+
if (isTextBlock(b))
|
|
2182
|
+
return b.text;
|
|
2183
|
+
return "";
|
|
2184
|
+
}).join("");
|
|
2185
|
+
}
|
|
2186
|
+
function mediaKind(type, mime) {
|
|
2187
|
+
const s = (type + " " + (mime ?? "")).toLowerCase();
|
|
2188
|
+
if (/image|input_image|image_url/.test(s))
|
|
2189
|
+
return "image";
|
|
2190
|
+
if (/audio/.test(s))
|
|
2191
|
+
return "audio";
|
|
2192
|
+
if (/video/.test(s))
|
|
2193
|
+
return "video";
|
|
2194
|
+
if (/file|document|pdf|attachment/.test(s))
|
|
2195
|
+
return "file";
|
|
2196
|
+
return "unknown";
|
|
2197
|
+
}
|
|
2198
|
+
function extractMediaAttachments(msgs) {
|
|
2199
|
+
const out = [];
|
|
2200
|
+
for (let i = 0;i < msgs.length; i++) {
|
|
2201
|
+
const blocks = Array.isArray(msgs[i].content) ? msgs[i].content : [];
|
|
2202
|
+
for (const b of blocks) {
|
|
2203
|
+
if (!b || typeof b !== "object")
|
|
2204
|
+
continue;
|
|
2205
|
+
const rec = b;
|
|
2206
|
+
const type = String(rec.type ?? "");
|
|
2207
|
+
if (type === "text" || type === "toolCall" || type === "tool_use")
|
|
2208
|
+
continue;
|
|
2209
|
+
const mimeType = rec.mimeType ?? rec.mime_type ?? rec.mediaType ?? rec.media_type;
|
|
2210
|
+
const name = rec.name ?? rec.filename ?? rec.fileName ?? rec.title;
|
|
2211
|
+
const sizeBytes = rec.sizeBytes ?? rec.size_bytes ?? rec.size;
|
|
2212
|
+
const source = typeof rec.url === "string" ? "url" : typeof rec.path === "string" ? "path" : typeof rec.data === "string" || typeof rec.base64 === "string" ? "inline" : undefined;
|
|
2213
|
+
const kind = mediaKind(type, mimeType);
|
|
2214
|
+
if (kind !== "unknown" || source || mimeType || name) {
|
|
2215
|
+
out.push({ index: i, kind, mimeType, name, sizeBytes: typeof sizeBytes === "number" ? sizeBytes : undefined, source });
|
|
2216
2216
|
}
|
|
2217
2217
|
}
|
|
2218
2218
|
}
|
|
2219
|
-
|
|
2220
|
-
return adjusted;
|
|
2221
|
-
const last = msgs[adjusted - 1];
|
|
2222
|
-
const first = msgs[adjusted];
|
|
2223
|
-
if (last && first) {
|
|
2224
|
-
const getText = (msg) => {
|
|
2225
|
-
const m = msg;
|
|
2226
|
-
const c = m?.content;
|
|
2227
|
-
if (typeof c === "string")
|
|
2228
|
-
return c;
|
|
2229
|
-
if (Array.isArray(c))
|
|
2230
|
-
return c.map((b) => {
|
|
2231
|
-
if (typeof b === "string")
|
|
2232
|
-
return b;
|
|
2233
|
-
if (typeof b === "object" && b !== null && b.type === "text")
|
|
2234
|
-
return b.text ?? "";
|
|
2235
|
-
return "";
|
|
2236
|
-
}).join("");
|
|
2237
|
-
return "";
|
|
2238
|
-
};
|
|
2239
|
-
const lastText = getText(last.message).toLowerCase();
|
|
2240
|
-
const keptText = getText(first.message).toLowerCase();
|
|
2241
|
-
const fileRe = /(?:path|file)=["']([^"']+)["']/g;
|
|
2242
|
-
const lastFiles = new Set([...lastText.matchAll(fileRe)].map((m) => m[1].split("/").pop()));
|
|
2243
|
-
fileRe.lastIndex = 0;
|
|
2244
|
-
const keptFiles = new Set([...keptText.matchAll(fileRe)].map((m) => m[1].split("/").pop()));
|
|
2245
|
-
if ([...lastFiles].filter((f) => keptFiles.has(f)).length > 0)
|
|
2246
|
-
return adjusted - 1;
|
|
2247
|
-
}
|
|
2248
|
-
return adjusted;
|
|
2219
|
+
return out;
|
|
2249
2220
|
}
|
|
2250
|
-
function
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2221
|
+
function buildToolCallIndex(msgs) {
|
|
2222
|
+
const idx = new Map;
|
|
2223
|
+
for (let i = 0;i < msgs.length; i++) {
|
|
2224
|
+
const m = msgs[i];
|
|
2225
|
+
if (m.role !== "assistant")
|
|
2226
|
+
continue;
|
|
2227
|
+
const blocks = Array.isArray(m.content) ? m.content : [];
|
|
2228
|
+
for (const b of blocks) {
|
|
2229
|
+
if (!isToolCallBlock(b))
|
|
2230
|
+
continue;
|
|
2231
|
+
if (b.id) {
|
|
2232
|
+
idx.set(b.id, { name: b.name, arguments: b.arguments, msgIndex: i });
|
|
2256
2233
|
}
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
for (
|
|
2260
|
-
const
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
}
|
|
2234
|
+
if (b.name === "multi_tool_use.parallel" && Array.isArray(b.arguments?.tool_uses)) {
|
|
2235
|
+
const nested = flattenToolCallBlock(b);
|
|
2236
|
+
for (let t = 0;t < nested.length; t++) {
|
|
2237
|
+
const tool = nested[t];
|
|
2238
|
+
const syntheticId = b.id ? b.id + "_" + t : "mtu_" + i + "_" + t;
|
|
2239
|
+
idx.set(tool.id || syntheticId, { name: tool.name, arguments: tool.arguments, msgIndex: i });
|
|
2264
2240
|
}
|
|
2265
2241
|
}
|
|
2266
2242
|
}
|
|
2267
2243
|
}
|
|
2244
|
+
return idx;
|
|
2268
2245
|
}
|
|
2269
|
-
function
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
const
|
|
2246
|
+
function trackFileOps(msgs, _tcIdx) {
|
|
2247
|
+
const tcIdx = _tcIdx ?? buildToolCallIndex(msgs);
|
|
2248
|
+
const modMap = new Map;
|
|
2249
|
+
const readSet = new Set;
|
|
2250
|
+
const delSet = new Set;
|
|
2273
2251
|
for (let i = 0;i < msgs.length; i++) {
|
|
2274
|
-
const m = msgs[i]
|
|
2275
|
-
if (m
|
|
2252
|
+
const m = msgs[i];
|
|
2253
|
+
if (m.role !== "toolResult" || m.isError)
|
|
2276
2254
|
continue;
|
|
2277
|
-
const
|
|
2278
|
-
|
|
2255
|
+
const tc = tcIdx.get(m.toolCallId ?? "");
|
|
2256
|
+
if (!tc)
|
|
2257
|
+
continue;
|
|
2258
|
+
const filePath = extractToolPath(tc.arguments);
|
|
2259
|
+
if (!filePath)
|
|
2260
|
+
continue;
|
|
2261
|
+
const cls = classifyTool(tc.arguments);
|
|
2262
|
+
if (cls === "mutates") {
|
|
2263
|
+
const resultText = extractText(m.content);
|
|
2264
|
+
if (isTruncated(resultText) || !NO_OP_RE.test(resultText)) {
|
|
2265
|
+
const existing = modMap.get(filePath);
|
|
2266
|
+
modMap.set(filePath, { toolCalls: (existing?.toolCalls ?? 0) + 1, lastIdx: i });
|
|
2267
|
+
}
|
|
2268
|
+
} else if (cls === "accesses") {
|
|
2269
|
+
if (DELETE_RESULT_RE.test(extractText(m.content)))
|
|
2270
|
+
delSet.add(filePath);
|
|
2271
|
+
else
|
|
2272
|
+
readSet.add(filePath);
|
|
2273
|
+
}
|
|
2279
2274
|
}
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2275
|
+
return {
|
|
2276
|
+
modified: [...modMap.entries()].map(([p, d]) => ({ path: p, toolCalls: d.toolCalls, lastModifiedIndex: d.lastIdx })),
|
|
2277
|
+
read: [...readSet],
|
|
2278
|
+
deleted: [...delSet]
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
function catalogErrors(msgs, _tcIdx) {
|
|
2282
|
+
const tcIdx = _tcIdx ?? buildToolCallIndex(msgs);
|
|
2283
|
+
const errors = [];
|
|
2284
|
+
for (let i = 0;i < msgs.length; i++) {
|
|
2285
|
+
const m = msgs[i];
|
|
2286
|
+
if (m.role !== "toolResult")
|
|
2287
|
+
continue;
|
|
2288
|
+
const tc = tcIdx.get(m.toolCallId ?? "");
|
|
2289
|
+
if (m.isError) {
|
|
2290
|
+
errors.push({ index: i, tool: tc?.name ?? "unknown", message: extractText(m.content).slice(0, TRUNC.ERROR_DETAIL), retryAttempted: false, resolved: false });
|
|
2291
|
+
continue;
|
|
2288
2292
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
+
if (tc && classifyTool(tc.arguments) === "executes") {
|
|
2294
|
+
const txt = extractText(m.content);
|
|
2295
|
+
if (LIKELY_ERROR_RE.test(txt) && txt.length < ERROR_SCAN_MAX_LEN) {
|
|
2296
|
+
errors.push({ index: i, tool: tc.name, message: txt.slice(0, TRUNC.MESSAGE), retryAttempted: false, resolved: false });
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
for (const err of errors) {
|
|
2301
|
+
for (let j = err.index + 1;j < Math.min(msgs.length, err.index + ERROR_RETRY_WINDOW); j++) {
|
|
2302
|
+
if (msgs[j]?.role !== "assistant")
|
|
2293
2303
|
continue;
|
|
2294
|
-
const
|
|
2295
|
-
|
|
2304
|
+
const rawBlocks = msgs[j]?.content;
|
|
2305
|
+
const blocks = Array.isArray(rawBlocks) ? rawBlocks : [];
|
|
2306
|
+
const retryTool = blocks.flatMap((b) => flattenToolCallBlock(b)).find((t) => t.name === err.tool);
|
|
2307
|
+
if (!retryTool)
|
|
2296
2308
|
continue;
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2309
|
+
err.retryAttempted = true;
|
|
2310
|
+
for (let k = j + 1;k < Math.min(msgs.length, j + ERROR_RESOLVE_WINDOW); k++) {
|
|
2311
|
+
const mk = msgs[k];
|
|
2312
|
+
if (mk?.role !== "toolResult" || mk.isError)
|
|
2313
|
+
continue;
|
|
2314
|
+
const resolved = retryTool.id != null ? mk.toolCallId === retryTool.id : tcIdx.get(mk.toolCallId ?? "")?.name === err.tool;
|
|
2315
|
+
if (resolved) {
|
|
2316
|
+
err.resolved = true;
|
|
2317
|
+
break;
|
|
2318
|
+
}
|
|
2302
2319
|
}
|
|
2320
|
+
break;
|
|
2303
2321
|
}
|
|
2304
2322
|
}
|
|
2305
|
-
return
|
|
2306
|
-
}
|
|
2307
|
-
function extractUserNote(args) {
|
|
2308
|
-
const SKIP = new Set(["verbose", "debug", "dry-run", "light", "balanced", "aggressive"]);
|
|
2309
|
-
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
2310
|
-
const isOptionToken = (t) => t.startsWith("--") || SKIP.has(t.toLowerCase()) || /^[a-z0-9_.-]+\/[a-z0-9_.:-]+$/i.test(t);
|
|
2311
|
-
const nonFlags = tokens.filter((t) => !isOptionToken(t));
|
|
2312
|
-
return nonFlags.length > 0 ? nonFlags.join(" ") : undefined;
|
|
2323
|
+
return errors;
|
|
2313
2324
|
}
|
|
2314
|
-
function
|
|
2315
|
-
const
|
|
2316
|
-
|
|
2317
|
-
for (const
|
|
2318
|
-
if (
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2325
|
+
function extractDecisions(msgs, _tcIdx) {
|
|
2326
|
+
const tcIdx = _tcIdx ?? buildToolCallIndex(msgs);
|
|
2327
|
+
const decisions = [];
|
|
2328
|
+
for (const [id, tc] of tcIdx) {
|
|
2329
|
+
if (tc.name !== "ask_user")
|
|
2330
|
+
continue;
|
|
2331
|
+
const args = tc.arguments;
|
|
2332
|
+
const question = typeof args === "string" ? args : args?.question ?? args?.prompt ?? "";
|
|
2333
|
+
if (!question)
|
|
2334
|
+
continue;
|
|
2335
|
+
for (let i = tc.msgIndex + 1;i < Math.min(msgs.length, tc.msgIndex + 4); i++) {
|
|
2336
|
+
if (msgs[i]?.role === "toolResult" && msgs[i]?.toolCallId === id) {
|
|
2337
|
+
decisions.push({ index: tc.msgIndex, type: "explicit", summary: question.slice(0, TRUNC.DECISION_SUMMARY), userResponse: extractText(msgs[i].content).slice(0, TRUNC.USER_RESPONSE) });
|
|
2338
|
+
break;
|
|
2339
|
+
}
|
|
2322
2340
|
}
|
|
2323
|
-
batch.push(ch);
|
|
2324
|
-
bt += ch.tokenEstimate;
|
|
2325
2341
|
}
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
return new Map;
|
|
2334
|
-
const weights = summaries.map((s, i) => {
|
|
2335
|
-
let w = 1;
|
|
2336
|
-
if (s.priority === "critical")
|
|
2337
|
-
w *= 2;
|
|
2338
|
-
else if (s.priority === "high")
|
|
2339
|
-
w *= 1.5;
|
|
2340
|
-
else if (s.priority === "low")
|
|
2341
|
-
w *= 0.6;
|
|
2342
|
-
const errorKeywords = (s.summary.match(/error|fail|bug|fix|crash|exception/gi) ?? []).length;
|
|
2343
|
-
w *= 1 + errorKeywords * 0.2;
|
|
2344
|
-
const recency = (i + 1) / n;
|
|
2345
|
-
w *= 0.6 + recency * 0.4;
|
|
2346
|
-
if (s.keyDecisions.length > 0)
|
|
2347
|
-
w *= 1.3;
|
|
2348
|
-
return w;
|
|
2349
|
-
});
|
|
2350
|
-
const totalWeight = weights.reduce((a, b) => a + b, 0);
|
|
2351
|
-
const baseTokensPerTopic = Math.floor(totalBudget / n);
|
|
2352
|
-
const budgetMap = new Map;
|
|
2353
|
-
for (let i = 0;i < summaries.length; i++) {
|
|
2354
|
-
const allocated = Math.round(baseTokensPerTopic * (weights[i] / (totalWeight / n)));
|
|
2355
|
-
budgetMap.set(summaries[i].topic, Math.max(200, allocated));
|
|
2342
|
+
for (let i = 0;i < msgs.length; i++) {
|
|
2343
|
+
if (msgs[i]?.role !== "user")
|
|
2344
|
+
continue;
|
|
2345
|
+
const txt = extractText(msgs[i].content);
|
|
2346
|
+
if (CHOICE_RE.test(txt)) {
|
|
2347
|
+
decisions.push({ index: i, type: "implicit", summary: txt.slice(0, TRUNC.DECISION_SUMMARY) });
|
|
2348
|
+
}
|
|
2356
2349
|
}
|
|
2357
|
-
return
|
|
2358
|
-
}
|
|
2359
|
-
function preProcessSummaries(summaries, budgetTokens) {
|
|
2360
|
-
const topicBudgets = budgetTokens ? allocateTopicBudgets(summaries, budgetTokens) : null;
|
|
2361
|
-
return {
|
|
2362
|
-
decisions: [...new Set(summaries.flatMap((s) => s.keyDecisions))],
|
|
2363
|
-
modified: [...new Set(summaries.flatMap((s) => s.filesModified))].sort(),
|
|
2364
|
-
read: [...new Set(summaries.flatMap((s) => s.filesRead))].sort(),
|
|
2365
|
-
text: summaries.map((cs, i) => {
|
|
2366
|
-
const budgetHint = topicBudgets?.get(cs.topic);
|
|
2367
|
-
const budgetLine = budgetHint ? `
|
|
2368
|
-
Budget: ~` + budgetHint + " tokens" : "";
|
|
2369
|
-
return "### Segment " + (i + 1) + ": " + cs.topic + `
|
|
2370
|
-
Priority: ` + cs.priority + " | msgs " + cs.startIndex + "-" + cs.endIndex + budgetLine + `
|
|
2371
|
-
|
|
2372
|
-
` + cs.summary + `
|
|
2373
|
-
|
|
2374
|
-
Decisions: ` + (cs.keyDecisions.join("; ") || "None") + `
|
|
2375
|
-
Modified: ` + (cs.filesModified.join(", ") || "None") + `
|
|
2376
|
-
Read: ` + (cs.filesRead.join(", ") || "None");
|
|
2377
|
-
}).join(`
|
|
2378
|
-
---
|
|
2379
|
-
`)
|
|
2380
|
-
};
|
|
2381
|
-
}
|
|
2382
|
-
function buildExtractionContext(extraction, forRange) {
|
|
2383
|
-
const files = forRange ? extraction.modifiedFiles.filter((f) => f.lastModifiedIndex >= forRange.start && f.lastModifiedIndex <= forRange.end) : extraction.modifiedFiles;
|
|
2384
|
-
const errors = forRange ? extraction.errors.filter((e) => e.index >= forRange.start && e.index <= forRange.end) : extraction.errors;
|
|
2385
|
-
const media = forRange ? (extraction.mediaAttachments ?? []).filter((a) => a.index >= forRange.start && a.index <= forRange.end) : extraction.mediaAttachments ?? [];
|
|
2386
|
-
return [
|
|
2387
|
-
"## Deterministic Extraction (verified facts)",
|
|
2388
|
-
"Files modified: " + (files.map((f) => f.path).join(", ") || "none"),
|
|
2389
|
-
"Errors: " + (errors.map((e) => "[" + e.tool + "] " + e.message.slice(0, 80) + (e.resolved ? " \u2713" : "")).join("; ") || "none"),
|
|
2390
|
-
"Decisions: " + (extraction.decisions.map((d) => d.type + ": " + d.summary.slice(0, 60)).join("; ") || "none"),
|
|
2391
|
-
"Constraints: " + (extraction.constraints.map((c) => "[" + c.category + "] " + c.text.slice(0, 60)).join("; ") || "none"),
|
|
2392
|
-
"Media attachments: " + (media.map((a) => a.kind + (a.name ? ":" + a.name : "") + (a.mimeType ? " (" + a.mimeType + ")" : "") + " @msg" + a.index).join("; ") || "none")
|
|
2393
|
-
].join(`
|
|
2394
|
-
`);
|
|
2350
|
+
return decisions;
|
|
2395
2351
|
}
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2352
|
+
var CONSTRAINT_PATTERNS = [
|
|
2353
|
+
{ re: /\b(?:must|need|require|has to|important)\b.*\b(?:be|use|have|include|support)\b/i, cat: "requirement", conf: TUNING.CONFIDENCE_HIGH },
|
|
2354
|
+
{ re: /\b(?:don't|never|avoid|shouldn't|must not|do not|no\s+(?:need|want))\b/i, cat: "prohibition", conf: TUNING.CONFIDENCE_MEDIUM },
|
|
2355
|
+
{ re: /\b(?:prefer|like|want|would rather|should)\b.*\b(?:use|be|have|with)\b/i, cat: "preference", conf: TUNING.CONFIDENCE_LOW },
|
|
2356
|
+
{ re: /(?<![A-Za-z0-9_])(?:kritik|kritikal|\u00F6nemli|onemli|\u015Fart|sart|zorunlu|\u015Fart ko\u015Ful|\u00F6nemli \u015Fart|kesinlikle|kesinlikle \u015Fart|asla|sak\u0131n|sak\u0131nha|bunu yapma|b\u00F6yle olsun|b\u00F6yle yap\u0131n|\u015F\u00F6yle olsun|\u015F\u00F6yle yap\u0131n)(?![A-Za-z0-9_])/iu, cat: "requirement", conf: TUNING.CONFIDENCE_MEDIUM },
|
|
2357
|
+
{ re: /(?<![A-Za-z0-9_])(?:yapma|kullanma|sak\u0131n|asla\s+(?:kullanma|yapma|getirme))(?![A-Za-z0-9_])/iu, cat: "prohibition", conf: TUNING.CONFIDENCE_MEDIUM },
|
|
2358
|
+
{ re: /(?<![A-Za-z0-9_])(?:tercih|isterim|olsun|kullanal\u0131m|yapal\u0131m|istiyorum)(?![A-Za-z0-9_])/iu, cat: "preference", conf: TUNING.CONFIDENCE_LOW }
|
|
2359
|
+
];
|
|
2360
|
+
function mineConstraints(msgs) {
|
|
2361
|
+
const constraints = [];
|
|
2362
|
+
for (let i = 0;i < msgs.length; i++) {
|
|
2363
|
+
if (msgs[i]?.role !== "user")
|
|
2402
2364
|
continue;
|
|
2403
|
-
|
|
2404
|
-
if (
|
|
2405
|
-
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
const block = part;
|
|
2411
|
-
if (block.type === "text" && typeof block.text === "string")
|
|
2412
|
-
mc += block.text.length;
|
|
2365
|
+
const txt = extractText(msgs[i].content);
|
|
2366
|
+
if (txt.length < 10 || txt.startsWith("/"))
|
|
2367
|
+
continue;
|
|
2368
|
+
for (const { re, cat, conf } of CONSTRAINT_PATTERNS) {
|
|
2369
|
+
if (re.test(txt)) {
|
|
2370
|
+
constraints.push({ index: i, text: txt.slice(0, TRUNC.CONSTRAINT_TEXT), category: cat, confidence: conf });
|
|
2371
|
+
break;
|
|
2413
2372
|
}
|
|
2414
2373
|
}
|
|
2415
|
-
totalChars += mc;
|
|
2416
|
-
if (m.role === "toolResult")
|
|
2417
|
-
toolChars += mc;
|
|
2418
2374
|
}
|
|
2419
|
-
return
|
|
2375
|
+
return constraints;
|
|
2420
2376
|
}
|
|
2421
|
-
function
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2377
|
+
function segmentTopicsHeuristic(msgs, pc, maxSegs = 20, _tcIdx) {
|
|
2378
|
+
const topics = [];
|
|
2379
|
+
let startIdx = 0, tokenAcc = 0, lastFile = null, errAcc = 0;
|
|
2380
|
+
let currentType = "exploration";
|
|
2381
|
+
let currentPrimaryFile = null;
|
|
2382
|
+
const tcIdx = _tcIdx ?? buildToolCallIndex(msgs);
|
|
2383
|
+
for (let i = 0;i < msgs.length; i++) {
|
|
2384
|
+
const m = msgs[i];
|
|
2385
|
+
const txt = extractText(m.content);
|
|
2386
|
+
tokenAcc += estimateTokens(txt);
|
|
2387
|
+
let brk = false;
|
|
2388
|
+
if (m.role === "assistant") {
|
|
2389
|
+
const blocks = Array.isArray(m.content) ? m.content : [];
|
|
2390
|
+
for (const b of blocks) {
|
|
2391
|
+
for (const tool of flattenToolCallBlock(b)) {
|
|
2392
|
+
const fp = extractToolPath(tool.arguments);
|
|
2393
|
+
if (!fp)
|
|
2394
|
+
continue;
|
|
2395
|
+
const fn = path3.basename(fp);
|
|
2396
|
+
if (lastFile && fn !== lastFile && tokenAcc > pc.minChunkTokens)
|
|
2397
|
+
brk = true;
|
|
2398
|
+
lastFile = fn;
|
|
2399
|
+
currentPrimaryFile = fp;
|
|
2400
|
+
const toolCls = classifyTool(tool.arguments);
|
|
2401
|
+
if (toolCls === "mutates") {
|
|
2402
|
+
if (currentType !== "implementation")
|
|
2403
|
+
currentType = "implementation";
|
|
2404
|
+
} else if (toolCls === "accesses") {
|
|
2405
|
+
if (currentType === "exploration")
|
|
2406
|
+
currentType = "review";
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
if (m.role === "toolResult" && m.isError) {
|
|
2412
|
+
errAcc++;
|
|
2413
|
+
if (currentType !== "implementation")
|
|
2414
|
+
currentType = "debugging";
|
|
2415
|
+
}
|
|
2416
|
+
if (m.role === "toolResult" && !m.isError) {
|
|
2417
|
+
const tc = tcIdx.get(m.toolCallId ?? "");
|
|
2418
|
+
if (tc && classifyTool(tc.arguments) === "executes" && /error|fail/i.test(txt)) {
|
|
2419
|
+
errAcc++;
|
|
2420
|
+
if (currentType !== "implementation")
|
|
2421
|
+
currentType = "debugging";
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
if (m.role === "user" && SHIFT_RE.test(txt) && tokenAcc > pc.minChunkTokens)
|
|
2425
|
+
brk = true;
|
|
2426
|
+
if (tokenAcc >= pc.maxChunkTokens)
|
|
2427
|
+
brk = true;
|
|
2428
|
+
if (brk && i > startIdx && topics.length < maxSegs - 1) {
|
|
2429
|
+
topics.push({ startIndex: startIdx, endIndex: i, primaryFile: currentPrimaryFile, type: currentType, errorDensity: errAcc });
|
|
2430
|
+
startIdx = i + 1;
|
|
2431
|
+
tokenAcc = 0;
|
|
2432
|
+
lastFile = null;
|
|
2433
|
+
errAcc = 0;
|
|
2434
|
+
currentType = "exploration";
|
|
2435
|
+
currentPrimaryFile = null;
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
if (startIdx < msgs.length) {
|
|
2439
|
+
topics.push({ startIndex: startIdx, endIndex: msgs.length - 1, primaryFile: currentPrimaryFile, type: currentType, errorDensity: errAcc });
|
|
2440
|
+
}
|
|
2441
|
+
return topics;
|
|
2429
2442
|
}
|
|
2430
|
-
function
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
if (hasModifications)
|
|
2445
|
-
return "implementation";
|
|
2446
|
-
return "implementation";
|
|
2447
|
-
}
|
|
2448
|
-
function buildExplorationContext(report) {
|
|
2449
|
-
if (!report.mainGoal && !report.crossReferences.length && !report.enrichedConstraints.length)
|
|
2450
|
-
return "";
|
|
2451
|
-
return [
|
|
2452
|
-
"## Exploration Report",
|
|
2453
|
-
"Main goal: " + report.mainGoal,
|
|
2454
|
-
"Session type: " + report.sessionType,
|
|
2455
|
-
report.crossReferences.length ? "Cross-references: " + report.crossReferences.join("; ") : "",
|
|
2456
|
-
report.enrichedConstraints.length ? "Enriched constraints: " + report.enrichedConstraints.join("; ") : "",
|
|
2457
|
-
report.statusAssessment.done.length ? "Assessed done: " + report.statusAssessment.done.join("; ") : "",
|
|
2458
|
-
report.statusAssessment.inProgress.length ? "Assessed in-progress: " + report.statusAssessment.inProgress.join("; ") : "",
|
|
2459
|
-
report.criticalContext.length ? "Critical context: " + report.criticalContext.join("; ") : ""
|
|
2460
|
-
].filter(Boolean).join(`
|
|
2461
|
-
`);
|
|
2462
|
-
}
|
|
2463
|
-
|
|
2464
|
-
// src/index.ts
|
|
2465
|
-
init_tokens();
|
|
2466
|
-
init_cache();
|
|
2467
|
-
|
|
2468
|
-
// src/app/run-smart-compact.ts
|
|
2469
|
-
init_overlays();
|
|
2470
|
-
init_logger();
|
|
2471
|
-
|
|
2472
|
-
// src/app/run-context.ts
|
|
2473
|
-
function markMeasuredPhase(rc, phase, startMs, endMs = Date.now()) {
|
|
2474
|
-
rc.phaseTimings.push({ phase, durationMs: Math.max(0, endMs - startMs) });
|
|
2475
|
-
rc.phaseStart = endMs;
|
|
2476
|
-
}
|
|
2477
|
-
function markPhase(rc, phase) {
|
|
2478
|
-
markMeasuredPhase(rc, phase, rc.phaseStart);
|
|
2443
|
+
function buildTimeline(msgs, errors) {
|
|
2444
|
+
const timeline = [];
|
|
2445
|
+
const errorIndices = new Set(errors.map((e) => e.index));
|
|
2446
|
+
for (let i = 0;i < msgs.length; i++) {
|
|
2447
|
+
const m = msgs[i];
|
|
2448
|
+
if (m.role === "user") {
|
|
2449
|
+
const txt = extractText(m.content);
|
|
2450
|
+
if (!txt.startsWith("/"))
|
|
2451
|
+
timeline.push({ index: i, event: "user_request", summary: txt.slice(0, TRUNC.TIMELINE_EVENT) });
|
|
2452
|
+
}
|
|
2453
|
+
if (errorIndices.has(i))
|
|
2454
|
+
timeline.push({ index: i, event: "error", summary: errors.find((e) => e.index === i)?.message.slice(0, TRUNC.TIMELINE_ERROR) ?? "error" });
|
|
2455
|
+
}
|
|
2456
|
+
return timeline.length > 30 ? [...timeline.filter((t) => t.event === "user_request").slice(0, TRUNC.TIMELINE_DISPLAY), ...timeline.filter((t) => t.event === "error")] : timeline;
|
|
2479
2457
|
}
|
|
2480
|
-
function
|
|
2481
|
-
|
|
2458
|
+
function extractMainGoal(msgs) {
|
|
2459
|
+
for (const m of msgs) {
|
|
2460
|
+
if (m?.role !== "user")
|
|
2461
|
+
continue;
|
|
2462
|
+
const txt = extractText(m.content).trim();
|
|
2463
|
+
if (txt && !txt.startsWith("/"))
|
|
2464
|
+
return txt.slice(0, TRUNC.MESSAGE);
|
|
2465
|
+
}
|
|
2466
|
+
return null;
|
|
2482
2467
|
}
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2468
|
+
function extractOpenLoops(msgs, extraction) {
|
|
2469
|
+
const loops = [];
|
|
2470
|
+
let loopId = 0;
|
|
2471
|
+
const fileNeedles = extraction.modifiedFiles.map((f) => ({
|
|
2472
|
+
path: f.path,
|
|
2473
|
+
needles: buildPathNeedles(f.path)
|
|
2474
|
+
}));
|
|
2475
|
+
for (const err of extraction.errors.filter((e) => !e.resolved)) {
|
|
2476
|
+
const errLower = err.message.toLowerCase();
|
|
2477
|
+
const errFiles = fileNeedles.filter(({ needles }) => needles.some((n) => errLower.includes(n))).map(({ path: path4 }) => path4);
|
|
2478
|
+
loops.push({
|
|
2479
|
+
id: ID_PREFIX.OPEN_LOOP + ++loopId,
|
|
2480
|
+
type: "bugfix",
|
|
2481
|
+
priority: err.retryAttempted ? "high" : "normal",
|
|
2482
|
+
status: "open",
|
|
2483
|
+
summary: err.message.slice(0, TRUNC.OPEN_LOOP_SUMMARY),
|
|
2484
|
+
files: errFiles,
|
|
2485
|
+
sourceIndex: err.index
|
|
2486
|
+
});
|
|
2501
2487
|
}
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2488
|
+
const FOLLOWUP_RE = /(?:next\s+(?:step|thing)|todo|action item|follow\s*up|still (?:need|have) to|gotta|yapalim|yapal\u0131m|yapmamiz|yapmam\u0131z|gerekiyor|eklenecek|d\u00FCzeltilecek|duzeltilecek|bitmedi|kaldi|kald\u0131)/iu;
|
|
2489
|
+
for (let idx = 0;idx < msgs.length; idx++) {
|
|
2490
|
+
const msg = msgs[idx];
|
|
2491
|
+
if (msg.role !== "user")
|
|
2492
|
+
continue;
|
|
2493
|
+
const txt = extractText(msg.content);
|
|
2494
|
+
if (txt.length < 10 || txt.startsWith("/"))
|
|
2495
|
+
continue;
|
|
2496
|
+
if (FOLLOWUP_RE.test(txt)) {
|
|
2497
|
+
const isDup = loops.some((l) => Math.abs((l.sourceIndex ?? 0) - idx) < 5);
|
|
2498
|
+
if (!isDup) {
|
|
2499
|
+
loops.push({
|
|
2500
|
+
id: ID_PREFIX.OPEN_LOOP + ++loopId,
|
|
2501
|
+
type: "follow-up",
|
|
2502
|
+
priority: "normal",
|
|
2503
|
+
status: "open",
|
|
2504
|
+
summary: txt.slice(0, TRUNC.OPEN_LOOP_SUMMARY),
|
|
2505
|
+
files: [],
|
|
2506
|
+
sourceIndex: idx
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2508
2510
|
}
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
|
-
var KNOWN_PROFILES = new Set(["light", "balanced", "aggressive"]);
|
|
2546
|
-
function isStringArray(v) {
|
|
2547
|
-
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
2548
|
-
}
|
|
2549
|
-
function isValidSmartCompactDetails(d) {
|
|
2550
|
-
if (!d || typeof d !== "object")
|
|
2551
|
-
return false;
|
|
2552
|
-
const r = d;
|
|
2553
|
-
if (!isStringArray(r.modifiedFiles))
|
|
2554
|
-
return false;
|
|
2555
|
-
if (!isStringArray(r.readFiles))
|
|
2556
|
-
return false;
|
|
2557
|
-
if (!isStringArray(r.topics))
|
|
2558
|
-
return false;
|
|
2559
|
-
if (typeof r.method !== "string" || !KNOWN_METHODS.has(r.method))
|
|
2560
|
-
return false;
|
|
2561
|
-
if (typeof r.profile !== "string" || !KNOWN_PROFILES.has(r.profile))
|
|
2562
|
-
return false;
|
|
2563
|
-
if (typeof r.qualityScore !== "number" || !Number.isFinite(r.qualityScore))
|
|
2564
|
-
return false;
|
|
2565
|
-
if (typeof r.totalMessages !== "number" || !Number.isFinite(r.totalMessages))
|
|
2566
|
-
return false;
|
|
2567
|
-
if (r.gaps !== undefined && !isStringArray(r.gaps))
|
|
2568
|
-
return false;
|
|
2569
|
-
if (r.verified !== undefined && typeof r.verified !== "boolean")
|
|
2570
|
-
return false;
|
|
2571
|
-
if (r.backupPath !== undefined && r.backupPath !== null && typeof r.backupPath !== "string")
|
|
2572
|
-
return false;
|
|
2573
|
-
return true;
|
|
2511
|
+
const BLOCKED_RE = /blocked|waiting for|depend|ba[\u011Fg]li|bekliyor|engell/i;
|
|
2512
|
+
for (let idx = 0;idx < msgs.length; idx++) {
|
|
2513
|
+
const msg = msgs[idx];
|
|
2514
|
+
if (msg.role !== "user")
|
|
2515
|
+
continue;
|
|
2516
|
+
const txt = extractText(msg.content);
|
|
2517
|
+
if (BLOCKED_RE.test(txt)) {
|
|
2518
|
+
const isDup = loops.some((l) => Math.abs((l.sourceIndex ?? 0) - idx) < 5);
|
|
2519
|
+
if (!isDup) {
|
|
2520
|
+
loops.push({
|
|
2521
|
+
id: ID_PREFIX.OPEN_LOOP + ++loopId,
|
|
2522
|
+
type: "blocked",
|
|
2523
|
+
priority: "high",
|
|
2524
|
+
status: "open",
|
|
2525
|
+
summary: txt.slice(0, TRUNC.OPEN_LOOP_SUMMARY),
|
|
2526
|
+
files: [],
|
|
2527
|
+
sourceIndex: idx
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
for (const err of extraction.errors.filter((e) => e.retryAttempted && !e.resolved)) {
|
|
2533
|
+
const exists = loops.some((l) => l.type === "bugfix" && l.sourceIndex === err.index);
|
|
2534
|
+
if (!exists) {
|
|
2535
|
+
loops.push({
|
|
2536
|
+
id: ID_PREFIX.OPEN_LOOP + ++loopId,
|
|
2537
|
+
type: "retry",
|
|
2538
|
+
priority: "high",
|
|
2539
|
+
status: "open",
|
|
2540
|
+
summary: "Retried but unresolved: " + err.message.slice(0, TRUNC.SNIPPET),
|
|
2541
|
+
files: [],
|
|
2542
|
+
sourceIndex: err.index
|
|
2543
|
+
});
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
return loops;
|
|
2574
2547
|
}
|
|
2575
|
-
function
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
const
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
const
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
modifiedFiles:
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
tokensBefore: typeof r.tokensBefore === "number" ? r.tokensBefore : 0
|
|
2548
|
+
function extractStructured(msgs, pc, precomputedTcIdx) {
|
|
2549
|
+
const tcIdx = precomputedTcIdx ?? buildToolCallIndex(msgs);
|
|
2550
|
+
const { modified, read, deleted } = trackFileOps(msgs, tcIdx);
|
|
2551
|
+
const errors = catalogErrors(msgs, tcIdx);
|
|
2552
|
+
const decisions = extractDecisions(msgs, tcIdx);
|
|
2553
|
+
const constraints = mineConstraints(msgs);
|
|
2554
|
+
const topics = segmentTopicsHeuristic(msgs, pc, 20, tcIdx);
|
|
2555
|
+
const timeline = buildTimeline(msgs, errors);
|
|
2556
|
+
const mediaAttachments = extractMediaAttachments(msgs);
|
|
2557
|
+
const mainGoal = extractMainGoal(msgs);
|
|
2558
|
+
const lastUserMessages = msgs.filter((m) => m.role === "user").slice(-5).map((m) => extractText(m.content));
|
|
2559
|
+
const lastErrors = errors.slice(-3).map((e) => e.message);
|
|
2560
|
+
return {
|
|
2561
|
+
modifiedFiles: modified,
|
|
2562
|
+
readFiles: read,
|
|
2563
|
+
deletedFiles: deleted,
|
|
2564
|
+
errors,
|
|
2565
|
+
decisions,
|
|
2566
|
+
constraints,
|
|
2567
|
+
topics,
|
|
2568
|
+
timeline,
|
|
2569
|
+
mediaAttachments,
|
|
2570
|
+
mainGoal,
|
|
2571
|
+
lastUserMessages,
|
|
2572
|
+
lastErrors,
|
|
2573
|
+
messageCount: msgs.length
|
|
2602
2574
|
};
|
|
2603
|
-
return isValidSmartCompactDetails(repaired) ? repaired : null;
|
|
2604
2575
|
}
|
|
2605
2576
|
|
|
2606
|
-
// src/utils/
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
"
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
"utils.ts",
|
|
2615
|
-
"main.ts",
|
|
2616
|
-
"main.js",
|
|
2617
|
-
"mod.rs",
|
|
2618
|
-
"lib.rs",
|
|
2619
|
-
"__init__.py"
|
|
2620
|
-
]);
|
|
2621
|
-
var MIN_BARE_BASENAME_LEN = 5;
|
|
2622
|
-
function buildPathNeedles(filePath) {
|
|
2623
|
-
const parts = filePath.toLowerCase().split("/").filter(Boolean);
|
|
2624
|
-
if (parts.length === 0)
|
|
2625
|
-
return [];
|
|
2626
|
-
const needles = [];
|
|
2627
|
-
const basename = parts[parts.length - 1];
|
|
2628
|
-
if (!GENERIC_BASENAMES.has(basename) && basename.length >= MIN_BARE_BASENAME_LEN) {
|
|
2629
|
-
needles.push(basename);
|
|
2577
|
+
// src/utils/helpers.ts
|
|
2578
|
+
init_constants();
|
|
2579
|
+
var VALID_PROFILES = ["light", "balanced", "aggressive"];
|
|
2580
|
+
var PROFILE_NUMERIC_KEYS = ["summaryBudgetTokens", "keepRecentTokens", "minChunkTokens", "maxChunkTokens", "singlePassMaxTokens", "batchMaxTokens"];
|
|
2581
|
+
function validateSmartCompactConfig(sc) {
|
|
2582
|
+
if ("profile" in sc && !VALID_PROFILES.includes(sc.profile)) {
|
|
2583
|
+
warn("smart-compact config: invalid profile '" + sc.profile + "', expected light|balanced|aggressive. Using default 'balanced'.");
|
|
2584
|
+
delete sc.profile;
|
|
2630
2585
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
2586
|
+
if ("autoTrigger" in sc && typeof sc.autoTrigger !== "boolean") {
|
|
2587
|
+
warn("smart-compact config: autoTrigger must be boolean, got " + typeof sc.autoTrigger);
|
|
2588
|
+
delete sc.autoTrigger;
|
|
2589
|
+
}
|
|
2590
|
+
if ("backupEnabled" in sc && typeof sc.backupEnabled !== "boolean") {
|
|
2591
|
+
warn("smart-compact config: backupEnabled must be boolean, got " + typeof sc.backupEnabled);
|
|
2592
|
+
delete sc.backupEnabled;
|
|
2593
|
+
}
|
|
2594
|
+
if ("summaryModel" in sc && sc.summaryModel !== null && typeof sc.summaryModel !== "string") {
|
|
2595
|
+
warn("smart-compact config: summaryModel must be string|null, got " + typeof sc.summaryModel);
|
|
2596
|
+
delete sc.summaryModel;
|
|
2597
|
+
}
|
|
2598
|
+
if ("segmentationModel" in sc && sc.segmentationModel !== null && typeof sc.segmentationModel !== "string") {
|
|
2599
|
+
warn("smart-compact config: segmentationModel must be string|null, got " + typeof sc.segmentationModel);
|
|
2600
|
+
delete sc.segmentationModel;
|
|
2601
|
+
}
|
|
2602
|
+
if ("profiles" in sc) {
|
|
2603
|
+
if (typeof sc.profiles !== "object" || sc.profiles === null || Array.isArray(sc.profiles)) {
|
|
2604
|
+
warn("smart-compact config: profiles must be an object, got " + typeof sc.profiles);
|
|
2605
|
+
delete sc.profiles;
|
|
2606
|
+
} else {
|
|
2607
|
+
const profiles = sc.profiles;
|
|
2608
|
+
for (const [profileName, value] of Object.entries(profiles)) {
|
|
2609
|
+
if (!VALID_PROFILES.includes(profileName)) {
|
|
2610
|
+
warn("smart-compact config: ignoring unknown profile override '" + profileName + "'.");
|
|
2611
|
+
delete profiles[profileName];
|
|
2612
|
+
continue;
|
|
2613
|
+
}
|
|
2614
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2615
|
+
warn("smart-compact config: profile '" + profileName + "' must be an object.");
|
|
2616
|
+
delete profiles[profileName];
|
|
2617
|
+
continue;
|
|
2618
|
+
}
|
|
2619
|
+
const profileCfg = value;
|
|
2620
|
+
for (const [key, raw] of Object.entries(profileCfg)) {
|
|
2621
|
+
if (!PROFILE_NUMERIC_KEYS.includes(key)) {
|
|
2622
|
+
warn("smart-compact config: ignoring unknown profile key '" + profileName + "." + key + "'.");
|
|
2623
|
+
delete profileCfg[key];
|
|
2624
|
+
continue;
|
|
2625
|
+
}
|
|
2626
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0 || raw > 1e6) {
|
|
2627
|
+
warn("smart-compact config: profile '" + profileName + "." + key + "' must be a positive finite number.");
|
|
2628
|
+
delete profileCfg[key];
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
if ("autoTriggerTimeoutMs" in sc) {
|
|
2635
|
+
const v = sc.autoTriggerTimeoutMs;
|
|
2636
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 1000 || v > 300000) {
|
|
2637
|
+
warn("smart-compact config: autoTriggerTimeoutMs must be 1000\u2013300000, got " + v + ". Using default " + DEFAULT_CONFIG.autoTriggerTimeoutMs + "ms.");
|
|
2638
|
+
delete sc.autoTriggerTimeoutMs;
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
if ("minContextPercent" in sc) {
|
|
2642
|
+
const v = sc.minContextPercent;
|
|
2643
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 0 || v > 100) {
|
|
2644
|
+
warn("smart-compact config: minContextPercent must be 0\u2013100, got " + v + ". Using default " + DEFAULT_CONFIG.minContextPercent + ".");
|
|
2645
|
+
delete sc.minContextPercent;
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
if ("backupDir" in sc && sc.backupDir !== undefined && typeof sc.backupDir !== "string") {
|
|
2649
|
+
warn("smart-compact config: backupDir must be a string, got " + typeof sc.backupDir + ". Using default.");
|
|
2650
|
+
delete sc.backupDir;
|
|
2651
|
+
}
|
|
2652
|
+
if ("pinPaths" in sc && sc.pinPaths !== undefined) {
|
|
2653
|
+
if (!Array.isArray(sc.pinPaths) || !sc.pinPaths.every((x) => typeof x === "string")) {
|
|
2654
|
+
warn("smart-compact config: pinPaths must be a string[], ignoring.");
|
|
2655
|
+
delete sc.pinPaths;
|
|
2656
|
+
}
|
|
2633
2657
|
}
|
|
2634
|
-
return needles;
|
|
2635
|
-
}
|
|
2636
|
-
|
|
2637
|
-
// src/utils/extraction.ts
|
|
2638
|
-
var TRUNCATE_RE = /\u2026\u2702\d+$/;
|
|
2639
|
-
function isTruncated(content) {
|
|
2640
|
-
return TRUNCATE_RE.test(extractText(content));
|
|
2641
|
-
}
|
|
2642
|
-
var WRITE_TOOL_HINTS = ["write", "edit", "patch", "create", "append", "update", "apply"];
|
|
2643
|
-
var DELETE_TOOL_HINTS = ["delete", "remove", "unlink"];
|
|
2644
|
-
var READ_TOOL_HINTS = ["read", "view", "open"];
|
|
2645
|
-
function hasToolHint(tool, hints) {
|
|
2646
|
-
return hints.some((hint) => tool.includes(hint));
|
|
2647
2658
|
}
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2659
|
+
var _cfg = null;
|
|
2660
|
+
var _cfgMtime = 0;
|
|
2661
|
+
var _cfgPath = null;
|
|
2662
|
+
function loadConfig() {
|
|
2663
|
+
try {
|
|
2664
|
+
const p = settingsFile();
|
|
2665
|
+
const stat = fs2.statSync(p);
|
|
2666
|
+
if (_cfg && _cfgPath === p && stat.mtimeMs === _cfgMtime)
|
|
2667
|
+
return _cfg;
|
|
2668
|
+
const raw = JSON.parse(fs2.readFileSync(p, "utf-8"));
|
|
2669
|
+
const sc = raw[CONFIG_KEY] ?? raw[CONFIG_KEY_ALT] ?? {};
|
|
2670
|
+
validateSmartCompactConfig(sc);
|
|
2671
|
+
const merged = { ...DEFAULT_CONFIG, ...sc };
|
|
2672
|
+
if (sc.profiles)
|
|
2673
|
+
merged.profiles = { ...PROFILES, ...sc.profiles };
|
|
2674
|
+
if (!merged.backupDir)
|
|
2675
|
+
merged.backupDir = defaultBackupDir();
|
|
2676
|
+
_cfg = merged;
|
|
2677
|
+
_cfgMtime = stat.mtimeMs;
|
|
2678
|
+
_cfgPath = p;
|
|
2679
|
+
return _cfg;
|
|
2680
|
+
} catch (e) {
|
|
2681
|
+
debug("loadConfig: settings.json not found or unreadable, using defaults", e);
|
|
2682
|
+
const fallback = { ...DEFAULT_CONFIG, backupDir: defaultBackupDir() };
|
|
2683
|
+
_cfg = fallback;
|
|
2684
|
+
_cfgPath = null;
|
|
2685
|
+
return fallback;
|
|
2660
2686
|
}
|
|
2661
|
-
return [{ name: b.name, id: b.id, arguments: b.arguments }];
|
|
2662
|
-
}
|
|
2663
|
-
function extractText(content) {
|
|
2664
|
-
if (typeof content === "string")
|
|
2665
|
-
return content;
|
|
2666
|
-
if (!Array.isArray(content))
|
|
2667
|
-
return "";
|
|
2668
|
-
return content.map((b) => {
|
|
2669
|
-
if (typeof b === "string")
|
|
2670
|
-
return b;
|
|
2671
|
-
if (isTextBlock(b))
|
|
2672
|
-
return b.text;
|
|
2673
|
-
return "";
|
|
2674
|
-
}).join("");
|
|
2675
|
-
}
|
|
2676
|
-
function mediaKind(type, mime) {
|
|
2677
|
-
const s = (type + " " + (mime ?? "")).toLowerCase();
|
|
2678
|
-
if (/image|input_image|image_url/.test(s))
|
|
2679
|
-
return "image";
|
|
2680
|
-
if (/audio/.test(s))
|
|
2681
|
-
return "audio";
|
|
2682
|
-
if (/video/.test(s))
|
|
2683
|
-
return "video";
|
|
2684
|
-
if (/file|document|pdf|attachment/.test(s))
|
|
2685
|
-
return "file";
|
|
2686
|
-
return "unknown";
|
|
2687
2687
|
}
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
const
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2688
|
+
var _pruneInFlight = new Set;
|
|
2689
|
+
function prunePass(dir) {
|
|
2690
|
+
try {
|
|
2691
|
+
const entries = fs2.readdirSync(dir).filter((name) => name.endsWith(".md")).map((name) => {
|
|
2692
|
+
const full = path4.join(dir, name);
|
|
2693
|
+
try {
|
|
2694
|
+
return { full, mtimeMs: fs2.statSync(full).mtimeMs };
|
|
2695
|
+
} catch {
|
|
2696
|
+
return null;
|
|
2697
|
+
}
|
|
2698
|
+
}).filter((v) => v !== null);
|
|
2699
|
+
const now = Date.now();
|
|
2700
|
+
const overAge = entries.filter((e) => now - e.mtimeMs > BACKUP_MAX_AGE_MS);
|
|
2701
|
+
const sorted = entries.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2702
|
+
const overCount = sorted.slice(BACKUP_MAX_FILES);
|
|
2703
|
+
const toRemove = new Set([...overAge, ...overCount].map((e) => e.full));
|
|
2704
|
+
for (const full of toRemove) {
|
|
2705
|
+
try {
|
|
2706
|
+
fs2.unlinkSync(full);
|
|
2707
|
+
} catch (e) {
|
|
2708
|
+
debug("prunePass unlink failed", e);
|
|
2706
2709
|
}
|
|
2707
2710
|
}
|
|
2711
|
+
} catch (e) {
|
|
2712
|
+
debug("prunePass scan failed", e);
|
|
2708
2713
|
}
|
|
2709
|
-
return out;
|
|
2710
2714
|
}
|
|
2711
|
-
function
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
continue;
|
|
2721
|
-
if (b.id) {
|
|
2722
|
-
idx.set(b.id, { name: b.name, arguments: b.arguments, msgIndex: i });
|
|
2723
|
-
}
|
|
2724
|
-
if (b.name === "multi_tool_use.parallel" && Array.isArray(b.arguments?.tool_uses)) {
|
|
2725
|
-
const nested = flattenToolCallBlock(b);
|
|
2726
|
-
for (let t = 0;t < nested.length; t++) {
|
|
2727
|
-
const tool = nested[t];
|
|
2728
|
-
const syntheticId = b.id ? b.id + "_" + t : "mtu_" + i + "_" + t;
|
|
2729
|
-
idx.set(tool.id || syntheticId, { name: tool.name, arguments: tool.arguments, msgIndex: i });
|
|
2730
|
-
}
|
|
2731
|
-
}
|
|
2715
|
+
function schedulePruneBackups(dir) {
|
|
2716
|
+
if (_pruneInFlight.has(dir))
|
|
2717
|
+
return;
|
|
2718
|
+
_pruneInFlight.add(dir);
|
|
2719
|
+
queueMicrotask(() => {
|
|
2720
|
+
try {
|
|
2721
|
+
prunePass(dir);
|
|
2722
|
+
} finally {
|
|
2723
|
+
_pruneInFlight.delete(dir);
|
|
2732
2724
|
}
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
2727
|
+
function backupConversation(convText, sessionId) {
|
|
2728
|
+
try {
|
|
2729
|
+
const cfg = loadConfig();
|
|
2730
|
+
if (!cfg.backupEnabled)
|
|
2731
|
+
return null;
|
|
2732
|
+
const dir = cfg.backupDir;
|
|
2733
|
+
ensureDir(dir);
|
|
2734
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
2735
|
+
const hash = crypto2.createHash("sha256").update(convText).digest("hex").slice(0, TRUNC.CONV_HASH);
|
|
2736
|
+
const fp = path4.join(dir, sessionId + "-" + ts + "-" + hash + ".md");
|
|
2737
|
+
atomicWriteFileSync(fp, `# Smart Compact Backup
|
|
2738
|
+
# Date: ` + new Date().toISOString() + `
|
|
2739
|
+
# Session: ` + sessionId + `
|
|
2740
|
+
|
|
2741
|
+
` + convText);
|
|
2742
|
+
schedulePruneBackups(dir);
|
|
2743
|
+
return fp;
|
|
2744
|
+
} catch (e) {
|
|
2745
|
+
warn("backupConversation failed", e);
|
|
2746
|
+
return null;
|
|
2733
2747
|
}
|
|
2734
|
-
return idx;
|
|
2735
2748
|
}
|
|
2736
|
-
function
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
const
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
}
|
|
2762
|
-
} else if (hasToolHint(tool, DELETE_TOOL_HINTS)) {
|
|
2763
|
-
delSet.add(filePath);
|
|
2764
|
-
} else if (hasToolHint(tool, READ_TOOL_HINTS)) {
|
|
2765
|
-
readSet.add(filePath);
|
|
2749
|
+
function listBackups(limit = 20) {
|
|
2750
|
+
try {
|
|
2751
|
+
const dir = loadConfig().backupDir;
|
|
2752
|
+
if (!fs2.existsSync(dir))
|
|
2753
|
+
return [];
|
|
2754
|
+
const out = [];
|
|
2755
|
+
for (const name of fs2.readdirSync(dir)) {
|
|
2756
|
+
if (!name.endsWith(".md"))
|
|
2757
|
+
continue;
|
|
2758
|
+
const full = path4.join(dir, name);
|
|
2759
|
+
try {
|
|
2760
|
+
const stat = fs2.statSync(full);
|
|
2761
|
+
if (!stat.isFile())
|
|
2762
|
+
continue;
|
|
2763
|
+
const head = fs2.readFileSync(full, "utf-8").split(`
|
|
2764
|
+
`).slice(0, TRUNC.BACKUP_PREVIEW_LINES).join(`
|
|
2765
|
+
`);
|
|
2766
|
+
const date = head.match(/^# Date:\s*(.+)$/m)?.[1]?.trim();
|
|
2767
|
+
const session = head.match(/^# Session:\s*(.+)$/m)?.[1]?.trim();
|
|
2768
|
+
out.push({
|
|
2769
|
+
path: full,
|
|
2770
|
+
sessionId: session ?? name,
|
|
2771
|
+
date: date ?? stat.mtime.toISOString(),
|
|
2772
|
+
sizeBytes: stat.size
|
|
2773
|
+
});
|
|
2774
|
+
} catch {}
|
|
2766
2775
|
}
|
|
2776
|
+
out.sort((a, b) => a.date < b.date ? 1 : a.date > b.date ? -1 : 0);
|
|
2777
|
+
return out.slice(0, limit);
|
|
2778
|
+
} catch (e) {
|
|
2779
|
+
warn("listBackups failed", e);
|
|
2780
|
+
return [];
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
function readBackupContent(fp) {
|
|
2784
|
+
try {
|
|
2785
|
+
const raw = fs2.readFileSync(fp, "utf-8");
|
|
2786
|
+
const lines = raw.split(`
|
|
2787
|
+
`);
|
|
2788
|
+
let i = 0;
|
|
2789
|
+
while (i < lines.length && lines[i].startsWith("#"))
|
|
2790
|
+
i++;
|
|
2791
|
+
if (i < lines.length && lines[i].trim() === "")
|
|
2792
|
+
i++;
|
|
2793
|
+
return lines.slice(i).join(`
|
|
2794
|
+
`).trim() || null;
|
|
2795
|
+
} catch (e) {
|
|
2796
|
+
warn("readBackupContent failed", e);
|
|
2797
|
+
return null;
|
|
2767
2798
|
}
|
|
2799
|
+
}
|
|
2800
|
+
function buildRestoreMessage(content, source) {
|
|
2768
2801
|
return {
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2802
|
+
customType: "smart-compact-restore",
|
|
2803
|
+
content: `# Restored pre-compaction context (smart-compact backup)
|
|
2804
|
+
Source: ` + source + `
|
|
2805
|
+
|
|
2806
|
+
` + content,
|
|
2807
|
+
display: true,
|
|
2808
|
+
details: { source, restoredAt: Date.now() }
|
|
2772
2809
|
};
|
|
2773
2810
|
}
|
|
2774
|
-
function
|
|
2775
|
-
const
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2811
|
+
function getPreviousCompactionContext(branch) {
|
|
2812
|
+
const compactions = branch.filter((e) => e.type === "compaction");
|
|
2813
|
+
if (!compactions.length)
|
|
2814
|
+
return "";
|
|
2815
|
+
const last = compactions[compactions.length - 1];
|
|
2816
|
+
const topics = last.details?.topics ?? [];
|
|
2817
|
+
if (!topics.length)
|
|
2818
|
+
return "";
|
|
2819
|
+
return `
|
|
2820
|
+
[IMPORTANT: Previous compaction exists (` + (last.details?.method ?? "unknown") + "). Already summarized topics: " + topics.join(", ") + ". Build upon this, don't re-summarize the same content.]";
|
|
2821
|
+
}
|
|
2822
|
+
function findLastAnchorIndex(branchEntries) {
|
|
2823
|
+
for (let i = branchEntries.length - 1;i >= 0; i--) {
|
|
2824
|
+
const e = branchEntries[i];
|
|
2825
|
+
if (e?.type !== "message")
|
|
2780
2826
|
continue;
|
|
2781
|
-
const
|
|
2782
|
-
if (
|
|
2783
|
-
errors.push({ index: i, tool: tc?.name ?? "unknown", message: extractText(m.content).slice(0, 500), retryAttempted: false, resolved: false });
|
|
2827
|
+
const msg = e.message;
|
|
2828
|
+
if (msg?.role !== "toolResult")
|
|
2784
2829
|
continue;
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
const txt = extractText(m.content);
|
|
2788
|
-
const isLikelyError = /(?:command not found|no such file|permission denied|syntax error|cannot find|module not found|compilation error|build failed|test failed|^FAIL\b|ERROR:)/i.test(txt);
|
|
2789
|
-
if (isLikelyError && txt.length < 2000) {
|
|
2790
|
-
errors.push({ index: i, tool: "bash", message: txt.slice(0, 300), retryAttempted: false, resolved: false });
|
|
2791
|
-
}
|
|
2830
|
+
if (msg?.toolName === "context" && msg?.details?.anchor) {
|
|
2831
|
+
return i;
|
|
2792
2832
|
}
|
|
2793
2833
|
}
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
if (msgs[k]?.role === "toolResult" && msgs[k]?.toolCallId === tool.id && !msgs[k]?.isError) {
|
|
2805
|
-
err.resolved = true;
|
|
2806
|
-
break;
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
break;
|
|
2810
|
-
}
|
|
2811
|
-
}
|
|
2812
|
-
if (err.retryAttempted)
|
|
2813
|
-
break;
|
|
2814
|
-
}
|
|
2815
|
-
if (err.retryAttempted)
|
|
2816
|
-
break;
|
|
2817
|
-
}
|
|
2834
|
+
return -1;
|
|
2835
|
+
}
|
|
2836
|
+
function branchIndexToMsgIndex(branchEntries, branchIdx, msgs) {
|
|
2837
|
+
let msgCount = 0;
|
|
2838
|
+
for (let i = 0;i <= branchIdx && i < branchEntries.length; i++) {
|
|
2839
|
+
const e = branchEntries[i];
|
|
2840
|
+
if (e?.type === "message") {
|
|
2841
|
+
if (msgCount >= msgs.length)
|
|
2842
|
+
return msgs.length - 1;
|
|
2843
|
+
msgCount++;
|
|
2818
2844
|
}
|
|
2819
2845
|
}
|
|
2820
|
-
return
|
|
2846
|
+
return Math.max(0, Math.min(msgCount - 1, msgs.length - 1));
|
|
2821
2847
|
}
|
|
2822
|
-
function
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
if (
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
if (!question)
|
|
2831
|
-
continue;
|
|
2832
|
-
for (let i = tc.msgIndex + 1;i < Math.min(msgs.length, tc.msgIndex + 4); i++) {
|
|
2833
|
-
if (msgs[i]?.role === "toolResult" && msgs[i]?.toolCallId === id) {
|
|
2834
|
-
decisions.push({ index: tc.msgIndex, type: "explicit", summary: question.slice(0, 200), userResponse: extractText(msgs[i].content).slice(0, 300) });
|
|
2835
|
-
break;
|
|
2848
|
+
function smartKeepBoundary(msgs, keepFromIndex, branchEntries) {
|
|
2849
|
+
let adjusted = keepFromIndex;
|
|
2850
|
+
if (branchEntries && branchEntries.length > 0) {
|
|
2851
|
+
const lastAnchorBranchIdx = findLastAnchorIndex(branchEntries);
|
|
2852
|
+
if (lastAnchorBranchIdx >= 0) {
|
|
2853
|
+
const lastAnchorMsgIdx = branchIndexToMsgIndex(branchEntries, lastAnchorBranchIdx, msgs);
|
|
2854
|
+
if (adjusted > lastAnchorMsgIdx && lastAnchorMsgIdx >= 0) {
|
|
2855
|
+
adjusted = lastAnchorMsgIdx;
|
|
2836
2856
|
}
|
|
2837
2857
|
}
|
|
2838
2858
|
}
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
const
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
{ re: /\b(?:must|need|require|has to|important)\b.*\b(?:be|use|have|include|support)\b/i, cat: "requirement", conf: 0.85 },
|
|
2851
|
-
{ re: /\b(?:don't|never|avoid|shouldn't|must not|do not|no\s+(?:need|want))\b/i, cat: "prohibition", conf: 0.8 },
|
|
2852
|
-
{ re: /\b(?:prefer|like|want|would rather|should)\b.*\b(?:use|be|have|with)\b/i, cat: "preference", conf: 0.6 },
|
|
2853
|
-
{ re: /\b(?:kritik|kritikal|\u00f6nemli|onemli|\u015fart|sart|zorunlu|\u015fart ko\u015ful|\u00f6nemli \u015fart|kesinlikle|kesinlikle \u015fart|asla|sak\u0131n|sak\u0131nha|bunu yapma|b\u00f6yle olsun|b\u00f6yle yap\u0131n|\u015f\u00f6yle olsun|\u015f\u00f6yle yap\u0131n)\b/iu, cat: "requirement", conf: 0.8 },
|
|
2854
|
-
{ re: /\b(?:yapma|kullanma|sak\u0131n|asla\s+(?:kullanma|yapma|getirme))\b/iu, cat: "prohibition", conf: 0.8 },
|
|
2855
|
-
{ re: /\b(?:tercih|isterim|olsun|kullanal\u0131m|yapal\u0131m|istiyorum)\b/iu, cat: "preference", conf: 0.6 }
|
|
2856
|
-
];
|
|
2857
|
-
function mineConstraints(msgs) {
|
|
2858
|
-
const constraints = [];
|
|
2859
|
-
for (let i = 0;i < msgs.length; i++) {
|
|
2860
|
-
if (msgs[i]?.role !== "user")
|
|
2861
|
-
continue;
|
|
2862
|
-
const txt = extractText(msgs[i].content);
|
|
2863
|
-
if (txt.length < 10 || txt.startsWith("/"))
|
|
2864
|
-
continue;
|
|
2865
|
-
for (const { re, cat, conf } of CONSTRAINT_PATTERNS) {
|
|
2866
|
-
if (re.test(txt)) {
|
|
2867
|
-
constraints.push({ index: i, text: txt.slice(0, 300), category: cat, confidence: conf });
|
|
2868
|
-
break;
|
|
2859
|
+
if (adjusted <= 0 || adjusted >= msgs.length)
|
|
2860
|
+
return adjusted;
|
|
2861
|
+
const touchedFiles = (msg) => {
|
|
2862
|
+
const m = msg;
|
|
2863
|
+
const blocks = Array.isArray(m?.content) ? m.content : [];
|
|
2864
|
+
const files = new Set;
|
|
2865
|
+
for (const b of blocks) {
|
|
2866
|
+
for (const tc of flattenToolCallBlock(b)) {
|
|
2867
|
+
const fp = extractToolPath(tc.arguments);
|
|
2868
|
+
if (fp)
|
|
2869
|
+
files.add(fp.split("/").pop() ?? fp);
|
|
2869
2870
|
}
|
|
2870
2871
|
}
|
|
2872
|
+
return files;
|
|
2873
|
+
};
|
|
2874
|
+
const lastFiles = touchedFiles(msgs[adjusted - 1].message);
|
|
2875
|
+
if (lastFiles.size > 0) {
|
|
2876
|
+
const keptFiles = touchedFiles(msgs[adjusted].message);
|
|
2877
|
+
if ([...lastFiles].some((f) => keptFiles.has(f)))
|
|
2878
|
+
return adjusted - 1;
|
|
2871
2879
|
}
|
|
2872
|
-
return
|
|
2880
|
+
return adjusted;
|
|
2873
2881
|
}
|
|
2874
|
-
function
|
|
2875
|
-
const
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
if (m.role === "assistant") {
|
|
2888
|
-
const blocks = Array.isArray(m.content) ? m.content : [];
|
|
2889
|
-
for (const b of blocks) {
|
|
2890
|
-
for (const tool of flattenToolCallBlock(b)) {
|
|
2891
|
-
const fp = tool.arguments?.path ?? tool.arguments?.file_path;
|
|
2892
|
-
if (!fp)
|
|
2893
|
-
continue;
|
|
2894
|
-
const fn = path6.basename(fp);
|
|
2895
|
-
if (lastFile && fn !== lastFile && tokenAcc > pc.minChunkTokens)
|
|
2896
|
-
brk = true;
|
|
2897
|
-
lastFile = fn;
|
|
2898
|
-
primaryFile = fp;
|
|
2899
|
-
currentPrimaryFile = fp;
|
|
2900
|
-
if (tool.name?.includes("write") || tool.name?.includes("edit")) {
|
|
2901
|
-
type = "implementation";
|
|
2902
|
-
if (currentType !== "implementation")
|
|
2903
|
-
currentType = "implementation";
|
|
2904
|
-
} else if (tool.name?.includes("read")) {
|
|
2905
|
-
type = "review";
|
|
2906
|
-
if (currentType === "exploration")
|
|
2907
|
-
currentType = "review";
|
|
2882
|
+
function collectToolCallIds(blocks, msgIndex, out) {
|
|
2883
|
+
for (const b of blocks) {
|
|
2884
|
+
const block = b;
|
|
2885
|
+
if (block?.type === "toolCall") {
|
|
2886
|
+
if (typeof block.id === "string") {
|
|
2887
|
+
out.set(block.id, msgIndex);
|
|
2888
|
+
}
|
|
2889
|
+
const args = block.arguments;
|
|
2890
|
+
if (block.name === "multi_tool_use.parallel" && args && Array.isArray(args.tool_uses)) {
|
|
2891
|
+
for (const nested of args.tool_uses) {
|
|
2892
|
+
const n = nested;
|
|
2893
|
+
if (typeof n.id === "string") {
|
|
2894
|
+
out.set(n.id, msgIndex);
|
|
2908
2895
|
}
|
|
2909
2896
|
}
|
|
2910
2897
|
}
|
|
2911
2898
|
}
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
function guardToolCallBoundary(msgs, keepFrom) {
|
|
2902
|
+
if (keepFrom <= 0 || keepFrom >= msgs.length)
|
|
2903
|
+
return keepFrom;
|
|
2904
|
+
const tcMap = new Map;
|
|
2905
|
+
for (let i = 0;i < msgs.length; i++) {
|
|
2906
|
+
const m = msgs[i].message;
|
|
2907
|
+
if (m?.role !== "assistant")
|
|
2908
|
+
continue;
|
|
2909
|
+
const blocks = Array.isArray(m?.content) ? m.content : [];
|
|
2910
|
+
collectToolCallIds(blocks, i, tcMap);
|
|
2911
|
+
}
|
|
2912
|
+
let adjusted = keepFrom;
|
|
2913
|
+
let changed = true;
|
|
2914
|
+
const MAX_ITER = msgs.length + 1;
|
|
2915
|
+
let iter = 0;
|
|
2916
|
+
while (changed) {
|
|
2917
|
+
if (++iter > MAX_ITER) {
|
|
2918
|
+
warn("guardToolCallBoundary hit MAX_ITER=" + MAX_ITER + " at adjusted=" + adjusted);
|
|
2919
|
+
break;
|
|
2917
2920
|
}
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2921
|
+
changed = false;
|
|
2922
|
+
for (let i = adjusted;i < msgs.length; i++) {
|
|
2923
|
+
const m = msgs[i].message;
|
|
2924
|
+
if (m?.role !== "toolResult")
|
|
2925
|
+
continue;
|
|
2926
|
+
const tcId = m?.toolCallId;
|
|
2927
|
+
if (!tcId)
|
|
2928
|
+
continue;
|
|
2929
|
+
const tcIdx = tcMap.get(tcId);
|
|
2930
|
+
if (tcIdx !== undefined && tcIdx < adjusted) {
|
|
2931
|
+
adjusted = tcIdx;
|
|
2932
|
+
changed = true;
|
|
2933
|
+
break;
|
|
2925
2934
|
}
|
|
2926
2935
|
}
|
|
2927
|
-
if (m.role === "user" && SHIFT_RE.test(txt) && tokenAcc > pc.minChunkTokens)
|
|
2928
|
-
brk = true;
|
|
2929
|
-
if (tokenAcc >= pc.maxChunkTokens)
|
|
2930
|
-
brk = true;
|
|
2931
|
-
if (brk && i > startIdx && topics.length < maxSegs - 1) {
|
|
2932
|
-
topics.push({ startIndex: startIdx, endIndex: i, primaryFile: currentPrimaryFile, type: currentType, errorDensity: errAcc });
|
|
2933
|
-
startIdx = i + 1;
|
|
2934
|
-
tokenAcc = 0;
|
|
2935
|
-
lastFile = null;
|
|
2936
|
-
errAcc = 0;
|
|
2937
|
-
currentType = "exploration";
|
|
2938
|
-
currentPrimaryFile = null;
|
|
2939
|
-
}
|
|
2940
2936
|
}
|
|
2941
|
-
|
|
2942
|
-
topics.push({ startIndex: startIdx, endIndex: msgs.length - 1, primaryFile: currentPrimaryFile, type: currentType, errorDensity: errAcc });
|
|
2943
|
-
}
|
|
2944
|
-
return topics;
|
|
2937
|
+
return Math.max(0, Math.min(adjusted, msgs.length));
|
|
2945
2938
|
}
|
|
2946
|
-
function
|
|
2947
|
-
const
|
|
2948
|
-
const
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2939
|
+
function extractUserNote(args) {
|
|
2940
|
+
const SKIP = new Set(["verbose", "debug", "dry-run", "light", "balanced", "aggressive"]);
|
|
2941
|
+
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
2942
|
+
const isOptionToken = (t) => t.startsWith("--") || SKIP.has(t.toLowerCase()) || /^[a-z0-9_.-]+\/[a-z0-9_.:-]+$/i.test(t);
|
|
2943
|
+
const nonFlags = tokens.filter((t) => !isOptionToken(t));
|
|
2944
|
+
return nonFlags.length > 0 ? nonFlags.join(" ") : undefined;
|
|
2945
|
+
}
|
|
2946
|
+
function createBatches(chunks, maxTokens) {
|
|
2947
|
+
const batches = [];
|
|
2948
|
+
let batch = [], bt = 0;
|
|
2949
|
+
for (const ch of chunks) {
|
|
2950
|
+
if (batch.length && bt + ch.tokenEstimate > maxTokens) {
|
|
2951
|
+
batches.push(batch);
|
|
2952
|
+
batch = [];
|
|
2953
|
+
bt = 0;
|
|
2955
2954
|
}
|
|
2956
|
-
|
|
2957
|
-
|
|
2955
|
+
batch.push(ch);
|
|
2956
|
+
bt += ch.tokenEstimate;
|
|
2957
|
+
}
|
|
2958
|
+
if (batch.length)
|
|
2959
|
+
batches.push(batch);
|
|
2960
|
+
return batches;
|
|
2961
|
+
}
|
|
2962
|
+
function allocateTopicBudgets(summaries, totalBudget) {
|
|
2963
|
+
const n = summaries.length;
|
|
2964
|
+
if (n === 0)
|
|
2965
|
+
return new Map;
|
|
2966
|
+
const weights = summaries.map((s, i) => {
|
|
2967
|
+
let w = 1;
|
|
2968
|
+
if (s.priority === "critical")
|
|
2969
|
+
w *= 2;
|
|
2970
|
+
else if (s.priority === "high")
|
|
2971
|
+
w *= 1.5;
|
|
2972
|
+
else if (s.priority === "low")
|
|
2973
|
+
w *= 0.6;
|
|
2974
|
+
const errorKeywords = (s.summary.match(/error|fail|bug|fix|crash|exception/gi) ?? []).length;
|
|
2975
|
+
w *= 1 + errorKeywords * 0.2;
|
|
2976
|
+
const recency = (i + 1) / n;
|
|
2977
|
+
w *= 0.6 + recency * 0.4;
|
|
2978
|
+
if (s.keyDecisions.length > 0)
|
|
2979
|
+
w *= 1.3;
|
|
2980
|
+
return w;
|
|
2981
|
+
});
|
|
2982
|
+
const totalWeight = weights.reduce((a, b) => a + b, 0);
|
|
2983
|
+
const baseTokensPerTopic = Math.floor(totalBudget / n);
|
|
2984
|
+
const budgetMap = new Map;
|
|
2985
|
+
for (let i = 0;i < summaries.length; i++) {
|
|
2986
|
+
const allocated = Math.round(baseTokensPerTopic * (weights[i] / (totalWeight / n)));
|
|
2987
|
+
budgetMap.set(summaries[i].topic, Math.max(200, allocated));
|
|
2958
2988
|
}
|
|
2959
|
-
return
|
|
2989
|
+
return budgetMap;
|
|
2990
|
+
}
|
|
2991
|
+
function preProcessSummaries(summaries, budgetTokens) {
|
|
2992
|
+
const topicBudgets = budgetTokens ? allocateTopicBudgets(summaries, budgetTokens) : null;
|
|
2993
|
+
return {
|
|
2994
|
+
decisions: [...new Set(summaries.flatMap((s) => s.keyDecisions))],
|
|
2995
|
+
modified: [...new Set(summaries.flatMap((s) => s.filesModified))].sort(),
|
|
2996
|
+
read: [...new Set(summaries.flatMap((s) => s.filesRead))].sort(),
|
|
2997
|
+
text: summaries.map((cs, i) => {
|
|
2998
|
+
const budgetHint = topicBudgets?.get(cs.topic);
|
|
2999
|
+
const budgetLine = budgetHint ? `
|
|
3000
|
+
Budget: ~` + budgetHint + " tokens" : "";
|
|
3001
|
+
return "### Segment " + (i + 1) + ": " + cs.topic + `
|
|
3002
|
+
Priority: ` + cs.priority + " | msgs " + cs.startIndex + "-" + cs.endIndex + budgetLine + `
|
|
3003
|
+
|
|
3004
|
+
` + cs.summary + `
|
|
3005
|
+
|
|
3006
|
+
Decisions: ` + (cs.keyDecisions.join("; ") || "None") + `
|
|
3007
|
+
Modified: ` + (cs.filesModified.join(", ") || "None") + `
|
|
3008
|
+
Read: ` + (cs.filesRead.join(", ") || "None");
|
|
3009
|
+
}).join(`
|
|
3010
|
+
---
|
|
3011
|
+
`)
|
|
3012
|
+
};
|
|
2960
3013
|
}
|
|
2961
|
-
function
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
3014
|
+
function buildExtractionContext(extraction, forRange) {
|
|
3015
|
+
const files = forRange ? extraction.modifiedFiles.filter((f) => f.lastModifiedIndex >= forRange.start && f.lastModifiedIndex <= forRange.end) : extraction.modifiedFiles;
|
|
3016
|
+
const errors = forRange ? extraction.errors.filter((e) => e.index >= forRange.start && e.index <= forRange.end) : extraction.errors;
|
|
3017
|
+
const media = forRange ? (extraction.mediaAttachments ?? []).filter((a) => a.index >= forRange.start && a.index <= forRange.end) : extraction.mediaAttachments ?? [];
|
|
3018
|
+
return [
|
|
3019
|
+
"## Deterministic Extraction (verified facts)",
|
|
3020
|
+
"Files modified: " + (files.map((f) => f.path).join(", ") || "none"),
|
|
3021
|
+
"Errors: " + (errors.map((e) => "[" + e.tool + "] " + e.message.slice(0, TRUNC.SNIPPET) + (e.resolved ? " \u2713" : "")).join("; ") || "none"),
|
|
3022
|
+
"Decisions: " + (extraction.decisions.map((d) => d.type + ": " + d.summary.slice(0, TRUNC.DECISION_DETAIL)).join("; ") || "none"),
|
|
3023
|
+
"Constraints: " + (extraction.constraints.map((c) => "[" + c.category + "] " + c.text.slice(0, TRUNC.DECISION_DETAIL)).join("; ") || "none"),
|
|
3024
|
+
"Media attachments: " + (media.map((a) => a.kind + (a.name ? ":" + a.name : "") + (a.mimeType ? " (" + a.mimeType + ")" : "") + " @msg" + a.index).join("; ") || "none")
|
|
3025
|
+
].join(`
|
|
3026
|
+
`);
|
|
2970
3027
|
}
|
|
2971
|
-
function
|
|
2972
|
-
|
|
2973
|
-
let
|
|
2974
|
-
const
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
}));
|
|
2978
|
-
for (const err of extraction.errors.filter((e) => !e.resolved)) {
|
|
2979
|
-
const errLower = err.message.toLowerCase();
|
|
2980
|
-
const errFiles = fileNeedles.filter(({ needles }) => needles.some((n) => errLower.includes(n))).map(({ path: path7 }) => path7);
|
|
2981
|
-
loops.push({
|
|
2982
|
-
id: "loop-" + ++loopId,
|
|
2983
|
-
type: "bugfix",
|
|
2984
|
-
priority: err.retryAttempted ? "high" : "normal",
|
|
2985
|
-
status: "open",
|
|
2986
|
-
summary: err.message.slice(0, 120),
|
|
2987
|
-
files: errFiles,
|
|
2988
|
-
sourceIndex: err.index
|
|
2989
|
-
});
|
|
2990
|
-
}
|
|
2991
|
-
const FOLLOWUP_RE = /(?:next\s+(?:step|thing)|todo|action item|follow\s*up|still (?:need|have) to|gotta|yapalim|yapal\u0131m|yapmamiz|yapmam\u0131z|gerekiyor|eklenecek|d\u00FCzeltilecek|duzeltilecek|bitmedi|kaldi|kald\u0131)/iu;
|
|
2992
|
-
for (let idx = 0;idx < msgs.length; idx++) {
|
|
2993
|
-
const msg = msgs[idx];
|
|
2994
|
-
if (msg.role !== "user")
|
|
2995
|
-
continue;
|
|
2996
|
-
const txt = extractText(msg.content);
|
|
2997
|
-
if (txt.length < 10 || txt.startsWith("/"))
|
|
3028
|
+
function computeToolCharPercentage(branchEntries) {
|
|
3029
|
+
let totalChars = 0;
|
|
3030
|
+
let toolChars = 0;
|
|
3031
|
+
for (const raw of branchEntries) {
|
|
3032
|
+
const m = raw?.message;
|
|
3033
|
+
if (!m)
|
|
2998
3034
|
continue;
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
sourceIndex: idx
|
|
3010
|
-
});
|
|
3035
|
+
let mc = 0;
|
|
3036
|
+
if (typeof m.content === "string") {
|
|
3037
|
+
mc = m.content.length;
|
|
3038
|
+
} else if (Array.isArray(m.content)) {
|
|
3039
|
+
for (const part of m.content) {
|
|
3040
|
+
if (!part || typeof part !== "object")
|
|
3041
|
+
continue;
|
|
3042
|
+
const block = part;
|
|
3043
|
+
if (block.type === "text" && typeof block.text === "string")
|
|
3044
|
+
mc += block.text.length;
|
|
3011
3045
|
}
|
|
3012
3046
|
}
|
|
3047
|
+
totalChars += mc;
|
|
3048
|
+
if (m.role === "toolResult")
|
|
3049
|
+
toolChars += mc;
|
|
3013
3050
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3051
|
+
return totalChars > 0 ? Math.round(toolChars / totalChars * 100) : 0;
|
|
3052
|
+
}
|
|
3053
|
+
function selectCompactionTier(contextPercent, toolPercent, totalTokens, minThreshold, minContextPercent = 60) {
|
|
3054
|
+
if (totalTokens < minThreshold)
|
|
3055
|
+
return "none";
|
|
3056
|
+
if (contextPercent < minContextPercent)
|
|
3057
|
+
return "none";
|
|
3058
|
+
if (contextPercent < 80)
|
|
3059
|
+
return "light";
|
|
3060
|
+
return "full";
|
|
3061
|
+
}
|
|
3062
|
+
function inferSessionType(extraction, report) {
|
|
3063
|
+
if (report?.sessionType)
|
|
3064
|
+
return report.sessionType;
|
|
3065
|
+
const hasModifications = extraction.modifiedFiles.length > 0;
|
|
3066
|
+
const hasUnresolvedErrors = extraction.errors.some((e) => !e.resolved);
|
|
3067
|
+
const hasResolvedErrors = extraction.errors.some((e) => e.resolved);
|
|
3068
|
+
const hasReadsOnly = extraction.readFiles.length > 2 && !hasModifications;
|
|
3069
|
+
const hasDecisions = extraction.decisions.length > 0;
|
|
3070
|
+
if (hasUnresolvedErrors && (hasModifications || hasResolvedErrors))
|
|
3071
|
+
return "debugging";
|
|
3072
|
+
if (hasReadsOnly && !hasDecisions)
|
|
3073
|
+
return "review";
|
|
3074
|
+
if (hasDecisions && !hasModifications && !hasUnresolvedErrors)
|
|
3075
|
+
return "discussion";
|
|
3076
|
+
if (hasModifications)
|
|
3077
|
+
return "implementation";
|
|
3078
|
+
return "implementation";
|
|
3079
|
+
}
|
|
3080
|
+
function buildExplorationContext(report) {
|
|
3081
|
+
if (!report.mainGoal && !report.crossReferences.length && !report.enrichedConstraints.length)
|
|
3082
|
+
return "";
|
|
3083
|
+
return [
|
|
3084
|
+
"## Exploration Report",
|
|
3085
|
+
"Main goal: " + report.mainGoal,
|
|
3086
|
+
"Session type: " + report.sessionType,
|
|
3087
|
+
report.crossReferences.length ? "Cross-references: " + report.crossReferences.join("; ") : "",
|
|
3088
|
+
report.enrichedConstraints.length ? "Enriched constraints: " + report.enrichedConstraints.join("; ") : "",
|
|
3089
|
+
report.statusAssessment.done.length ? "Assessed done: " + report.statusAssessment.done.join("; ") : "",
|
|
3090
|
+
report.statusAssessment.inProgress.length ? "Assessed in-progress: " + report.statusAssessment.inProgress.join("; ") : "",
|
|
3091
|
+
report.criticalContext.length ? "Critical context: " + report.criticalContext.join("; ") : ""
|
|
3092
|
+
].filter(Boolean).join(`
|
|
3093
|
+
`);
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
// src/index.ts
|
|
3097
|
+
init_tokens();
|
|
3098
|
+
init_cache();
|
|
3099
|
+
|
|
3100
|
+
// src/app/run-smart-compact.ts
|
|
3101
|
+
init_overlays();
|
|
3102
|
+
init_logger();
|
|
3103
|
+
|
|
3104
|
+
// src/app/run-context.ts
|
|
3105
|
+
function markMeasuredPhase(rc, phase, startMs, endMs = Date.now()) {
|
|
3106
|
+
rc.phaseTimings.push({ phase, durationMs: Math.max(0, endMs - startMs) });
|
|
3107
|
+
rc.phaseStart = endMs;
|
|
3108
|
+
}
|
|
3109
|
+
function markPhase(rc, phase) {
|
|
3110
|
+
markMeasuredPhase(rc, phase, rc.phaseStart);
|
|
3111
|
+
}
|
|
3112
|
+
function advance(rc, _stage) {
|
|
3113
|
+
return rc;
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
// src/app/run-smart-compact.ts
|
|
3117
|
+
init_services();
|
|
3118
|
+
|
|
3119
|
+
// src/app/steps/prepare.ts
|
|
3120
|
+
init_constants();
|
|
3121
|
+
init_tokens();
|
|
3122
|
+
init_logger();
|
|
3123
|
+
async function prepareRun(rc) {
|
|
3124
|
+
const config = loadConfig();
|
|
3125
|
+
const profileCfg = { ...PROFILES[rc.profile], ...config.profiles?.[rc.profile] ?? {} };
|
|
3126
|
+
const providerCaps = getProviderCaps(rc.summaryModel.provider);
|
|
3127
|
+
const auth = await rc.ctx.modelRegistry.getApiKeyAndHeaders(rc.summaryModel);
|
|
3128
|
+
const segAuth = rc.segModel !== rc.summaryModel ? await rc.ctx.modelRegistry.getApiKeyAndHeaders(rc.segModel) : auth;
|
|
3129
|
+
if (!auth.ok || !auth.apiKey || (!segAuth.ok || !segAuth.apiKey)) {
|
|
3130
|
+
if (!rc.flags.autoTriggered)
|
|
3131
|
+
rc.ctx.ui.notify("Auth failed", "error");
|
|
3132
|
+
return null;
|
|
3034
3133
|
}
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
priority: "high",
|
|
3042
|
-
status: "open",
|
|
3043
|
-
summary: "Retried but unresolved: " + err.message.slice(0, 80),
|
|
3044
|
-
files: [],
|
|
3045
|
-
sourceIndex: err.index
|
|
3046
|
-
});
|
|
3047
|
-
}
|
|
3134
|
+
if (rc.timeoutMs > 0) {
|
|
3135
|
+
rc.cancellation.timeoutId = setTimeout(() => {
|
|
3136
|
+
rc.cancellation.timedOut = true;
|
|
3137
|
+
rc.cancellation.controller.abort();
|
|
3138
|
+
rc.notify("Smart compact auto-trigger exceeded " + rc.timeoutMs + "ms; Pi will use native compact for this run", "warning");
|
|
3139
|
+
}, rc.timeoutMs);
|
|
3048
3140
|
}
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
const topics = segmentTopicsHeuristic(msgs, pc, 20, tcIdx);
|
|
3058
|
-
const timeline = buildTimeline(msgs, errors);
|
|
3059
|
-
const mediaAttachments = extractMediaAttachments(msgs);
|
|
3060
|
-
const mainGoal = extractMainGoal(msgs);
|
|
3061
|
-
const lastUserMessages = msgs.filter((m) => m.role === "user").slice(-5).map((m) => extractText(m.content));
|
|
3062
|
-
const lastErrors = errors.slice(-3).map((e) => e.message);
|
|
3063
|
-
return {
|
|
3064
|
-
modifiedFiles: modified,
|
|
3065
|
-
readFiles: read,
|
|
3066
|
-
deletedFiles: deleted,
|
|
3067
|
-
errors,
|
|
3068
|
-
decisions,
|
|
3069
|
-
constraints,
|
|
3070
|
-
topics,
|
|
3071
|
-
timeline,
|
|
3072
|
-
mediaAttachments,
|
|
3073
|
-
mainGoal,
|
|
3074
|
-
lastUserMessages,
|
|
3075
|
-
lastErrors,
|
|
3076
|
-
messageCount: msgs.length
|
|
3077
|
-
};
|
|
3141
|
+
debug("prepareRun: profile=" + rc.profile + " model=" + rc.modelLabel);
|
|
3142
|
+
const out = rc;
|
|
3143
|
+
out.config = config;
|
|
3144
|
+
out.profileCfg = profileCfg;
|
|
3145
|
+
out.providerCaps = providerCaps;
|
|
3146
|
+
out.summaryAuth = { apiKey: auth.apiKey, headers: auth.headers };
|
|
3147
|
+
out.segAuth = { apiKey: segAuth.apiKey, headers: segAuth.headers };
|
|
3148
|
+
return advance(out, "_prepared");
|
|
3078
3149
|
}
|
|
3079
3150
|
|
|
3151
|
+
// src/app/steps/window.ts
|
|
3152
|
+
init_tokens();
|
|
3153
|
+
|
|
3080
3154
|
// src/infra/session-identity.ts
|
|
3081
3155
|
import { randomUUID } from "crypto";
|
|
3082
3156
|
var UNRESOLVED_PREFIX = "unresolved:";
|
|
@@ -3405,9 +3479,9 @@ function pruneRedundant(msgs, precomputedTcIdx) {
|
|
|
3405
3479
|
if (msgs[i2].role !== "toolResult")
|
|
3406
3480
|
continue;
|
|
3407
3481
|
const tc = tcIdx.get(msgs[i2].toolCallId ?? "");
|
|
3408
|
-
if (!tc || tc.
|
|
3482
|
+
if (!tc || classifyTool(tc.arguments) !== "accesses")
|
|
3409
3483
|
continue;
|
|
3410
|
-
const fp = tc
|
|
3484
|
+
const fp = extractToolPath(tc.arguments);
|
|
3411
3485
|
if (!fp)
|
|
3412
3486
|
continue;
|
|
3413
3487
|
const arr = readIndices.get(fp) ?? [];
|
|
@@ -3486,7 +3560,8 @@ function pruneRedundant(msgs, precomputedTcIdx) {
|
|
|
3486
3560
|
originalTokens += estimateTokens(text);
|
|
3487
3561
|
if (!keep.has(idx))
|
|
3488
3562
|
continue;
|
|
3489
|
-
|
|
3563
|
+
const protectFromTruncation = m.role === "toolResult" && text.length > MAX_TOOL_OUTPUT_CHARS && text.length < ERROR_SCAN_MAX_LEN && LIKELY_ERROR_RE.test(text);
|
|
3564
|
+
if (m.role === "toolResult" && text.length > MAX_TOOL_OUTPUT_CHARS && !protectFromTruncation) {
|
|
3490
3565
|
const head = text.slice(0, half);
|
|
3491
3566
|
const tail = text.slice(-half);
|
|
3492
3567
|
const truncated = head + `
|
|
@@ -3521,7 +3596,7 @@ init_logger();
|
|
|
3521
3596
|
init_paths();
|
|
3522
3597
|
init_fs();
|
|
3523
3598
|
import path8 from "path";
|
|
3524
|
-
import
|
|
3599
|
+
import crypto5 from "crypto";
|
|
3525
3600
|
|
|
3526
3601
|
// src/infra/git.ts
|
|
3527
3602
|
init_logger();
|
|
@@ -3545,6 +3620,7 @@ function findGitRoot(cwd) {
|
|
|
3545
3620
|
}
|
|
3546
3621
|
|
|
3547
3622
|
// src/utils/fingerprint.ts
|
|
3623
|
+
init_constants();
|
|
3548
3624
|
var LANG_MAP = {
|
|
3549
3625
|
".ts": "typescript",
|
|
3550
3626
|
".tsx": "typescript",
|
|
@@ -3584,7 +3660,7 @@ function isProjectPath(filePath) {
|
|
|
3584
3660
|
return !NOISE_PATH_RE.test(filePath);
|
|
3585
3661
|
}
|
|
3586
3662
|
function hashProjectId(seed) {
|
|
3587
|
-
return
|
|
3663
|
+
return ID_PREFIX.PROJECT + crypto5.createHash("sha256").update(seed).digest("hex").slice(0, TRUNC.PROJ_ID_HASH);
|
|
3588
3664
|
}
|
|
3589
3665
|
function findGitRoot2(cwd) {
|
|
3590
3666
|
return findGitRoot(cwd);
|
|
@@ -3625,11 +3701,11 @@ function deriveFromRelativePaths(paths) {
|
|
|
3625
3701
|
for (const p of paths) {
|
|
3626
3702
|
const segs = p.split("/").filter(Boolean);
|
|
3627
3703
|
if (segs.length >= 2) {
|
|
3628
|
-
const d2 = segs.slice(0,
|
|
3704
|
+
const d2 = segs.slice(0, TRUNC.FINGERPRINT_SEG).join("/");
|
|
3629
3705
|
dir2Counts.set(d2, (dir2Counts.get(d2) ?? 0) + 1);
|
|
3630
3706
|
}
|
|
3631
3707
|
}
|
|
3632
|
-
const stableDirs = [...dir2Counts.entries()].sort((a, b) => b[1] - a[1]).slice(0,
|
|
3708
|
+
const stableDirs = [...dir2Counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, TRUNC.CONV_HASH).map(([d]) => d).sort();
|
|
3633
3709
|
return hashProjectId(topEntries.join(",") + "|" + stableDirs.join(","));
|
|
3634
3710
|
}
|
|
3635
3711
|
function deriveProjectId(cwd, extraction, sessionId) {
|
|
@@ -3697,7 +3773,7 @@ function loadProjectFingerprint(projectId) {
|
|
|
3697
3773
|
const data = readJsonSync(getFingerprintPath(projectId));
|
|
3698
3774
|
if (!data)
|
|
3699
3775
|
return null;
|
|
3700
|
-
if (Date.now() - data.updatedAt >
|
|
3776
|
+
if (Date.now() - data.updatedAt > THIRTY_DAYS_MS)
|
|
3701
3777
|
return null;
|
|
3702
3778
|
return data;
|
|
3703
3779
|
}
|
|
@@ -3883,7 +3959,7 @@ function executeExplorationTool(call, llmMessages) {
|
|
|
3883
3959
|
return JSON.stringify(llmMessages.slice(s, e).map((m, i) => ({
|
|
3884
3960
|
idx: s + i,
|
|
3885
3961
|
role: m?.role,
|
|
3886
|
-
preview: extractText(m?.content).slice(0,
|
|
3962
|
+
preview: extractText(m?.content).slice(0, TRUNC.PREVIEW),
|
|
3887
3963
|
toolCalls: getToolCallNames(m?.content),
|
|
3888
3964
|
isError: m?.isError
|
|
3889
3965
|
})));
|
|
@@ -3906,7 +3982,7 @@ function executeExplorationTool(call, llmMessages) {
|
|
|
3906
3982
|
return JSON.stringify(matches.map(({ idx, m }) => ({
|
|
3907
3983
|
idx,
|
|
3908
3984
|
role: m?.role,
|
|
3909
|
-
preview: extractText(m?.content).slice(0,
|
|
3985
|
+
preview: extractText(m?.content).slice(0, TRUNC.PREVIEW)
|
|
3910
3986
|
})));
|
|
3911
3987
|
}
|
|
3912
3988
|
case "get_recent_user_messages": {
|
|
@@ -3919,7 +3995,7 @@ function executeExplorationTool(call, llmMessages) {
|
|
|
3919
3995
|
return JSON.stringify(llmMessages.slice(s, e).map((m, i) => ({
|
|
3920
3996
|
idx: s + i,
|
|
3921
3997
|
role: m?.role,
|
|
3922
|
-
text: extractText(m?.content).slice(0,
|
|
3998
|
+
text: extractText(m?.content).slice(0, TRUNC.DETAIL),
|
|
3923
3999
|
toolCalls: getToolCallNames(m?.content),
|
|
3924
4000
|
isError: m?.isError
|
|
3925
4001
|
})));
|
|
@@ -3931,22 +4007,16 @@ function executeExplorationTool(call, llmMessages) {
|
|
|
3931
4007
|
const tcs = filterToolCalls(llmMessages[i]?.content);
|
|
3932
4008
|
for (const block of tcs) {
|
|
3933
4009
|
const a = block.arguments ?? {};
|
|
3934
|
-
const fileFields = [
|
|
3935
|
-
a.path,
|
|
3936
|
-
a.file,
|
|
3937
|
-
a.filePath,
|
|
3938
|
-
a.file_path
|
|
3939
|
-
].filter((v) => typeof v === "string").map((v) => v.toLowerCase());
|
|
4010
|
+
const fileFields = [a.path, a.file, a.filePath, a.file_path].filter((v) => typeof v === "string").map((v) => v.toLowerCase());
|
|
3940
4011
|
const matchesPath = fileFields.some((f) => f.includes(target));
|
|
3941
|
-
if (block.
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
results.push({ idx: i, role: "assistant", toolCall: "write", preview: extractText(llmMessages[i]?.content).slice(0, 400) });
|
|
4012
|
+
if (classifyTool(block.arguments) === "mutates" && matchesPath) {
|
|
4013
|
+
const surgical = a.oldText != null || a.newText != null || a.edits != null || a.patch != null;
|
|
4014
|
+
const preview = extractText(llmMessages[i]?.content).slice(0, TRUNC.PREVIEW_LONG);
|
|
4015
|
+
results.push(surgical ? { idx: i, role: "assistant", toolCall: block.name ?? "mutates", args: block.arguments, preview } : { idx: i, role: "assistant", toolCall: block.name ?? "mutates", preview });
|
|
3946
4016
|
}
|
|
3947
4017
|
}
|
|
3948
4018
|
}
|
|
3949
|
-
return JSON.stringify(results.slice(0,
|
|
4019
|
+
return JSON.stringify(results.slice(0, TRUNC.EXPLORE_RESULTS) || [{ info: "No edits found for: " + args.path }]);
|
|
3950
4020
|
}
|
|
3951
4021
|
case "get_error_chain": {
|
|
3952
4022
|
const errIdx = args.index ?? 0;
|
|
@@ -3955,7 +4025,7 @@ function executeExplorationTool(call, llmMessages) {
|
|
|
3955
4025
|
return JSON.stringify(llmMessages.slice(s, e).map((m, i) => ({
|
|
3956
4026
|
idx: s + i,
|
|
3957
4027
|
role: m?.role,
|
|
3958
|
-
text: extractText(m?.content).slice(0,
|
|
4028
|
+
text: extractText(m?.content).slice(0, TRUNC.PREVIEW_XL),
|
|
3959
4029
|
isError: m?.isError,
|
|
3960
4030
|
toolCalls: getToolCallNames(m?.content)
|
|
3961
4031
|
})));
|
|
@@ -4009,7 +4079,7 @@ function normalizeBoundaries(raw, llmLength) {
|
|
|
4009
4079
|
const confidence = b.confidence;
|
|
4010
4080
|
return {
|
|
4011
4081
|
afterIndex: Math.max(0, Math.min(b.afterIndex, maxIndex)),
|
|
4012
|
-
topic: String(b.topic ?? "").slice(0,
|
|
4082
|
+
topic: String(b.topic ?? "").slice(0, TRUNC.TOPIC_LABEL),
|
|
4013
4083
|
priority: typeof priority === "string" && BOUNDARY_PRIORITIES.includes(priority) ? priority : "normal",
|
|
4014
4084
|
confidence: typeof confidence === "number" && Number.isFinite(confidence) ? Math.min(1, Math.max(0, confidence)) : 0.5
|
|
4015
4085
|
};
|
|
@@ -4057,12 +4127,12 @@ async function exploreConversation(llmMessages, extraction, model, auth, prevSum
|
|
|
4057
4127
|
"Main goal: " + (extraction.mainGoal ?? "unknown"),
|
|
4058
4128
|
"Files modified (" + extraction.modifiedFiles.length + "): " + (extraction.modifiedFiles.map((f) => f.path).join(", ") || "none"),
|
|
4059
4129
|
"Files read (" + extraction.readFiles.length + "): " + (extraction.readFiles.join(", ") || "none"),
|
|
4060
|
-
"Errors (" + extraction.errors.length + "): " + (extraction.errors.map((e) => "[" + e.tool + "] " + e.message.slice(0,
|
|
4061
|
-
"Decisions (" + extraction.decisions.length + "): " + (extraction.decisions.map((d) => d.type + ": " + d.summary.slice(0,
|
|
4062
|
-
"Constraints (" + extraction.constraints.length + "): " + (extraction.constraints.map((cc) => "[" + cc.category + "] " + cc.text.slice(0,
|
|
4130
|
+
"Errors (" + extraction.errors.length + "): " + (extraction.errors.map((e) => "[" + e.tool + "] " + e.message.slice(0, TRUNC.SNIPPET) + (e.resolved ? " (resolved)" : e.retryAttempted ? " (retry attempted)" : "")).join("; ") || "none"),
|
|
4131
|
+
"Decisions (" + extraction.decisions.length + "): " + (extraction.decisions.map((d) => d.type + ": " + d.summary.slice(0, TRUNC.SNIPPET)).join("; ") || "none"),
|
|
4132
|
+
"Constraints (" + extraction.constraints.length + "): " + (extraction.constraints.map((cc) => "[" + cc.category + "] " + cc.text.slice(0, TRUNC.SNIPPET)).join("; ") || "none"),
|
|
4063
4133
|
"Heuristic topics (" + extraction.topics.length + "): " + (extraction.topics.map((t) => "[" + t.startIndex + "-" + t.endIndex + "] " + t.type).join("; ") || "none"),
|
|
4064
|
-
extraction.lastUserMessages.length ? "Last user messages: " + extraction.lastUserMessages.map((m) => m.slice(0,
|
|
4065
|
-
extraction.lastErrors.length ? "Last errors: " + extraction.lastErrors.map((e) => e.slice(0,
|
|
4134
|
+
extraction.lastUserMessages.length ? "Last user messages: " + extraction.lastUserMessages.map((m) => m.slice(0, TRUNC.TOPIC_LABEL)).join(" | ") : "",
|
|
4135
|
+
extraction.lastErrors.length ? "Last errors: " + extraction.lastErrors.map((e) => e.slice(0, TRUNC.TOPIC_LABEL)).join(" | ") : ""
|
|
4066
4136
|
].filter(Boolean).join(`
|
|
4067
4137
|
`);
|
|
4068
4138
|
const userContent = `Explore this conversation and produce the structured report.
|
|
@@ -4177,7 +4247,7 @@ async function exploreConversation(llmMessages, extraction, model, auth, prevSum
|
|
|
4177
4247
|
return { report, rounds: 1, toolSupported: supportsTools };
|
|
4178
4248
|
}
|
|
4179
4249
|
async function explorationRetry(model, auth, llmMessages, extraction, prevSummary, userNote, signal, services) {
|
|
4180
|
-
const last5 = llmMessages.slice(-5).map((m) => "[" + m?.role + "] " + extractText(m?.content).slice(0,
|
|
4250
|
+
const last5 = llmMessages.slice(-5).map((m) => "[" + m?.role + "] " + extractText(m?.content).slice(0, TRUNC.PREVIEW)).join(`
|
|
4181
4251
|
`);
|
|
4182
4252
|
const retryPrompt = `IMPORTANT: Output ONLY valid raw JSON. No markdown. No explanation. No code fences. Just the JSON object.
|
|
4183
4253
|
|
|
@@ -4203,10 +4273,10 @@ User steering: ` + userNote : "");
|
|
|
4203
4273
|
}
|
|
4204
4274
|
}
|
|
4205
4275
|
async function directExploration(llmMessages, extraction, model, auth, prevSummary, userNote, signal, services) {
|
|
4206
|
-
const first3 = llmMessages.filter((m) => m?.role === "user").slice(0, 3).map((m) => extractText(m?.content).slice(0,
|
|
4276
|
+
const first3 = llmMessages.filter((m) => m?.role === "user").slice(0, 3).map((m) => extractText(m?.content).slice(0, TRUNC.PREVIEW_MID)).join(`
|
|
4207
4277
|
---
|
|
4208
4278
|
`);
|
|
4209
|
-
const last30 = llmMessages.slice(-30).map((m) => "[" + m?.role + "] " + extractText(m?.content).slice(0,
|
|
4279
|
+
const last30 = llmMessages.slice(-30).map((m) => "[" + m?.role + "] " + extractText(m?.content).slice(0, TRUNC.DETAIL)).join(`
|
|
4210
4280
|
`);
|
|
4211
4281
|
const prompt = `Analyze this conversation and produce a JSON report.
|
|
4212
4282
|
|
|
@@ -4215,9 +4285,9 @@ First user messages:
|
|
|
4215
4285
|
|
|
4216
4286
|
Deterministic data:
|
|
4217
4287
|
` + "- Files modified: " + (extraction.modifiedFiles.map((f) => f.path).join(", ") || "none") + `
|
|
4218
|
-
- Errors: ` + (extraction.errors.map((e) => e.message.slice(0,
|
|
4219
|
-
- Decisions: ` + (extraction.decisions.map((d) => d.summary.slice(0,
|
|
4220
|
-
- Constraints: ` + (extraction.constraints.map((c) => c.text.slice(0,
|
|
4288
|
+
- Errors: ` + (extraction.errors.map((e) => e.message.slice(0, TRUNC.SNIPPET)).join("; ") || "none") + `
|
|
4289
|
+
- Decisions: ` + (extraction.decisions.map((d) => d.summary.slice(0, TRUNC.SNIPPET)).join("; ") || "none") + `
|
|
4290
|
+
- Constraints: ` + (extraction.constraints.map((c) => c.text.slice(0, TRUNC.SNIPPET)).join("; ") || "none") + `
|
|
4221
4291
|
|
|
4222
4292
|
Last 30 messages:
|
|
4223
4293
|
` + last30 + (prevSummary ? `
|
|
@@ -4330,7 +4400,7 @@ Session-specific instructions:
|
|
|
4330
4400
|
async function summarizeBatch(batch, extraction, model, auth, signal, services) {
|
|
4331
4401
|
const range = { start: batch[0].startIndex, end: batch[batch.length - 1].endIndex };
|
|
4332
4402
|
const extractionCtx = buildExtractionContext(extraction, range);
|
|
4333
|
-
const activeDecisions = extraction.decisions.filter((d) => d.index < range.start).map((d) => "- " + d.summary.slice(0,
|
|
4403
|
+
const activeDecisions = extraction.decisions.filter((d) => d.index < range.start).map((d) => "- " + d.summary.slice(0, TRUNC.OPEN_LOOP_SUMMARY) + (d.userResponse ? " \u2192 " + d.userResponse.slice(0, TRUNC.DECISION_DETAIL) : ""));
|
|
4334
4404
|
const decisionCtx = activeDecisions.length ? `
|
|
4335
4405
|
## Active Decisions from previous segments (honour these):
|
|
4336
4406
|
` + activeDecisions.join(`
|
|
@@ -4340,8 +4410,8 @@ async function summarizeBatch(batch, extraction, model, auth, signal, services)
|
|
|
4340
4410
|
return "--- CHUNK " + id + ": " + ch.topic + " (" + ch.priority + `) ---
|
|
4341
4411
|
` + ch.messages.map((m) => {
|
|
4342
4412
|
const role = m?.role ?? "unknown";
|
|
4343
|
-
const content = extractText(m?.content).slice(0,
|
|
4344
|
-
const toolCalls = filterToolCalls(m?.content).map((tc) => tc.name + " " + JSON.stringify(tc.arguments).slice(0,
|
|
4413
|
+
const content = extractText(m?.content).slice(0, TRUNC.PREVIEW_XL);
|
|
4414
|
+
const toolCalls = filterToolCalls(m?.content).map((tc) => tc.name + " " + JSON.stringify(tc.arguments).slice(0, TRUNC.DETAIL)).join("; ");
|
|
4345
4415
|
const toolSuffix = toolCalls ? `
|
|
4346
4416
|
[tool_calls] ` + toolCalls : "";
|
|
4347
4417
|
return "[" + role + "] " + content + toolSuffix;
|
|
@@ -4383,9 +4453,9 @@ async function summarizeBatch(batch, extraction, model, auth, signal, services)
|
|
|
4383
4453
|
const prio = f("Priority").toLowerCase();
|
|
4384
4454
|
const sectionFallback = sec.split(`
|
|
4385
4455
|
`).slice(1).join(`
|
|
4386
|
-
`).trim().slice(0,
|
|
4387
|
-
const chunkFallback = ch.messages.map((m) => "[" + (m?.role ?? "unknown") + "] " + extractText(m?.content).slice(0,
|
|
4388
|
-
`).slice(0,
|
|
4456
|
+
`).trim().slice(0, TRUNC.PREVIEW_XL);
|
|
4457
|
+
const chunkFallback = ch.messages.map((m) => "[" + (m?.role ?? "unknown") + "] " + extractText(m?.content).slice(0, TRUNC.CHUNK_FALLBACK)).join(`
|
|
4458
|
+
`).slice(0, TRUNC.PREVIEW_XL);
|
|
4389
4459
|
return {
|
|
4390
4460
|
topic: ch.topic,
|
|
4391
4461
|
startIndex: ch.startIndex,
|
|
@@ -4422,18 +4492,18 @@ function assembleFallback(summaries, extraction) {
|
|
|
4422
4492
|
extraction.mainGoal ?? "See topics below.",
|
|
4423
4493
|
"",
|
|
4424
4494
|
"## Constraints & Preferences",
|
|
4425
|
-
...extraction.constraints.map((c) => "- [" + c.category + "] " + c.text.slice(0,
|
|
4495
|
+
...extraction.constraints.map((c) => "- [" + c.category + "] " + c.text.slice(0, TRUNC.PREVIEW_MID)),
|
|
4426
4496
|
"",
|
|
4427
4497
|
"## Progress",
|
|
4428
4498
|
"### Done",
|
|
4429
4499
|
"- See topics below",
|
|
4430
4500
|
"### In Progress",
|
|
4431
|
-
...summaries.filter((s) => s.priority === "high").map((s) => "- [ ] " + s.summary.slice(0,
|
|
4501
|
+
...summaries.filter((s) => s.priority === "high").map((s) => "- [ ] " + s.summary.slice(0, TRUNC.PREVIEW)),
|
|
4432
4502
|
"### Blocked",
|
|
4433
4503
|
"- None",
|
|
4434
4504
|
"",
|
|
4435
4505
|
"## Key Decisions",
|
|
4436
|
-
...extraction.decisions.map((d) => "- **" + d.summary.slice(0,
|
|
4506
|
+
...extraction.decisions.map((d) => "- **" + d.summary.slice(0, TRUNC.TOPIC_LABEL) + "**" + (d.userResponse ? " \u2192 " + d.userResponse : "")),
|
|
4437
4507
|
"",
|
|
4438
4508
|
"## Files Modified",
|
|
4439
4509
|
...detModified.map((f) => "- " + f),
|
|
@@ -4445,10 +4515,10 @@ function assembleFallback(summaries, extraction) {
|
|
|
4445
4515
|
"1. See topics below",
|
|
4446
4516
|
"",
|
|
4447
4517
|
"## Critical Context",
|
|
4448
|
-
...extraction.errors.filter((e) => !e.resolved).map((e) => "- Unresolved error: " + e.message.slice(0,
|
|
4518
|
+
...extraction.errors.filter((e) => !e.resolved).map((e) => "- Unresolved error: " + e.message.slice(0, TRUNC.TOPIC_LABEL)),
|
|
4449
4519
|
"",
|
|
4450
4520
|
"## Topics Covered",
|
|
4451
|
-
...summaries.map((s) => "- **" + s.topic + "** [" + s.priority + "]: " + s.summary.slice(0,
|
|
4521
|
+
...summaries.map((s) => "- **" + s.topic + "** [" + s.priority + "]: " + s.summary.slice(0, TRUNC.PREVIEW_MID))
|
|
4452
4522
|
].join(`
|
|
4453
4523
|
`);
|
|
4454
4524
|
}
|
|
@@ -4458,7 +4528,7 @@ function failedChunkSummary(ch) {
|
|
|
4458
4528
|
startIndex: ch.startIndex,
|
|
4459
4529
|
endIndex: ch.endIndex,
|
|
4460
4530
|
summary: "[Failed] " + ch.messages.map((m) => extractText(m.content)).join(`
|
|
4461
|
-
`).slice(0,
|
|
4531
|
+
`).slice(0, TRUNC.DETAIL),
|
|
4462
4532
|
keyDecisions: [],
|
|
4463
4533
|
filesModified: [],
|
|
4464
4534
|
filesRead: [],
|
|
@@ -4860,6 +4930,16 @@ function appendToSection(summary, kind, text, fallbackBody = "") {
|
|
|
4860
4930
|
` : "") + text.trim());
|
|
4861
4931
|
}
|
|
4862
4932
|
|
|
4933
|
+
// src/domain/keywords.ts
|
|
4934
|
+
function extractCheckKeywords(text, max) {
|
|
4935
|
+
const words = text.split(/\s+/).map((w) => w.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu, "")).filter((w) => w.length > 3);
|
|
4936
|
+
if (!words.length)
|
|
4937
|
+
return [];
|
|
4938
|
+
const salient = (w) => /^[A-Z\u00C0-\u00DE]/.test(w) || /[0-9]/.test(w);
|
|
4939
|
+
const preferred = words.filter(salient);
|
|
4940
|
+
return (preferred.length ? preferred : words).slice(0, max);
|
|
4941
|
+
}
|
|
4942
|
+
|
|
4863
4943
|
// src/phases/verify.ts
|
|
4864
4944
|
function verifySummary(summary, extraction) {
|
|
4865
4945
|
const parsed = parseSummary(summary);
|
|
@@ -4891,22 +4971,22 @@ function verifySummary(summary, extraction) {
|
|
|
4891
4971
|
}
|
|
4892
4972
|
}
|
|
4893
4973
|
for (const e of extraction.errors.filter((e2) => !e2.resolved)) {
|
|
4894
|
-
const snippet = e.message.slice(0,
|
|
4974
|
+
const snippet = e.message.trim().replace(/\s+/g, " ").slice(0, TRUNC.ERROR_SNIPPET).toLowerCase();
|
|
4895
4975
|
if (snippet.length > 5 && !lower.includes(snippet)) {
|
|
4896
|
-
gaps.push("Missing error: " + e.message.slice(0,
|
|
4976
|
+
gaps.push("Missing error: " + e.message.slice(0, TRUNC.SNIPPET));
|
|
4897
4977
|
score -= 5;
|
|
4898
4978
|
}
|
|
4899
4979
|
}
|
|
4900
4980
|
for (const c of extraction.constraints.filter((c2) => c2.confidence >= 0.8)) {
|
|
4901
|
-
const keywords = c.text
|
|
4981
|
+
const keywords = extractCheckKeywords(c.text, 3);
|
|
4902
4982
|
const found = keywords.some((k) => lower.includes(k.toLowerCase()));
|
|
4903
4983
|
if (!found && keywords.length > 0) {
|
|
4904
|
-
gaps.push("Missing constraint: " + c.text.slice(0,
|
|
4984
|
+
gaps.push("Missing constraint: " + c.text.slice(0, TRUNC.TOPIC_LABEL));
|
|
4905
4985
|
score -= 3;
|
|
4906
4986
|
}
|
|
4907
4987
|
}
|
|
4908
4988
|
if (extraction.mainGoal) {
|
|
4909
|
-
const goalWords = extraction.mainGoal
|
|
4989
|
+
const goalWords = extractCheckKeywords(extraction.mainGoal, 4);
|
|
4910
4990
|
const goalFound = goalWords.some((w) => lower.includes(w.toLowerCase()));
|
|
4911
4991
|
if (!goalFound) {
|
|
4912
4992
|
gaps.push("Main goal may be missing from summary");
|
|
@@ -4949,9 +5029,9 @@ function verifySummary(summary, extraction) {
|
|
|
4949
5029
|
const decisionSection = findSection(parsed, "decisions");
|
|
4950
5030
|
const decisionBody = decisionSection?.body.toLowerCase() ?? "";
|
|
4951
5031
|
for (const d of highConfDecisions) {
|
|
4952
|
-
const keywords = d.summary
|
|
5032
|
+
const keywords = extractCheckKeywords(d.summary, 3);
|
|
4953
5033
|
if (keywords.length > 0 && !keywords.some((k) => decisionBody.includes(k.toLowerCase()))) {
|
|
4954
|
-
gaps.push("Missing decision: " + d.summary.slice(0,
|
|
5034
|
+
gaps.push("Missing decision: " + d.summary.slice(0, TRUNC.TOPIC_LABEL));
|
|
4955
5035
|
score -= 3;
|
|
4956
5036
|
}
|
|
4957
5037
|
}
|
|
@@ -5130,7 +5210,7 @@ function loadCompactionState(projectId) {
|
|
|
5130
5210
|
updatedAt = 0;
|
|
5131
5211
|
}
|
|
5132
5212
|
}
|
|
5133
|
-
if (Date.now() - updatedAt >
|
|
5213
|
+
if (Date.now() - updatedAt > SEVEN_DAYS_MS)
|
|
5134
5214
|
return null;
|
|
5135
5215
|
}
|
|
5136
5216
|
return data;
|
|
@@ -5139,21 +5219,18 @@ function buildCompactionState(extraction, openLoops, report, nextActions, critic
|
|
|
5139
5219
|
let decisionId = 0;
|
|
5140
5220
|
let constraintId = 0;
|
|
5141
5221
|
let errorId = 0;
|
|
5142
|
-
const
|
|
5143
|
-
path: f.path,
|
|
5144
|
-
bn: f.path.split("/").pop()?.toLowerCase() ?? ""
|
|
5145
|
-
}));
|
|
5222
|
+
const fileNeedles = extraction.modifiedFiles.map((f) => ({ path: f.path, needles: buildPathNeedles(f.path) }));
|
|
5146
5223
|
return {
|
|
5147
5224
|
goal: extraction.mainGoal,
|
|
5148
5225
|
decisions: extraction.decisions.map((d) => ({
|
|
5149
|
-
id:
|
|
5150
|
-
summary: d.summary.slice(0,
|
|
5151
|
-
...d.userResponse ? { userResponse: d.userResponse.slice(0,
|
|
5226
|
+
id: ID_PREFIX.DECISION + ++decisionId,
|
|
5227
|
+
summary: d.summary.slice(0, TRUNC.DECISION_SUMMARY),
|
|
5228
|
+
...d.userResponse ? { userResponse: d.userResponse.slice(0, TRUNC.USER_RESPONSE) } : {},
|
|
5152
5229
|
type: d.type
|
|
5153
5230
|
})),
|
|
5154
5231
|
constraints: extraction.constraints.map((c) => ({
|
|
5155
5232
|
id: "constraint-" + ++constraintId,
|
|
5156
|
-
text: c.text.slice(0,
|
|
5233
|
+
text: c.text.slice(0, TRUNC.CONSTRAINT_TEXT),
|
|
5157
5234
|
category: c.category,
|
|
5158
5235
|
confidence: c.confidence
|
|
5159
5236
|
})),
|
|
@@ -5162,17 +5239,17 @@ function buildCompactionState(extraction, openLoops, report, nextActions, critic
|
|
|
5162
5239
|
deletedFiles: extraction.deletedFiles,
|
|
5163
5240
|
unresolvedErrors: extraction.errors.filter((e) => !e.resolved).map((e) => {
|
|
5164
5241
|
const msgLower = e.message.toLowerCase();
|
|
5165
|
-
const
|
|
5242
|
+
const match = fileNeedles.find(({ needles }) => needles.some((n) => msgLower.includes(n)));
|
|
5166
5243
|
return {
|
|
5167
|
-
id:
|
|
5168
|
-
message: e.message.slice(0,
|
|
5244
|
+
id: ID_PREFIX.ERROR + ++errorId,
|
|
5245
|
+
message: e.message.slice(0, TRUNC.MESSAGE),
|
|
5169
5246
|
tool: e.tool,
|
|
5170
|
-
files:
|
|
5247
|
+
files: match ? [match.path] : []
|
|
5171
5248
|
};
|
|
5172
5249
|
}),
|
|
5173
5250
|
resolvedErrors: extraction.errors.filter((e) => e.resolved).map((e) => ({
|
|
5174
|
-
id:
|
|
5175
|
-
message: e.message.slice(0,
|
|
5251
|
+
id: ID_PREFIX.ERROR + ++errorId,
|
|
5252
|
+
message: e.message.slice(0, TRUNC.MESSAGE),
|
|
5176
5253
|
tool: e.tool
|
|
5177
5254
|
})),
|
|
5178
5255
|
openLoops,
|
|
@@ -5202,28 +5279,28 @@ function injectOpenLoopsSection(summary, openLoops) {
|
|
|
5202
5279
|
return renderSummary(updated);
|
|
5203
5280
|
}
|
|
5204
5281
|
function computeDelta(prev, current) {
|
|
5205
|
-
const
|
|
5206
|
-
const
|
|
5207
|
-
const
|
|
5208
|
-
const
|
|
5209
|
-
const
|
|
5210
|
-
const
|
|
5282
|
+
const key = (s) => s.toLowerCase().replace(/\s+/g, " ").trim();
|
|
5283
|
+
const prevDecisionTexts = new Set(prev.decisions.map((d) => key(d.summary)));
|
|
5284
|
+
const currDecisionTexts = new Set(current.decisions.map((d) => key(d.summary)));
|
|
5285
|
+
const newDecisions = current.decisions.filter((d) => !prevDecisionTexts.has(key(d.summary))).map((d) => d.summary);
|
|
5286
|
+
const removedDecisions = prev.decisions.filter((d) => !currDecisionTexts.has(key(d.summary))).map((d) => d.summary);
|
|
5287
|
+
const prevLoopSummaries = new Map(prev.openLoops.map((l) => [key(l.summary), l]));
|
|
5288
|
+
const currLoopKeys = new Set(current.openLoops.map((l) => key(l.summary)));
|
|
5211
5289
|
const resolvedLoops = [];
|
|
5212
5290
|
const persistentLoops = [];
|
|
5213
|
-
for (const [
|
|
5214
|
-
if (
|
|
5291
|
+
for (const [k, loop] of prevLoopSummaries) {
|
|
5292
|
+
if (currLoopKeys.has(k))
|
|
5215
5293
|
persistentLoops.push(loop.summary);
|
|
5216
|
-
|
|
5294
|
+
else
|
|
5217
5295
|
resolvedLoops.push(loop.summary);
|
|
5218
|
-
}
|
|
5219
5296
|
}
|
|
5220
|
-
const newLoops = current.openLoops.filter((l) => !prevLoopSummaries.has(l.summary
|
|
5297
|
+
const newLoops = current.openLoops.filter((l) => !prevLoopSummaries.has(key(l.summary))).map((l) => l.summary);
|
|
5221
5298
|
const prevFiles = new Set(prev.modifiedFiles);
|
|
5222
5299
|
const newModifiedFiles = current.modifiedFiles.filter((f) => !prevFiles.has(f));
|
|
5223
|
-
const prevErrorMsgs = new Set(prev.unresolvedErrors.map((e) => e.message
|
|
5224
|
-
const currErrorMsgs = new Set(current.unresolvedErrors.map((e) => e.message
|
|
5225
|
-
const resolvedErrors = prev.unresolvedErrors.filter((e) => !currErrorMsgs.has(e.message
|
|
5226
|
-
const newErrors = current.unresolvedErrors.filter((e) => !prevErrorMsgs.has(e.message
|
|
5300
|
+
const prevErrorMsgs = new Set(prev.unresolvedErrors.map((e) => key(e.message)));
|
|
5301
|
+
const currErrorMsgs = new Set(current.unresolvedErrors.map((e) => key(e.message)));
|
|
5302
|
+
const resolvedErrors = prev.unresolvedErrors.filter((e) => !currErrorMsgs.has(key(e.message))).map((e) => e.message);
|
|
5303
|
+
const newErrors = current.unresolvedErrors.filter((e) => !prevErrorMsgs.has(key(e.message))).map((e) => e.message);
|
|
5227
5304
|
const goalChanged = prev.goal !== current.goal && prev.goal !== null && current.goal !== null;
|
|
5228
5305
|
return {
|
|
5229
5306
|
newDecisions,
|
|
@@ -5244,22 +5321,22 @@ function formatDeltaSection(delta) {
|
|
|
5244
5321
|
lines.push("- **Goal shifted**: " + (delta.previousGoal ?? "?") + " \u2192 see current goal above");
|
|
5245
5322
|
}
|
|
5246
5323
|
if (delta.resolvedLoops.length) {
|
|
5247
|
-
lines.push("- **Resolved loops**: " + delta.resolvedLoops.map((s) => "~~" + s.slice(0,
|
|
5324
|
+
lines.push("- **Resolved loops**: " + delta.resolvedLoops.map((s) => "~~" + s.slice(0, TRUNC.DECISION_DETAIL) + "~~").join(", "));
|
|
5248
5325
|
}
|
|
5249
5326
|
if (delta.persistentLoops.length) {
|
|
5250
|
-
lines.push("- **Still open**: " + delta.persistentLoops.map((s) => s.slice(0,
|
|
5327
|
+
lines.push("- **Still open**: " + delta.persistentLoops.map((s) => s.slice(0, TRUNC.DECISION_DETAIL)).join("; "));
|
|
5251
5328
|
}
|
|
5252
5329
|
if (delta.newLoops.length) {
|
|
5253
|
-
lines.push("- **New loops**: " + delta.newLoops.map((s) => s.slice(0,
|
|
5330
|
+
lines.push("- **New loops**: " + delta.newLoops.map((s) => s.slice(0, TRUNC.DECISION_DETAIL)).join("; "));
|
|
5254
5331
|
}
|
|
5255
5332
|
if (delta.newDecisions.length) {
|
|
5256
|
-
lines.push("- **New decisions**: " + delta.newDecisions.map((s) => s.slice(0,
|
|
5333
|
+
lines.push("- **New decisions**: " + delta.newDecisions.map((s) => s.slice(0, TRUNC.SNIPPET)).join("; "));
|
|
5257
5334
|
}
|
|
5258
5335
|
if (delta.resolvedErrors.length) {
|
|
5259
|
-
lines.push("- **Resolved errors**: " + delta.resolvedErrors.map((s) => s.slice(0,
|
|
5336
|
+
lines.push("- **Resolved errors**: " + delta.resolvedErrors.map((s) => s.slice(0, TRUNC.DECISION_DETAIL)).join("; "));
|
|
5260
5337
|
}
|
|
5261
5338
|
if (delta.newErrors.length) {
|
|
5262
|
-
lines.push("- **New errors**: " + delta.newErrors.map((s) => s.slice(0,
|
|
5339
|
+
lines.push("- **New errors**: " + delta.newErrors.map((s) => s.slice(0, TRUNC.DECISION_DETAIL)).join("; "));
|
|
5263
5340
|
}
|
|
5264
5341
|
if (delta.newModifiedFiles.length) {
|
|
5265
5342
|
lines.push("- **New files touched**: " + delta.newModifiedFiles.join(", "));
|
|
@@ -5294,17 +5371,17 @@ function ensurePinnedPaths(summary, pinned) {
|
|
|
5294
5371
|
return renderSummary(updated);
|
|
5295
5372
|
}
|
|
5296
5373
|
function extractNextActions(summary) {
|
|
5297
|
-
const
|
|
5298
|
-
if (!
|
|
5374
|
+
const section = findSection(summary, "next-steps");
|
|
5375
|
+
if (!section)
|
|
5299
5376
|
return [];
|
|
5300
|
-
return
|
|
5377
|
+
return section.body.split(`
|
|
5301
5378
|
`).map((l) => l.replace(/^\d+\.\s*/, "").trim()).filter((l) => l.length > 0);
|
|
5302
5379
|
}
|
|
5303
5380
|
function extractCriticalContext(summary) {
|
|
5304
|
-
const
|
|
5305
|
-
if (!
|
|
5381
|
+
const section = findSection(summary, "critical-context");
|
|
5382
|
+
if (!section)
|
|
5306
5383
|
return [];
|
|
5307
|
-
return
|
|
5384
|
+
return section.body.split(`
|
|
5308
5385
|
`).map((l) => l.replace(/^-\s*/, "").trim()).filter((l) => l.length > 0);
|
|
5309
5386
|
}
|
|
5310
5387
|
|
|
@@ -5312,6 +5389,7 @@ function extractCriticalContext(summary) {
|
|
|
5312
5389
|
init_logger();
|
|
5313
5390
|
init_paths();
|
|
5314
5391
|
init_fs();
|
|
5392
|
+
init_constants();
|
|
5315
5393
|
var COMPLAINT_PATTERNS = [
|
|
5316
5394
|
/(?:I already (?:told|said|mentioned|explained) you|(?:we|I) (?:already|just) (?:discussed|went over|covered) this|you forgot|you lost|nerede kald\u0131|hat\u0131rlam\u0131yor|unuttun)/i,
|
|
5317
5395
|
/(?:that'?s? not (?:what I|right)|that'?s? wrong|yanl\u0131\u015F|hay\u0131r de\u011Fil|no that'|that doesn'?t match)/i,
|
|
@@ -5328,8 +5406,8 @@ function detectDamage(postMessages, details) {
|
|
|
5328
5406
|
if (msg.role === "assistant") {
|
|
5329
5407
|
const blocks = Array.isArray(msg.content) ? msg.content : [];
|
|
5330
5408
|
for (const b of blocks) {
|
|
5331
|
-
if (isToolCallBlock(b) && (b.
|
|
5332
|
-
const fp = b.arguments
|
|
5409
|
+
if (isToolCallBlock(b) && classifyTool(b.arguments) === "accesses") {
|
|
5410
|
+
const fp = extractToolPath(b.arguments);
|
|
5333
5411
|
if (fp) {
|
|
5334
5412
|
const fpLower = fp.toLowerCase();
|
|
5335
5413
|
if (compactedFiles.has(fpLower) || compactedReadFiles.has(fpLower)) {
|
|
@@ -5351,18 +5429,18 @@ function detectDamage(postMessages, details) {
|
|
|
5351
5429
|
signals.push({
|
|
5352
5430
|
type: "user-complaint",
|
|
5353
5431
|
severity: "high",
|
|
5354
|
-
detail: 'User complaint after compaction: "' + text.slice(0,
|
|
5432
|
+
detail: 'User complaint after compaction: "' + text.slice(0, TRUNC.TOPIC_LABEL) + '"'
|
|
5355
5433
|
});
|
|
5356
5434
|
break;
|
|
5357
5435
|
}
|
|
5358
5436
|
}
|
|
5359
5437
|
for (const t of details.topics) {
|
|
5360
|
-
const topicWords = t
|
|
5361
|
-
if (topicWords.length
|
|
5438
|
+
const topicWords = extractCheckKeywords(t, 3);
|
|
5439
|
+
if (topicWords.length > 0 && topicWords.some((w) => text.includes(w.toLowerCase()))) {
|
|
5362
5440
|
signals.push({
|
|
5363
5441
|
type: "re-question",
|
|
5364
5442
|
severity: "low",
|
|
5365
|
-
detail: "User mentions compacted topic: " + t.slice(0,
|
|
5443
|
+
detail: "User mentions compacted topic: " + t.slice(0, TRUNC.SNIPPET)
|
|
5366
5444
|
});
|
|
5367
5445
|
}
|
|
5368
5446
|
}
|
|
@@ -5412,7 +5490,7 @@ function logDamageReport(sessionId, report, details) {
|
|
|
5412
5490
|
warn("logDamageReport failed", e);
|
|
5413
5491
|
}
|
|
5414
5492
|
}
|
|
5415
|
-
var REMEDIATION_TTL_MS =
|
|
5493
|
+
var REMEDIATION_TTL_MS = SEVEN_DAYS_MS;
|
|
5416
5494
|
function writeRemediationHints(projectId, files) {
|
|
5417
5495
|
if (!files.length)
|
|
5418
5496
|
return;
|
|
@@ -5854,34 +5932,29 @@ function unwrapConsumed(result, ctx) {
|
|
|
5854
5932
|
return null;
|
|
5855
5933
|
}
|
|
5856
5934
|
}
|
|
5857
|
-
function
|
|
5858
|
-
const [p, ...r] =
|
|
5935
|
+
function findModelById(ctx, modelId) {
|
|
5936
|
+
const [p, ...r] = modelId.split("/");
|
|
5859
5937
|
return ctx.modelRegistry.find(p, r.join("/"));
|
|
5860
5938
|
}
|
|
5861
|
-
function resolveModels(ctx, primary, config) {
|
|
5939
|
+
function resolveModels(ctx, primary, config, explicit = false) {
|
|
5862
5940
|
const fallback = primary ?? ctx.model;
|
|
5863
5941
|
const available = ctx.modelRegistry.getAvailable();
|
|
5864
5942
|
let sumModel = fallback;
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
const found = ctx.modelRegistry.find(p, r.join("/"));
|
|
5869
|
-
if (found) {
|
|
5943
|
+
if (!explicit && config.summaryModel) {
|
|
5944
|
+
const found = findModelById(ctx, config.summaryModel);
|
|
5945
|
+
if (found)
|
|
5870
5946
|
sumModel = found;
|
|
5871
|
-
break;
|
|
5872
|
-
}
|
|
5873
5947
|
}
|
|
5874
5948
|
if (sumModel === fallback && !fallback)
|
|
5875
5949
|
sumModel = available[0];
|
|
5876
5950
|
let segModel = sumModel;
|
|
5877
5951
|
if (config.segmentationModel) {
|
|
5878
|
-
|
|
5879
|
-
segModel = ctx.modelRegistry.find(p, r.join("/")) ?? sumModel;
|
|
5952
|
+
segModel = findModelById(ctx, config.segmentationModel) ?? sumModel;
|
|
5880
5953
|
}
|
|
5881
5954
|
return { segModel, sumModel };
|
|
5882
5955
|
}
|
|
5883
5956
|
function smartCompactExtension(pi) {
|
|
5884
|
-
const PENDING_TTL_MS =
|
|
5957
|
+
const PENDING_TTL_MS = FIVE_MINUTES_MS;
|
|
5885
5958
|
const pendingRef = createPendingSlot({ ttlMs: PENDING_TTL_MS });
|
|
5886
5959
|
const isRunning = { value: false };
|
|
5887
5960
|
pi.registerCommand("smart-compact", {
|
|
@@ -5978,7 +6051,7 @@ function smartCompactExtension(pi) {
|
|
|
5978
6051
|
ctx.ui.notify("Cancelled", "info");
|
|
5979
6052
|
return;
|
|
5980
6053
|
}
|
|
5981
|
-
const { segModel: segModel2, sumModel: sumModel2 } = resolveModels(ctx, selected.model.model, loadConfig());
|
|
6054
|
+
const { segModel: segModel2, sumModel: sumModel2 } = resolveModels(ctx, selected.model.model, loadConfig(), true);
|
|
5982
6055
|
if (!sumModel2) {
|
|
5983
6056
|
ctx.ui.notify("Could not resolve model", "error");
|
|
5984
6057
|
return;
|
|
@@ -5986,7 +6059,7 @@ function smartCompactExtension(pi) {
|
|
|
5986
6059
|
await runSmartCompact({ ctx, summaryModel: sumModel2, segModel: segModel2 ?? sumModel2, profile: selected.profile, pendingRef, isRunning, force: true });
|
|
5987
6060
|
return;
|
|
5988
6061
|
}
|
|
5989
|
-
const { segModel, sumModel } = resolveModels(ctx, modelArg ?
|
|
6062
|
+
const { segModel, sumModel } = resolveModels(ctx, modelArg ? findModelById(ctx, modelArg) : ctx.model, loadConfig(), Boolean(modelArg));
|
|
5990
6063
|
if (!sumModel) {
|
|
5991
6064
|
ctx.ui.notify("Could not resolve model", "error");
|
|
5992
6065
|
return;
|
|
@@ -6123,5 +6196,7 @@ Dashboard: ` + fp : "") }], details: undefined };
|
|
|
6123
6196
|
});
|
|
6124
6197
|
}
|
|
6125
6198
|
export {
|
|
6199
|
+
resolveModels,
|
|
6200
|
+
findModelById,
|
|
6126
6201
|
smartCompactExtension as default
|
|
6127
6202
|
};
|